Files
dc/sas/sasjs/services/editors/getdata.test.2.sas
T
dc 355c46a7b6
Build / Build-and-ng-test (pull_request) Successful in 5m30s
Lighthouse Checks / lighthouse (pull_request) Canceled after 14m27s
Build / Build-and-test-development (pull_request) Failing after 34m21s
fix(lint): clear the sasjs lint warnings on the files 7.19 touched
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.
2026-10-27 21:27:54 +00:00

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()