Files
dc/sas/sasjs/services/public/getcolvals.sas
T
dc d494606ebd
Build / Build-and-ng-test (pull_request) Successful in 5m29s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m30s
Build / Build-and-test-development (pull_request) Successful in 25m22s
fix: validate request inputs and add admin gates to public services
Security fixes for the input-validation gaps in the public download and
metadata services, plus missing in-code admin gates:

- mpe_accesscheck: validate base_table (LIBDS) and access_level before
  they reach the authorisation query, and escape embedded quotes in the
  SQL literals (defence in depth for direct macro callers)
- getrawdata: validate table (LIBDS / format-catalog form), filter
  (integer) and type before they are used; read all inputs with symget
  in a data step so macro content cannot execute at a resolution boundary
- getdiffs: validate libds, table and stp_diffs_csv before the access
  check and the staging-file stream path
- getcols, getcolvals, validatefilter: read the IWANT inputs with symget
  in a data step and validate (LIBDS / SAS name) before use
- admin dirlist, refreshlibs, refreshcatalog, exportconfig: require
  membership of the DC administrators group (the admin folder prefix is
  not an access control)
- admin dirlist: read parent with symget and reject macro characters

Tests (all proven RED on the vulnerable services first, then GREEN on
the fix): getrawdata.test.1, getdiffs.test, getcols.test,
getcolvals.test.4, validatefilter.test.1, dirlist.test,
refreshcatalog.test.1
2026-09-21 16:06:59 +00:00

219 lines
5.1 KiB
SAS

/**
@file
@brief Retrieves column info to enable population of dropdowns
@details An optional filterquery may be provided, if so then it is validated
and then used to filter the subsequent results.
<h4> Service Inputs </h4>
<h5> IWANT </h5>
The STARTROW and ROWS variables are used to fetch additional values beyond
the initial default (4000).
|libds:$19.|col:$9.|STARTROW:8.|ROWS:8.|
|---|---|---|---|
|DC258467.MPE_X_TEST|SOME_TIME|4001|1000
<h5> FILTERQUERY </h5>
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|---|---|---|---|---|---|
|AND|AND|1|SOME_BESTNUM|>|1|
|AND|AND|1|SOME_TIME|=|77333|
<h4> Service Outputs </h4>
<h5> VALS </h5>
The type of this column actually depends on the underlying column type, so it can change
|FORMATTED|UNFORMATTED|
|---|---|
|$44.00|44|
<h5> META </h5>
|COLUMN:$32.|SASFORMAT:$32.|STARTROW:8.|ROWS:8.|
|---|---|---|---|
|COL_NAME|DOLLAR8.2|4001|1000
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mf_verifymacvars.sas
@li dc_assignlib.sas
@li mf_getvarformat.sas
@li mp_abort.sas
@li mp_cntlout.sas
@li mp_filtercheck.sas
@li mp_filtergenerate.sas
@li mp_validatecol.sas
@version 9.2
@author 4GL Apps Ltd.
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
and may not be re-distributed or re-sold without the express permission of
4GL Apps Ltd.
**/
%mpeinit()
/* input table may or may not exist */
data work.initvars;
length GROUP_LOGIC $3 SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
OPERATOR_NM $10 RAW_VALUE $32767;
call missing(of _all_);
stop;
data work.filterquery;
set %sysfunc(ifc(
%mf_existds(work.filterquery)=1
,work.filterquery
,work.initvars
));
run;
/* print data for debugging */
data _null_;
set work.iwant;
put (_all_)(=);
run;
data _null_;
set work.filterquery;
put (_all_)(=);
run;
/**
* libds and col are request inputs that flow into executable positions
* (set &libds, proc sql select &col2). They are read from the IWANT
* table with symget in a data step (never re-resolved) and validated
* here before use - mf_getvalue would re-resolve any macro content in
* the value before this code ran.
*/
%let libds=;
%let col2=;
%let is_libds=0;
%let is_col=0;
data _null_;
length _libds $64 _col $32 _ds $34;
set work.iwant;
_libds=libds;
_col=col;
%mp_validatecol(_libds,LIBDS,is_libds)
/* permit the format-catalog form: LIBREF.CATALOGNAME-FC */
if is_libds=0 then do;
_ds=scan(_libds,1,'-');
%mp_validatecol(_ds,LIBDS,is_libds)
end;
%mp_validatecol(_col,ISNAME,is_col)
if is_libds=0 then putlog 'ERR' 'OR: Invalid libds:' _libds;
if is_col=0 then putlog 'ERR' 'OR: Invalid col:' _col;
call symputx('is_libds',is_libds,'l');
call symputx('is_col',is_col,'l');
if is_libds=1 then call symputx('libds',upcase(_libds),'l');
if is_col=1 then call symputx('col2',upcase(_col),'l');
stop;
run;
%let is_fmt=0;
%let startrow=1;
%let rows=4000;
%mp_abort(iftrue= (%mf_verifymacvars(libds col2)=0)
,mac=&_program..sas
,msg=%str(Missing inputs from iwant)
)
%mp_abort(iftrue= (&is_libds ne 1)
,mac=&_program..sas
,msg=%str(Invalid libds)
)
%mp_abort(iftrue= (&is_col ne 1)
,mac=&_program..sas
,msg=%str(Invalid col)
)
%dc_assignlib(WRITE,%scan(&libds,1,.))
data _null_;
call missing(startrow,rows);
set work.iwant;
/* check if the request is for a format catalog */
call symputx('orig_libds',libds);
is_fmt=0;
if substr(cats(reverse(libds)),1,3)=:'CF-' then do;
libds=scan(libds,1,'-');
putlog "Format Catalog Captured";
call symputx('libds','work.fmtextract');
is_fmt=1;
end;
call symputx('is_fmt',is_fmt);
call symputx('startrow',coalesce(startrow,&startrow));
call symputx('rows',coalesce(rows,&rows));
putlog (_all_)(=);
run;
%mp_cntlout(
iftrue=(&is_fmt=1)
,libcat=&orig_libds
,fmtlist=0
,cntlout=work.fmtextract
)
/**
* Validate the filter query
*/
%mp_filtercheck(work.filterquery,targetds=&libds,abort=YES)
/**
* Prepare the query
*/
%mp_filtergenerate(work.filterquery,outref=myfilter)
/* cannot %inc in a sql where clause, only data step, so - use a view */
data work.vw_vals/view=work.vw_vals;
set &libds;
where %inc myfilter;;
run;
proc sql;
create view work.vw_vals_sorted as
select distinct
put(&col2,%mf_getVarFormat(&libds,&col2,force=1)) as formatted,
&col2 as unformatted
from work.vw_vals;
/* restrict num of output values */
data work.vals;
set work.vw_vals_sorted;
if _n_ ge &startrow;
x+1;
if x>&rows then stop;
drop x;
run;
data vals;
/* ensure empty value if table is empty, for dropdowns */
if nobs=0 then output;
set vals nobs=nobs;
format unformatted ;
output;
run;
proc sql noprint;
select count(*) into: nobs from work.vw_vals_sorted;
data meta;
column="&col2";
sasformat="%mf_getVarFormat(&libds,&col2)";
startrow=&startrow;
rows=&rows;
nobs=&nobs;
run;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&_program..sas
,msg=%str(syscc=&syscc)
)
%webout(OPEN)
%webout(OBJ,vals,missing=STRING,showmeta=YES)
%webout(OBJ,meta)
%webout(CLOSE)
%mpeterm()