Files
dc/sas/sasjs/services/hooks/mpe_row_level_security_postedit.sas
T
dc 688550a28d
Lighthouse Checks / lighthouse (pull_request) Successful in 21m0s
Build / Build-and-ng-test (pull_request) Successful in 5m13s
Build / Build-and-test-development (pull_request) Successful in 32m7s
fix(derived): stop the hooks aborting on their own macro variables
A hook is %included from %mp_include, so its open-code %let runs in that
macro's local scope: a %global for the same name inside the hook raises
"exists in a local environment", sets SYSCC, and puts the session into syntax
check mode - every check after it silently skipped while the step still
aborts. The derived hook now sets its (renamed) control variables in the
including scope and does not %global them.

The RLS hook also referenced rls_msg before assigning it whenever there was no
derived conflict, which produced an unresolved-symbol WARNING and SYSCC=4 on
every clean rule submission. It is initialised now.
2026-10-23 10:32:49 +00:00

134 lines
4.0 KiB
SAS

/**
@file
@brief Post Edit Hook script for the MPE_ROW_LEVEL_SECURITY table
@details Post edit hooks provide additional backend validation for user
provided data. The incoming dataset is named `work.staging_ds` and is
provided in mpe_loader.sas.
Available macro variables:
@li DC_LIBREF - The DC control library
@li LIBREF - The library of the dataset being edited (is assigned)
@li DS - The dataset being edited
This validation checks the incoming row_level_security settings to ensure
each individual filter is
It also checks that EDIT scope security is not being applied to tables
with a REPLACE loadtype (these are incompatible - see issue #211).
<h4> SAS Macros </h4>
@li dc_assignlib.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_filtercheck.sas
<h4> Related Macros </h4>
@li mpe_loader.sas
**/
/* REPLACE loadtype is incompatible with row level EDIT security (#211) */
proc sql noprint;
create table work.badloadtypes as
select distinct cats(upcase(a.rls_libref),'.',upcase(a.rls_table)) as libds
from work.staging_ds(where=(rls_active=1
and upcase(rls_scope) in ('EDIT','ALL')
and upcase(_____DELETE__THIS__RECORD_____) ne 'YES')) a
inner join &dc_libref..mpe_tables(where=(&dc_dttmtfmt. lt tx_to)) b
on upcase(a.rls_libref)=upcase(b.libref)
and upcase(a.rls_table)=upcase(b.dsn)
where upcase(b.loadtype)='REPLACE';
%let badloadtypes=0;
%let badloadtype=;
data _null_;
set work.badloadtypes;
call symputx('badloadtypes',_n_);
call symputx('badloadtype',libds);
run;
%mp_abort(iftrue=(&badloadtypes ne 0)
,mac=mpe_row_level_security_postedit
,msg=%str(Row level EDIT security cannot be applied to table(s) with a%trim(
) REPLACE loadtype, eg: &badloadtype. Remove the security rule(s) or%trim(
) change the loadtype.)
)
/* group and subgroup are not used for validation */
proc sql;
create table work.batches as
select distinct upcase(rls_libref) as rls_libref,
upcase(rls_table) as rls_table,
rls_group_logic as group_logic,
rls_subgroup_logic as subgroup_logic,
rls_subgroup_id as subgroup_id,
rls_variable_nm as variable_nm,
rls_operator_nm as operator_nm,
rls_raw_value as raw_value
from work.staging_ds
where rls_active=1
and upcase(_____DELETE__THIS__RECORD_____) ne 'YES'
order by rls_libref, rls_table;
%let cnt=0;
data _null_;
set work.batches;
by rls_libref rls_table;
putlog (_all_)(=);
if last.rls_table then do;
x+1;
call symputx(cats('libds',x),cats(rls_libref,'.',rls_table));
call symputx('cnt',x);
end;
run;
/* derived variables exist only in the view the EDIT screen reads - a rule on
one cannot be validated against the physical table, so reject it here rather
than let mp_filtercheck fail with a variable-not-found error. Applies to
every scope: the VIEW scope could evaluate the column, but this hook checks
rules against the table they are submitted for. */
proc sql;
create table work.rls_derived as
select b.rls_libref, b.rls_table, b.variable_nm
from work.batches b
inner join &DC_LIBREF..mpe_derived d
on upcase(d.libref)=b.rls_libref
and upcase(d.dsn)=b.rls_table
and upcase(d.var_nm)=upcase(b.variable_nm)
where &dc_dttmtfmt lt d.tx_to
and d.active=1;
quit;
%let rls_msg=;
data _null_;
set work.rls_derived(obs=1);
call symputx('rls_msg'
,cats('MPE_ROW_LEVEL_SECURITY: ',variable_nm
,' is a derived variable in ',rls_libref,'.',rls_table
,' - derived variables cannot be used in a row level security rule'));
run;
%mp_abort(iftrue= (0 < %mf_nobs(work.rls_derived))
,mac=mpe_row_level_security_postedit
,msg=%superq(rls_msg)
)
%macro quickloop();
%do i=1 %to &cnt;
data work.inds&i;
set work.batches;
if cats(rls_libref,'.',rls_table)="&&libds&i";
keep group_logic subgroup_logic subgroup_id variable_nm operator_nm
raw_value;
run;
%dc_assignlib(READ,%scan(&&libds&i,1,.))
%mp_filtercheck(work.inds&i
,targetds=&&libds&i
,outds=work.badrecords
,abort=YES
)
%end;
%mend quickloop;
%quickloop()