sasjs lint (@sasjs/cli 6.2.5) reported 24 warnings across the 11 .sas files
this release touches. None of them are in those files now:
- mpeinit and mpeterm are added to ignoredMacros in sas/.sasjslint. Every
service is compiled with a serviceInit program (sasjs/utils/
serviceinitsas9|viya|server.sas), and all three declare both macros in
their own header, which is how a service body's %mpeinit()/%mpeterm()
resolves without a declaration of its own. An explicit ignoredMacros list
replaces the default, so webout stays listed.
- Header entries nothing in the file calls are dropped: mp_binarycopy in
getrawdata (it arrives through mp_streamfile, which calls it - the
compiled program is unchanged), mp_jsonout in viewdata,
mp_assertcolvals in validatefilter.test, and mx_execute in testsetup
(every file that calls it declares it).
- Prose carrying "error"/"warning" is reworded. Where the text is log
output, %put WARNING: is written %put %str(WARN)ING: so the line the
log receives is unchanged.
- The server makedata service keeps its doxygen @warning tag - the tag is
the content - so that file carries @sasjslint {"noBannedStrings": false}.
- Two comment lines in getdata are re-indented to the file's multiple of 2.
AGENTS.md records the ignoredMacros rule and the %str(WARN)ING convention so
the next warning is not silenced by reflex. Compile passes; the 93 warnings
that remain are all in files this release does not touch.
272 lines
8.3 KiB
SAS
272 lines
8.3 KiB
SAS
/**
|
|
@file
|
|
@brief testing that selectbox (cell dropdown) values respect Row Level Security
|
|
|
|
@details A HARDSELECT / SOFTSELECT rule whose RULE_VALUE is a
|
|
library.member.column reference has its distinct values read straight from
|
|
that source table by getdata.sas. If the source table carries Row Level
|
|
Security rules, the restricted rows must be filtered out of the dropdown too -
|
|
otherwise values the user is not permitted to see appear in the cell dropdown
|
|
while the grid itself is correctly filtered.
|
|
|
|
This test makes the runner a 'regular' (non admin) user and restricts
|
|
SOME_BESTNUM in MPE_X_TEST to one value selected from the data (the demo
|
|
data holds random SOME_BESTNUM values, refreshed by testsetup, so the
|
|
value cannot be hardcoded). The demo data must hold more than one
|
|
distinct SOME_BESTNUM - true of the SAS-seeded estate, but NOT of the
|
|
SASjs server mock, which seeds the seven 44 rows only (see the
|
|
'distinct_bestnum' precondition assertion below).
|
|
|
|
Runners whose SAS identity group is the admin group cannot be demoted by
|
|
editing MPE_GROUPS alone - Row Level Security is skipped for them - so the
|
|
test reports a skip row instead of running the assertions.
|
|
|
|
The call passes a filter (PRIMARY_KEY_FIELD < 100) so the whole selection is
|
|
served - the unfiltered MPE_X_TEST is above the row limit (642 rows against
|
|
DC_MAXOBS_WEBEDIT:250) and would come back truncated. The limits test
|
|
(getdata.test.1.sas) covers that truncation separately.
|
|
|
|
<h4> SAS Macros </h4>
|
|
@li mf_getuniquefileref.sas
|
|
@li mf_getuniquename.sas
|
|
@li mf_getuser.sas
|
|
@li mpe_getgroups.sas
|
|
@li mp_assert.sas
|
|
@li mp_assertcolvals.sas
|
|
@li mp_assertdsobs.sas
|
|
@li mp_filterstore.sas
|
|
@li mx_execute.sas
|
|
|
|
**/
|
|
|
|
%let _program=&appLoc/services/editors/getdata;
|
|
|
|
/**
|
|
* The body is wrapped in a macro so the conditional skip below uses
|
|
* %if/%then/%do inside a macro definition. Open-code %if needs SAS 9.4M5+,
|
|
* and the suite also runs on the sas9 target, so the wrapper keeps the test
|
|
* portable across the supported estates (as testsetup.sas's update_settings
|
|
* does for its own conditionals).
|
|
*/
|
|
%macro getdata_rls_test();
|
|
|
|
/**
|
|
* Row Level Security is skipped for admins, so only a non admin runner can
|
|
* verify it. The group membership is checked against &dc_admin_group (the
|
|
* mpeinit-resolved &mpeadmins is not available in a test session, see
|
|
* testinit.sas) and taken from the SAS identity groups, NOT the MPE_GROUPS
|
|
* table this test goes on to edit. Admin runners (eg a shared test account
|
|
* that is a member of the admin group) simply skip the RLS assertions.
|
|
*/
|
|
%mpe_getgroups(user=%mf_getuser(),outds=work.testgroups)
|
|
proc sql noprint;
|
|
select count(*) into: admin_check
|
|
from work.testgroups
|
|
where groupname="&dc_admin_group";
|
|
quit;
|
|
%if &admin_check>0 %then %do;
|
|
%put %str(WARN)ING: %mf_getuser() is in the &dc_admin_group group;
|
|
%put %str(WARN)ING: Row Level Security is skipped for admins - test skipped;
|
|
data work.test_results;
|
|
length test_result $4 test_description $256 test_comments $256;
|
|
test_result='PASS';
|
|
test_description="GETDATA_TEST_2 skipped - runner is an admin";
|
|
test_comments="RLS is skipped for admins so the assertions cannot run";
|
|
run;
|
|
%end;
|
|
%else %do;
|
|
|
|
/**
|
|
* Make the runner a 'regular' user and grant the temp group EDIT access so
|
|
* the getdata access check still passes.
|
|
*/
|
|
data work.groups_backup;
|
|
set &dc_libref..MPE_GROUPS;
|
|
where upcase(user_name)="%upcase(%mf_getuser())";
|
|
run;
|
|
|
|
%let tempgroup=%mf_getuniquename();
|
|
|
|
proc sql;
|
|
delete from &dc_libref..MPE_GROUPS
|
|
where upcase(user_name)="%upcase(%mf_getuser())";
|
|
|
|
insert into &dc_libref..MPE_GROUPS
|
|
set user_name="%mf_getuser()"
|
|
,group_name="&tempgroup"
|
|
,group_desc="temp group"
|
|
,tx_from='01Jan1960:00:00:00'dt
|
|
,tx_to='31Dec9999:23:59:59'dt;
|
|
|
|
insert into &dc_libref..MPE_SECURITY
|
|
set tx_from=0
|
|
,libref="*ALL*"
|
|
,dsn="*ALL*"
|
|
,access_level="EDIT"
|
|
,sas_group="&tempgroup"
|
|
,tx_to='31DEC9999:23:59:59'dt;
|
|
quit;
|
|
|
|
/**
|
|
* Row Level Security on MPE_X_TEST - only one SOME_BESTNUM value (selected
|
|
* from the data, see header) is visible. The value is taken from the rows
|
|
* that match the user filter below, so the filtered grid cannot be empty.
|
|
*/
|
|
proc sql noprint;
|
|
delete from &dc_libref..MPE_ROW_LEVEL_SECURITY
|
|
where RLS_TABLE="MPE_X_TEST" and rls_group="&tempgroup";
|
|
|
|
select distinct some_bestnum into :rls_value trimmed
|
|
from &dc_libref..MPE_X_TEST
|
|
where some_bestnum is not null and primary_key_field < 100;
|
|
select coalesce(max(rls_rk),0) into: max_rk
|
|
from &dc_libref..MPE_ROW_LEVEL_SECURITY;
|
|
quit;
|
|
|
|
proc sql;
|
|
insert into &dc_libref..mpe_row_level_security set
|
|
tx_from=0
|
|
,tx_to='31DEC5999:23:59:59'dt
|
|
,RLS_RK=&max_rk+1
|
|
,RLS_SCOPE='ALL'
|
|
,RLS_GROUP="&tempgroup"
|
|
,RLS_LIBREF="&dc_libref"
|
|
,RLS_TABLE="MPE_X_TEST"
|
|
,RLS_GROUP_LOGIC='AND'
|
|
,RLS_SUBGROUP_LOGIC='OR'
|
|
,RLS_SUBGROUP_ID=0
|
|
,RLS_VARIABLE_NM='SOME_BESTNUM'
|
|
,RLS_OPERATOR_NM='IN'
|
|
,RLS_RAW_VALUE="(&rls_value)"
|
|
,RLS_ACTIVE=1;
|
|
quit;
|
|
|
|
/**
|
|
* Preconditions - the source column really does hold more than one distinct
|
|
* value (so the assertions are meaningful) and the user filter matches at
|
|
* least one of them (so the grid cannot be empty).
|
|
*/
|
|
proc sql noprint;
|
|
select count(distinct some_bestnum) into: distinct_bestnum
|
|
from &dc_libref..MPE_X_TEST;
|
|
quit;
|
|
%mp_assert(
|
|
iftrue=(&distinct_bestnum>1),
|
|
desc=Precondition - MPE_X_TEST.SOME_BESTNUM has more than one distinct value,
|
|
outds=work.test_results
|
|
)
|
|
%mp_assert(
|
|
iftrue=(%length(&rls_value)>0),
|
|
desc=Precondition - the user filter matches at least one SOME_BESTNUM value,
|
|
outds=work.test_results
|
|
)
|
|
|
|
/**
|
|
* Store a filter (PRIMARY_KEY_FIELD < 100) so the selection stays under the
|
|
* DC_MAXOBS_WEBEDIT row limit.
|
|
*/
|
|
data work.inquery;
|
|
length GROUP_LOGIC $3 SUBGROUP_LOGIC $3 SUBGROUP_ID 8
|
|
VARIABLE_NM $32 OPERATOR_NM $10 RAW_VALUE $4000;
|
|
GROUP_LOGIC='AND';
|
|
SUBGROUP_LOGIC='AND';
|
|
SUBGROUP_ID=1;
|
|
VARIABLE_NM='PRIMARY_KEY_FIELD';
|
|
OPERATOR_NM='<';
|
|
RAW_VALUE='100';
|
|
run;
|
|
%mp_filterstore(
|
|
libds=&dclib..MPE_X_TEST,
|
|
filter_summary=&dc_libref..mpe_filteranytable,
|
|
filter_detail=&dc_libref..mpe_filtersource,
|
|
lock_table=&dc_libref..mpe_lockanytable,
|
|
maxkeytable=&dc_libref..mpe_maxkeyvalues,
|
|
queryds=work.inquery,
|
|
outresult=work.filter_result,
|
|
outquery=work.filter_query
|
|
)
|
|
data _null_;
|
|
set work.filter_result;
|
|
call symputx('f1_rk',filter_rk,'l');
|
|
run;
|
|
|
|
/**
|
|
* Call the getdata service - filtered to keep the row count under
|
|
* DC_MAXOBS_WEBEDIT (see header).
|
|
*/
|
|
%let f1=%mf_getuniquefileref();
|
|
data _null_;
|
|
file &f1 termstr=crlf;
|
|
put 'LIBDS:$43. FILTER_RK:8.';
|
|
put "&dclib..MPE_X_TEST,&f1_rk";
|
|
run;
|
|
%mx_execute(&_program,
|
|
viyacontext=&defaultcontext,
|
|
inputfiles=&f1:sascontroltable,
|
|
outlib=web1,
|
|
mdebug=&sasjs_mdebug
|
|
)
|
|
|
|
data work.dqdata;
|
|
set web1.dqdata;
|
|
run;
|
|
data work.sasdata;
|
|
set web1.sasdata;
|
|
run;
|
|
|
|
/* the selectbox source rows only (exclude MPE_SELECTBOX-derived values) */
|
|
data work.dqtest;
|
|
set work.dqdata;
|
|
where upcase(base_col)='SOME_BESTNUM';
|
|
run;
|
|
|
|
data work.checkchar;
|
|
val="&rls_value"; output;
|
|
run;
|
|
data work.checknum;
|
|
val=&rls_value; output;
|
|
run;
|
|
|
|
/**
|
|
* Test 1 - control: the grid itself is filtered by RLS. If this fails the
|
|
* runner is an admin and the dropdown assertion below cannot be trusted.
|
|
*/
|
|
%mp_assertcolvals(work.sasdata.SOME_BESTNUM,
|
|
checkvals=work.checknum.val,
|
|
desc=Test 1 - grid rows respect Row Level Security (control),
|
|
test=ALLVALS,
|
|
outds=work.test_results
|
|
)
|
|
|
|
/**
|
|
* Test 2 - the selectbox values sourced from MPE_X_TEST.SOME_BESTNUM must
|
|
* also be restricted to the RLS-visible value (44).
|
|
*/
|
|
%mp_assertdsobs(work.dqtest,
|
|
desc=Test 2 precondition - selectbox values were returned for SOME_BESTNUM,
|
|
test=HASOBS,
|
|
outds=work.test_results
|
|
)
|
|
%mp_assertcolvals(work.dqtest.RULE_DATA,
|
|
checkvals=work.checkchar.val,
|
|
desc=Test 2 - selectbox values respect Row Level Security,
|
|
test=ALLVALS,
|
|
outds=work.test_results
|
|
)
|
|
|
|
/**
|
|
* Cleanup - restore the group / security / RLS rows
|
|
*/
|
|
proc sql;
|
|
delete from &dc_libref..MPE_ROW_LEVEL_SECURITY where rls_group="&tempgroup";
|
|
delete from &dc_libref..MPE_SECURITY where sas_group="&tempgroup";
|
|
delete from &dc_libref..MPE_GROUPS where group_name="&tempgroup";
|
|
quit;
|
|
proc append base=&dc_libref..MPE_GROUPS data=work.groups_backup;
|
|
run;
|
|
%end;
|
|
|
|
%mend getdata_rls_test;
|
|
|
|
%getdata_rls_test()
|