Files
dc/sas/sasjs/services/public/getcolvals.sas
T
dc 0fa5da8abf
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m53s
Build / Build-and-test-development (pull_request) Successful in 25m7s
fix(security): accept the format-catalog form when validating a libds
mpe_accesscheck and validatefilter validated their libds input with
mp_validatecol(LIBDS), which rejects the LIBREF.CATALOGNAME-FC form that
Data Controller uses to address a format catalog.  A format-catalog load
or filter therefore aborted with "Invalid base_table" / "Invalid
filter_table" - stagedata, getdata and postdata all reach mpe_accesscheck
through the edit/approve path.

Add mpe_validatecol, a wrapper that permits the catalog form: the -FC
suffix is matched exactly and the remainder is validated as a strict
LIBREF.DATASET, so the whole value is covered and a caller that needs the
catalog reference downstream (MPE_SECURITY stores it with the suffix)
still receives it.  getrawdata and getcolvals had grown an inline version
of this check that scanned on the dash and validated only the prefix,
leaving whatever followed it unvalidated; the wrapper replaces both.

mpe_validatecol.test.sas asserts the matrix - plain libds, catalog form,
and payloads that smuggle content past a valid libds prefix.
2026-09-22 11:23:37 +00:00

215 lines
5.0 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
@li mpe_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;
set work.iwant;
_libds=libds;
_col=col;
%mpe_validatecol(_libds,LIBDS,is_libds)
%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()