From fd7630aaa80964d25e5278fa0ca24578370d5ad7 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 25 Mar 2024 05:57:58 +0100 Subject: [PATCH] fix indrirect permission by indirect foreign key --- .../rbac/rbacdef/InsertTriggerGenerator.java | 55 ++++++++++++++++++- .../hsadminng/rbac/rbacdef/RbacView.java | 34 ++++++------ src/test/resources/application.yml | 3 +- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index d625c0d7..e79f8acf 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -114,7 +114,16 @@ public class InsertTriggerGenerator { } } } else { - generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); + final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias(); + + // TODO: Maybe this should depend on the indirection degree of the fetchSql? + // Maybe we need a separate fetchedBy method for all the simple, direct cases? + if (superRoleEntityAlias.fetchSql().sql.contains("JOIN ")) { + generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); + + } } }, () -> { @@ -149,6 +158,50 @@ public class InsertTriggerGenerator { with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); } + private void generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey( + final StringWriter plPgSql, + final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + if ( not hasInsertPermission( + ( SELECT ${varName}.uuid FROM + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); + plPgSql.indented(3, () -> { + plPgSql.writeLn( + "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), + with("ref", NEW.name())); + }); + plPgSql.writeLn(""" + + ), 'INSERT', '${rawSubTable}') ) then + raise exception + '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index e6967658..ee1c9fdf 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -1033,6 +1033,23 @@ public class RbacView { } } + private static void generateRbacView(final Class c) { + final Method mainMethod = stream(c.getMethods()).filter( + m -> isStatic(m.getModifiers()) && m.getName().equals("main") + ) + .findFirst() + .orElse(null); + if (mainMethod != null) { + try { + mainMethod.invoke(null, new Object[] { null }); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); + } + } + /** * This main method generates the RbacViews (PostgreSQL+diagram) for all given entity classes. */ @@ -1052,21 +1069,6 @@ public class RbacView { HsOfficeSepaMandateEntity.class, HsOfficeCoopSharesTransactionEntity.class, HsOfficeMembershipEntity.class - ).forEach(c -> { - final Method mainMethod = stream(c.getMethods()).filter( - m -> isStatic(m.getModifiers()) && m.getName().equals("main") - ) - .findFirst() - .orElse(null); - if (mainMethod != null) { - try { - mainMethod.invoke(null, new Object[] { null }); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } else { - System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); - } - }); + ).forEach(RbacView::generateRbacView); } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index a4f570f9..40ae85bb 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -4,8 +4,9 @@ spring: platform: postgres datasource: - url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers + url-tc: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers url-local: jdbc:postgresql://localhost:5432/postgres + url: ${spring.datasource.url-tc} username: postgres password: password