avoid nested subselect for insert permission check

This commit is contained in:
Michael Hoennig 2024-03-25 11:46:39 +01:00
parent 66af0def5b
commit 9fdeb047ee
8 changed files with 51 additions and 57 deletions

View File

@ -88,7 +88,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql(""" fetchedBySql("""
SELECT partnerRel.* SELECT ${columns}
FROM hs_office_relation AS partnerRel FROM hs_office_relation AS partnerRel
JOIN hs_office_partner AS partner JOIN hs_office_partner AS partner
ON partner.detailsUuid = ${ref}.uuid ON partner.detailsUuid = ${ref}.uuid

View File

@ -114,8 +114,7 @@ public class InsertTriggerGenerator {
} }
} }
} else { } else {
final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias(); if (g.getSuperRoleDef().getEntityAlias().isFetchedByDirectForeignKey()) {
if (superRoleEntityAlias.fetchSql().part == RbacView.SQL.Part.AUTO_FETCH) {
generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g);
} else { } else {
generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g);
@ -164,24 +163,23 @@ public class InsertTriggerGenerator {
An indirect role is a role FIXME. An indirect role is a role FIXME.
*/ */
create or replace function ${rawSubTable}_insert_permission_missing_tf() create or replace function ${rawSubTable}_insert_permission_check_tf()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
declare
superRoleObjectUuid uuid;
begin begin
if ( not hasInsertPermission(
( SELECT ${varName}.uuid FROM
""", """,
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); plPgSql.indented(2, () -> {
plPgSql.indented(3, () -> {
plPgSql.writeLn( plPgSql.writeLn(
"(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", "superRoleObjectUuid := (" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ");",
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), with("columns", g.getSuperRoleDef().getEntityAlias().aliasName() + ".uuid"),
with("ref", NEW.name())); with("ref", NEW.name()));
}); });
plPgSql.writeLn(""" plPgSql.writeLn("""
if ( not hasInsertPermission(superRoleObjectUuid, 'INSERT', '${rawSubTable}') ) then
), 'INSERT', '${rawSubTable}') ) then
raise exception raise exception
'[403] insert into ${rawSubTable} not allowed for current subjects % (%)', '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids(); currentSubjects(), currentSubjectsUuids();
@ -192,7 +190,7 @@ public class InsertTriggerGenerator {
create trigger ${rawSubTable}_insert_permission_check_tg create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable} before insert on ${rawSubTable}
for each row for each row
execute procedure ${rawSubTable}_insert_permission_missing_tf(); execute procedure ${rawSubTable}_insert_permission_check_tf();
""", """,
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));

View File

@ -34,6 +34,7 @@ import static java.util.Arrays.stream;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static org.apache.commons.lang3.StringUtils.uncapitalize; import static org.apache.commons.lang3.StringUtils.uncapitalize;
@ -839,8 +840,8 @@ public class RbacView {
}; };
} }
public boolean hasFetchSql() { boolean isFetchedByDirectForeignKey() {
return fetchSql != null; return fetchSql != null && fetchSql.part == AUTO_FETCH;
} }
private String withoutEntitySuffix(final String simpleEntityName) { private String withoutEntitySuffix(final String simpleEntityName) {
@ -909,14 +910,25 @@ public class RbacView {
/** /**
* DSL method to specify an SQL SELECT expression which fetches the related entity, * DSL method to specify an SQL SELECT expression which fetches the related entity,
* using the reference `${ref}` of the root entity. * using the reference `${ref}` of the root entity and `${columns}` for the projection.
* `${ref}` is going to be replaced by either `NEW` or `OLD` of the trigger function. *
* `into ...` will be added with a variable name prefixed with either `new` or `old`. * <p>The query <strong>must define</strong> the entity alias name of the fetched table
* as its alias for, so it can be used in the generated projection (the columns between
* `SELECT` and `FROM`.</p>
*
* <p>`${ref}` is going to be replaced by either `NEW` or `OLD` of the trigger function.
* `into ...` will be added with a variable name prefixed with either `new` or `old`.</p>
*
* <p>`${columns}` is going to be replaced by the columns which are needed for the query,
* e.g. `*` or `uuid`.</p>
* *
* @param sql an SQL SELECT expression (not ending with ';) * @param sql an SQL SELECT expression (not ending with ';)
* @return the wrapped SQL expression * @return the wrapped SQL expression
*/ */
public static SQL fetchedBySql(final String sql) { public static SQL fetchedBySql(final String sql) {
if ( !sql.startsWith("SELECT ${columns}") ) {
throw new IllegalArgumentException("SQL SELECT expression must start with 'SELECT ${columns}', but is: " + sql);
}
validateExpression(sql); validateExpression(sql);
return new SQL(sql, Part.SQL_QUERY); return new SQL(sql, Part.SQL_QUERY);
} }
@ -929,7 +941,7 @@ public class RbacView {
* @return the wrapped SQL definition object * @return the wrapped SQL definition object
*/ */
public static SQL directlyFetchedByDependsOnColumn() { public static SQL directlyFetchedByDependsOnColumn() {
return new SQL(null, Part.AUTO_FETCH); return new SQL(null, AUTO_FETCH);
} }
/** /**

View File

@ -189,30 +189,22 @@ execute procedure test_package_test_customer_insert_tf();
/** /**
Checks if the user or assumed roles are allowed to insert a row to test_package, Checks if the user or assumed roles are allowed to insert a row to test_package,
where the check is performed by an indirect role. where the check is performed by a direct role.
An indirect role is a role FIXME. A direct role is a role depending on a foreign key directly available in the NEW row.
*/ */
create or replace function test_package_insert_permission_missing_tf() create or replace function test_package_insert_permission_missing_tf()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
begin begin
if ( not hasInsertPermission( raise exception '[403] insert into test_package not allowed for current subjects % (%)',
( SELECT customer.uuid FROM
(SELECT * FROM test_customer WHERE uuid = NEW.customerUuid) AS customer
), 'INSERT', 'test_package') ) then
raise exception
'[403] insert into test_package not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids(); currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$; end; $$;
create trigger test_package_insert_permission_check_tg create trigger test_package_insert_permission_check_tg
before insert on test_package before insert on test_package
for each row for each row
when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') )
execute procedure test_package_insert_permission_missing_tf(); execute procedure test_package_insert_permission_missing_tf();
--// --//

View File

@ -188,30 +188,22 @@ execute procedure test_domain_test_package_insert_tf();
/** /**
Checks if the user or assumed roles are allowed to insert a row to test_domain, Checks if the user or assumed roles are allowed to insert a row to test_domain,
where the check is performed by an indirect role. where the check is performed by a direct role.
An indirect role is a role FIXME. A direct role is a role depending on a foreign key directly available in the NEW row.
*/ */
create or replace function test_domain_insert_permission_missing_tf() create or replace function test_domain_insert_permission_missing_tf()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
begin begin
if ( not hasInsertPermission( raise exception '[403] insert into test_domain not allowed for current subjects % (%)',
( SELECT package.uuid FROM
(SELECT * FROM test_package WHERE uuid = NEW.packageUuid) AS package
), 'INSERT', 'test_domain') ) then
raise exception
'[403] insert into test_domain not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids(); currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$; end; $$;
create trigger test_domain_insert_permission_check_tg create trigger test_domain_insert_permission_check_tg
before insert on test_domain before insert on test_domain
for each row for each row
when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') )
execute procedure test_domain_insert_permission_missing_tf(); execute procedure test_domain_insert_permission_missing_tf();
--// --//

View File

@ -54,8 +54,8 @@ begin
hsOfficeRelationAdmin(NEW), hsOfficeRelationAdmin(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficePersonAdmin(newAnchorPerson), hsOfficeRelationOwner(NEW),
hsOfficeRelationOwner(NEW)] hsOfficePersonAdmin(newAnchorPerson)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
@ -69,13 +69,13 @@ begin
hsOfficeRelationTenant(NEW), hsOfficeRelationTenant(NEW),
permissions => array['SELECT'], permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficeRelationAgent(NEW),
hsOfficeContactAdmin(newContact), hsOfficeContactAdmin(newContact),
hsOfficePersonAdmin(newHolderPerson)], hsOfficePersonAdmin(newHolderPerson),
hsOfficeRelationAgent(NEW)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsOfficePersonReferrer(newAnchorPerson), hsOfficePersonReferrer(newHolderPerson),
hsOfficeContactReferrer(newContact), hsOfficeContactReferrer(newContact),
hsOfficePersonReferrer(newHolderPerson)] hsOfficePersonReferrer(newAnchorPerson)]
); );
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);

View File

@ -35,7 +35,7 @@ declare
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
SELECT partnerRel.* SELECT ${columns}
FROM hs_office_relation AS partnerRel FROM hs_office_relation AS partnerRel
JOIN hs_office_partner AS partner JOIN hs_office_partner AS partner
ON partner.detailsUuid = NEW.uuid ON partner.detailsUuid = NEW.uuid

View File

@ -57,17 +57,17 @@ begin
hsOfficeSepaMandateAgent(NEW), hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsOfficeBankAccountReferrer(newBankAccount), hsOfficeRelationAgent(newDebitorRel),
hsOfficeRelationAgent(newDebitorRel)] hsOfficeBankAccountReferrer(newBankAccount)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateReferrer(NEW), hsOfficeSepaMandateReferrer(NEW),
permissions => array['SELECT'], permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficeSepaMandateAgent(NEW),
hsOfficeRelationAgent(newDebitorRel), hsOfficeRelationAgent(newDebitorRel),
hsOfficeBankAccountAdmin(newBankAccount), hsOfficeBankAccountAdmin(newBankAccount)],
hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)]
); );