improved RBAC generators #26

Merged
hsh-michaelhoennig merged 17 commits from improved-rbac-generator into master 2024-03-26 11:25:18 +01:00
19 changed files with 405 additions and 153 deletions
Showing only changes of commit 0decfe1132 - Show all commits

View File

@ -131,7 +131,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
"vatBusiness", "vatBusiness",
"vatReverseCharge", "vatReverseCharge",
"defaultPrefix" /* TODO: do we want that updatable? */) "defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN) .createPermission(INSERT).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
fetchedBySql(""" fetchedBySql("""

View File

@ -84,7 +84,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
"birthName", "birthName",
"birthday", "birthday",
"dateOfDeath") "dateOfDeath")
.createPermission(custom("new-partner-details")).grantedTo("global", ADMIN) .createPermission(INSERT).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql(""" fetchedBySql("""

View File

@ -90,7 +90,7 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
"partnerRelUuid", "partnerRelUuid",
"personUuid", "personUuid",
"contactUuid") "contactUuid")
.createPermission(custom("new-partner")).grantedTo("global", ADMIN) .createPermission(INSERT).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"), fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"),

View File

@ -4,6 +4,7 @@ import java.util.Optional;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.stream.Stream; import java.util.stream.Stream;
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
@ -53,16 +54,16 @@ public class InsertTriggerGenerator {
FOR row IN SELECT * FROM ${rawSuperTableName} FOR row IN SELECT * FROM ${rawSuperTableName}
LOOP LOOP
roleUuid := findRoleId(${rawSuperRoleDescriptor}(row)); roleUuid := findRoleId(${rawSuperRoleDescriptor});
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}');
call grantPermissionToRole(roleUuid, permissionUuid); call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP; END LOOP;
END; END;
$$; $$;
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef)) with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row"))
); );
}); });
} }
@ -79,50 +80,50 @@ public class InsertTriggerGenerator {
strict as $$ strict as $$
begin begin
call grantPermissionToRole( call grantPermissionToRole(
${rawSuperRoleDescriptor}(NEW), createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'),
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}')); ${rawSuperRoleDescriptor});
return NEW; return NEW;
end; $$; end; $$;
create trigger ${rawSubTableName}_${rawSuperTableName}_insert_tg -- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_${rawSubTableName}_${rawSuperTableName}_insert_tg
after insert on ${rawSuperTableName} after insert on ${rawSuperTableName}
for each row for each row
execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf(); execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf();
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef)) with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name()))
); );
}); });
} }
private void generateInsertCheckTrigger(final StringWriter plPgSql) { private void generateInsertCheckTrigger(final StringWriter plPgSql) {
plPgSql.writeLn("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
*/
create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
getOptionalInsertGrant().ifPresentOrElse(g -> { getOptionalInsertGrant().ifPresentOrElse(g -> {
plPgSql.writeLn(""" if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) {
create trigger ${rawSubTable}_insert_permission_check_tg if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) {
before insert on ${rawSubTable} generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g);
for each row } else {
when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g);
execute procedure ${rawSubTable}_insert_permission_missing_tf(); }
""", } else {
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), switch (g.getSuperRoleDef().getRole()) {
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() )); case ADMIN -> {
generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql);
}
case GUEST -> {
// no permission check trigger generated, as anybody can insert rows into this table
}
default -> {
throw new IllegalArgumentException(
"invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole());
}
}
}
}, },
() -> { () -> {
plPgSql.writeLn(""" plPgSql.writeLn("""
-- FIXME: Where is this case necessary?
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
@ -135,6 +136,92 @@ public class InsertTriggerGenerator {
}); });
} }
private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
plPgSql.writeLn("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
*/
create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName()));
}
private void generateInsertPermissionTriggerAllowByIndirectRole(
final StringWriter plPgSql,
final RbacView.RbacGrantDefinition g) {
plPgSql.writeLn("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
*/
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("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
*/
create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
when ( not isGlobalAdmin() )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}
private Stream<RbacView.RbacGrantDefinition> getInsertGrants() { private Stream<RbacView.RbacGrantDefinition> getInsertGrants() {
return rbacDef.getGrantDefs().stream() return rbacDef.getGrantDefs().stream()
.filter(g -> g.grantType() == PERM_TO_ROLE) .filter(g -> g.grantType() == PERM_TO_ROLE)
@ -162,4 +249,12 @@ public class InsertTriggerGenerator {
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
} }
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
final var functionName = toVar(roleDef);
if (roleDef.getEntityAlias().isGlobal()) {
return functionName + "()";
}
return functionName + "(" + ref + ")";
}
} }

View File

@ -31,7 +31,7 @@ public class RbacIdentityViewGenerator {
$idName$); $idName$);
"""; """;
case SQL_QUERY -> """ case SQL_QUERY -> """
call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$ call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$
${identityViewSqlPart} ${identityViewSqlPart}
$idName$); $idName$);
"""; """;

View File

@ -24,9 +24,11 @@ public class RbacRestrictedViewGenerator {
--changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('${rawTableName}', call generateRbacRestrictedView('${rawTableName}',
'${orderBy}', $orderBy$
${orderBy}
$orderBy$,
$updates$ $updates$
${updates} ${updates}
$updates$); $updates$);
--// --//

View File

@ -32,6 +32,7 @@ import java.util.stream.Stream;
import static java.lang.reflect.Modifier.isStatic; import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.stream; 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.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched;
import static org.apache.commons.lang3.StringUtils.uncapitalize; import static org.apache.commons.lang3.StringUtils.uncapitalize;
@ -141,35 +142,42 @@ public class RbacView {
if (rootEntityAliasProxy != null) { if (rootEntityAliasProxy != null) {
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);
} }
rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL);
return this; return this;
} }
public RbacView importSubEntityAlias( public RbacView importSubEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass, final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum) { final SQL fetchSql, final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true); importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true, NOT_NULL);
return this;
}
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, nullable);
return this; return this;
} }
public RbacView importEntityAlias( public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass, final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql) { final Column dependsOnColum, final SQL fetchSql) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL);
return this; return this;
} }
public RbacView importEntityAlias( public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass, final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum) { final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false); importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false, null);
return this; return this;
} }
private EntityAlias importEntityAliasImpl( private EntityAlias importEntityAliasImpl(
final String aliasName, final Class<? extends HasUuid> entityClass, final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) { final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity); final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable);
entityAliases.put(aliasName, entityAlias); entityAliases.put(aliasName, entityAlias);
try { try {
importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity); importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity);
@ -281,15 +289,19 @@ public class RbacView {
return RbacView.this; return RbacView.this;
} }
public RbacView grantPermission(final String entityAliasName, final Permission perm) { public RbacView grantPermission(final Permission perm) {
final var entityAlias = findEntityAlias(entityAliasName); final var forTable = rootEntityAlias.getRawTableName();
final var forTable = entityAlias.getRawTableName(); findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate();
findOrCreateGrantDef(findRbacPerm(entityAlias, perm, forTable), superRoleDef).toCreate();
return RbacView.this; return RbacView.this;
} }
} }
public enum Nullable {
NOT_NULL, // DEFAULT
NULLABLE
}
@Getter @Getter
@EqualsAndHashCode @EqualsAndHashCode
public class RbacGrantDefinition { public class RbacGrantDefinition {
@ -560,14 +572,14 @@ public class RbacView {
.orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition)); .orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition));
} }
record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity) { record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
public EntityAlias(final String aliasName) { public EntityAlias(final String aliasName) {
this(aliasName, null, null, null, false); this(aliasName, null, null, null, false, null);
} }
public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) { public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) {
this(aliasName, entityClass, null, null, false); this(aliasName, entityClass, null, null, false, null);
} }
boolean isGlobal() { boolean isGlobal() {
@ -626,39 +638,35 @@ public class RbacView {
return tableName.substring(0, tableName.length() - "_rv".length()); return tableName.substring(0, tableName.length() - "_rv".length());
} }
public record Role(String roleName) { public enum Role {
public static final Role OWNER = new Role("owner"); OWNER,
public static final Role ADMIN = new Role("admin"); ADMIN,
public static final Role AGENT = new Role("agent"); AGENT,
public static final Role TENANT = new Role("tenant"); TENANT,
public static final Role REFERRER = new Role("referrer"); REFERRER,
GUEST;
@Override @Override
public String toString() { public String toString() {
return ":" + roleName; return ":" + roleName();
} }
@Override String roleName() {
public boolean equals(final Object obj) { return name().toLowerCase();
return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName));
} }
} }
public record Permission(String permission) { public enum Permission {
INSERT,
public static final Permission INSERT = new Permission("INSERT"); DELETE,
public static final Permission DELETE = new Permission("DELETE"); UPDATE,
public static final Permission UPDATE = new Permission("UPDATE"); SELECT;
public static final Permission SELECT = new Permission("SELECT");
public static Permission custom(final String permission) {
return new Permission(permission);
}
@Override @Override
public String toString() { public String toString() {
return ":" + permission; return ":" + name();
} }
} }

View File

@ -37,8 +37,11 @@ public class RbacViewPostgresGenerator {
@Override @Override
public String toString() { public String toString() {
return plPgSql.toString(); return plPgSql.toString()
} .replace("\n\n\n", "\n\n")
.replace("-- ====", "\n-- ====")
.replace("\n\n--//", "\n--//");
}
@SneakyThrows @SneakyThrows
public void generateToChangeLog(final Path outputPath) { public void generateToChangeLog(final Path outputPath) {

View File

@ -82,6 +82,7 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn("begin"); plPgSql.writeLn("begin");
plPgSql.indented(() -> { plPgSql.indented(() -> {
plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);");
plPgSql.writeLn();
generateCreateRolesAndGrantsAfterInsert(plPgSql); generateCreateRolesAndGrantsAfterInsert(plPgSql);
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);");
@ -90,6 +91,37 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn(); plPgSql.writeLn();
} }
private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) {
final var updateConditions = updatableEntityAliases()
.map(RbacView.EntityAlias::dependsOnColumName)
.distinct()
.map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName)
.collect(joining( "\n or "));
plPgSql.writeLn("""
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesFor${simpleEntityName}(
OLD ${rawTableName},
NEW ${rawTableName}
)
language plpgsql as $$
begin
if ${updateConditions} then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemFor${simpleEntityName}(NEW);
end if;
end; $$;
""",
with("simpleEntityName", simpleEntityName),
with("rawTableName", rawTableName),
with("updateConditions", updateConditions));
}
private void generateUpdateTriggerFunction(final StringWriter plPgSql) { private void generateUpdateTriggerFunction(final StringWriter plPgSql) {
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*
@ -109,7 +141,7 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.chopEmptyLines(); plPgSql.chopEmptyLines();
plPgSql.indented(() -> { plPgSql.indented(() -> {
updatableEntityAliases() referencedEntityAliases()
.forEach((ea) -> { .forEach((ea) -> {
plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";"); plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";");
plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"); plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";");
@ -120,6 +152,7 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn("begin"); plPgSql.writeLn("begin");
plPgSql.indented(() -> { plPgSql.indented(() -> {
plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);");
plPgSql.writeLn();
generateUpdateRolesAndGrantsAfterUpdate(plPgSql); generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);");
@ -132,11 +165,18 @@ class RolesGrantsAndPermissionsGenerator {
return updatableEntityAliases().anyMatch(e -> true); return updatableEntityAliases().anyMatch(e -> true);
} }
private boolean hasAnyUpdatableAndNullableEntityAliases() {
return updatableEntityAliases()
.filter(ea -> ea.nullable() == RbacView.Nullable.NULLABLE)
.anyMatch(e -> true);
}
private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) {
referencedEntityAliases() referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn( .forEach((ea) -> {
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", generateFetchedVars(plPgSql, ea, NEW);
with("ref", NEW.name()))); plPgSql.writeLn();
});
createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN); createRolesWithGrantsSql(plPgSql, ADMIN);
@ -165,14 +205,11 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
updatableEntityAliases() referencedEntityAliases()
.forEach((ea) -> { .forEach((ea) -> {
plPgSql.writeLn( generateFetchedVars(plPgSql, ea, OLD);
ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";", generateFetchedVars(plPgSql, ea, NEW);
with("ref", OLD.name())); plPgSql.writeLn();
plPgSql.writeLn(
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
with("ref", NEW.name()));
}); });
updatableEntityAliases() updatableEntityAliases()
@ -190,6 +227,23 @@ class RolesGrantsAndPermissionsGenerator {
}); });
} }
private void generateFetchedVars(
final StringWriter plPgSql,
final RbacView.EntityAlias ea,
final PostgresTriggerReference old) {
plPgSql.writeLn(
ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";",
with("ref", old.name()));
if (ea.nullable() == RbacView.Nullable.NOT_NULL) {
plPgSql.writeLn(
"assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s', ${REF}.${dependsOnColumn});",
with("entityRefVar", entityRefVar(old, ea)),
with("dependsOnColumn", ea.dependsOnColumName()),
with("ref", old.name()));
plPgSql.writeLn();
}
}
private boolean isUpdatable(final RbacView.Column c) { private boolean isUpdatable(final RbacView.Column c) {
return rbacDef.getUpdatableColumns().contains(c); return rbacDef.getUpdatableColumns().contains(c);
} }
@ -222,7 +276,7 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef())) .replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef()))
.replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef()));
case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});" case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});"
.replace("${permRef}", findPerm(OLD, grantDef.getPermDef())) .replace("${permRef}", getPerm(OLD, grantDef.getPermDef()))
.replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef()));
}; };
} }
@ -246,6 +300,10 @@ class RolesGrantsAndPermissionsGenerator {
return permRef("findPermissionId", ref, permDef); return permRef("findPermissionId", ref, permDef);
} }
private String getPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
return permRef("getPermissionId", ref, permDef);
}
private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
return permRef("createPermission", ref, permDef); return permRef("createPermission", ref, permDef);
} }
@ -256,7 +314,7 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias) .replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias)
? ref.name() ? ref.name()
: refVarName(ref, permDef.entityAlias)) : refVarName(ref, permDef.entityAlias))
.replace("${perm}", permDef.permission.permission()); .replace("${perm}", permDef.permission.name());
} }
private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) { private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) {
@ -301,12 +359,12 @@ class RolesGrantsAndPermissionsGenerator {
generatePermissionsForRole(plPgSql, role); generatePermissionsForRole(plPgSql, role);
generateUserGrantsForRole(plPgSql, role);
generateIncomingSuperRolesForRole(plPgSql, role); generateIncomingSuperRolesForRole(plPgSql, role);
generateOutgoingSubRolesForRole(plPgSql, role); generateOutgoingSubRolesForRole(plPgSql, role);
generateUserGrantsForRole(plPgSql, role);
plPgSql.chopTail(",\n"); plPgSql.chopTail(",\n");
plPgSql.writeLn(); plPgSql.writeLn();
}); });
@ -333,7 +391,7 @@ class RolesGrantsAndPermissionsGenerator {
final var arrayElements = permissionGrantsForRole.stream() final var arrayElements = permissionGrantsForRole.stream()
.map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacView.RbacGrantDefinition::getPermDef)
.map(RbacPermissionDefinition::getPermission) .map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::permission) .map(RbacView.Permission::name)
.map(p -> "'" + p + "'") .map(p -> "'" + p + "'")
.sorted() .sorted()
.toList(); .toList();
@ -444,7 +502,11 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateTrigger(final StringWriter plPgSql) { private void generateUpdateTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "update"); generateHeader(plPgSql, "update");
generateUpdateTriggerFunction(plPgSql); if ( hasAnyUpdatableAndNullableEntityAliases() ) {
generateSimplifiedUpdateTriggerFunction(plPgSql);
} else {
generateUpdateTriggerFunction(plPgSql);
}
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*

View File

@ -38,12 +38,26 @@ public class StringWriter {
--indentLevel; --indentLevel;
} }
void indent(int levels) {
indentLevel += levels;
}
void unindent(int levels) {
indentLevel -= levels;
}
void indented(final Runnable indented) { void indented(final Runnable indented) {
indent(); indent();
indented.run(); indented.run();
unindent(); unindent();
} }
void indented(int levels, final Runnable indented) {
indent(levels);
indented.run();
unindent(levels);
}
boolean chopTail(final String tail) { boolean chopTail(final String tail) {
if (string.toString().endsWith(tail)) { if (string.toString().endsWith(tail)) {
string.setLength(string.length() - tail.length()); string.setLength(string.length() - tail.length());
@ -103,8 +117,8 @@ public class StringWriter {
text = matcher.replaceAll(varDef.value()); text = matcher.replaceAll(varDef.value());
}); });
return text; return text;
} catch (Exception exc) { } catch (final RuntimeException exc) {
throw exc; throw exc; // FIXME: just for debugging, remove try/catch before merging to master
} }
} }
} }

View File

@ -21,6 +21,8 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.
@Service @Service
public class RbacGrantsDiagramService { public class RbacGrantsDiagramService {
private static final int GRANT_LIMIT = 500;
public static void writeToFile(final String title, final String graph, final String fileName) { public static void writeToFile(final String title, final String graph, final String fileName) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
@ -42,7 +44,11 @@ public class RbacGrantsDiagramService {
PERMISSIONS, PERMISSIONS,
NOT_ASSUMED, NOT_ASSUMED,
TEST_ENTITIES, TEST_ENTITIES,
NON_TEST_ENTITIES NON_TEST_ENTITIES;
public static final EnumSet<Include> ALL = EnumSet.allOf(Include.class);
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS);
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS);
} }
@Autowired @Autowired
@ -55,7 +61,7 @@ public class RbacGrantsDiagramService {
private EntityManager em; private EntityManager em;
public String allGrantsToCurrentUser(final EnumSet<Include> includes) { public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new HashSet<RawRbacGrantEntity>(); final var graph = new LimitedHashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.currentSubjectsUuids() ) { for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
traverseGrantsTo(graph, subjectUuid, includes); traverseGrantsTo(graph, subjectUuid, includes);
} }
@ -65,6 +71,10 @@ public class RbacGrantsDiagramService {
private void traverseGrantsTo(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> includes) { private void traverseGrantsTo(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> includes) {
final var grants = rawGrantRepo.findByAscendingUuid(refUuid); final var grants = rawGrantRepo.findByAscendingUuid(refUuid);
grants.forEach(g -> { grants.forEach(g -> {
if ( g.getDescendantIdName() == null ) {
// FIXME: what's that?
return;
}
if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) {
return; return;
} }
@ -88,7 +98,7 @@ public class RbacGrantsDiagramService {
.setParameter("targetObject", targetObject) .setParameter("targetObject", targetObject)
.setParameter("op", op) .setParameter("op", op)
.getSingleResult(); .getSingleResult();
final var graph = new HashSet<RawRbacGrantEntity>(); final var graph = new LimitedHashSet<RawRbacGrantEntity>();
traverseGrantsFrom(graph, refUuid, includes); traverseGrantsFrom(graph, refUuid, includes);
return toMermaidFlowchart(graph, includes); return toMermaidFlowchart(graph, includes);
} }
@ -116,7 +126,7 @@ public class RbacGrantsDiagramService {
) )
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName)) .collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
.entrySet().stream() .entrySet().stream()
.map(entity -> "subgraph " + quoted(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " .map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
+ entity.getValue().stream() + entity.getValue().stream()
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n ")) .map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
.sorted() .sorted()
@ -127,14 +137,15 @@ public class RbacGrantsDiagramService {
: ""; : "";
final var grants = graph.stream() final var grants = graph.stream()
.map(g -> quoted(g.getAscendantIdName()) .map(g -> cleanId(g.getAscendantIdName())
+ " -->" + (g.isAssumed() ? " " : "|XX| ") + " -->" + (g.isAssumed() ? " " : "|XX| ")
+ quoted(g.getDescendantIdName())) + cleanId(g.getDescendantIdName()))
.sorted() .sorted()
.collect(joining("\n")); .collect(joining("\n"));
final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n";
return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "")
+ (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "")
+ "flowchart TB\n\n" + "flowchart TB\n\n"
+ entities + entities
+ grants; + grants;
@ -151,7 +162,7 @@ public class RbacGrantsDiagramService {
// } // }
// return "[" + table + "\n" + entity + "]"; // return "[" + table + "\n" + entity + "]";
// } // }
return "[" + entityId + "]"; return "[" + cleanId(entityId) + "]";
} }
private static String renderEntityIdName(final Node node) { private static String renderEntityIdName(final Node node) {
@ -170,7 +181,7 @@ public class RbacGrantsDiagramService {
} }
private String renderNode(final String idName, final UUID uuid) { private String renderNode(final String idName, final UUID uuid) {
return quoted(idName) + renderNodeContent(idName, uuid); return cleanId(idName) + renderNodeContent(idName, uuid);
} }
private String renderNodeContent(final String idName, final UUID uuid) { private String renderNodeContent(final String idName, final UUID uuid) {
@ -196,9 +207,24 @@ public class RbacGrantsDiagramService {
} }
@NotNull @NotNull
private static String quoted(final String idName) { private static String cleanId(final String idName) {
return idName.replace(" ", ":").replaceAll("@.*", ""); return idName.replace(" ", ":").replaceAll("@.*", "")
.replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "");
} }
class LimitedHashSet<T> extends HashSet<T> {
@Override
public boolean add(final T t) {
if (size() < GRANT_LIMIT ) {
return super.add(t);
} else {
return false;
}
}
}
} }
record Node(String idName, UUID uuid) { record Node(String idName, UUID uuid) {

View File

@ -53,7 +53,7 @@ public class TestDomainEntity implements HasUuid {
SELECT * FROM test_package p SELECT * FROM test_package p
WHERE p.uuid= ${ref}.packageUuid WHERE p.uuid= ${ref}.packageUuid
""")) """))
.toRole("package", ADMIN).grantPermission("domain", INSERT) .toRole("package", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.incomingSuperRole("package", ADMIN); with.incomingSuperRole("package", ADMIN);

View File

@ -54,7 +54,7 @@ public class TestPackageEntity implements HasUuid {
SELECT * FROM test_customer c SELECT * FROM test_customer c
WHERE c.uuid= ${ref}.customerUuid WHERE c.uuid= ${ref}.customerUuid
""")) """))
.toRole("customer", ADMIN).grantPermission("package", INSERT) .toRole("customer", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.incomingSuperRole("customer", ADMIN); with.incomingSuperRole("customer", ADMIN);

View File

@ -1,6 +1,6 @@
### rbac customer ### rbac customer
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.571772062. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:18.451453701.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.584886824. -- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:18.467932975.
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--// --changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
@ -36,8 +37,8 @@ begin
perform createRoleWithGrants( perform createRoleWithGrants(
testCustomerOwner(NEW), testCustomerOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
userUuids => array[currentUserUuid()], incomingSuperRoles => array[globalAdmin(unassumed())],
incomingSuperRoles => array[globalAdmin(unassumed())] userUuids => array[currentUserUuid()]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
@ -72,9 +73,9 @@ create trigger insertTriggerForTestCustomer_tg
after insert on test_customer after insert on test_customer
for each row for each row
execute procedure insertTriggerForTestCustomer_tf(); execute procedure insertTriggerForTestCustomer_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-INSERT:1 endDelimiter:--// --changeset test-customer-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -97,8 +98,8 @@ create trigger test_customer_insert_permission_check_tg
-- only global admins are allowed to insert any rows. -- only global admins are allowed to insert any rows.
when ( not isGlobalAdmin() ) when ( not isGlobalAdmin() )
execute procedure test_customer_insert_permission_missing_tf(); execute procedure test_customer_insert_permission_missing_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -106,18 +107,19 @@ create trigger test_customer_insert_permission_check_tg
call generateRbacIdentityViewFromProjection('test_customer', $idName$ call generateRbacIdentityViewFromProjection('test_customer', $idName$
prefix prefix
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_customer', call generateRbacRestrictedView('test_customer',
'reference', $orderBy$
reference
$orderBy$,
$updates$ $updates$
reference = new.reference, reference = new.reference,
prefix = new.prefix, prefix = new.prefix,
adminUserName = new.adminUserName adminUserName = new.adminUserName
$updates$); $updates$);
--// --//

View File

@ -1,6 +1,6 @@
### rbac package ### rbac package
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.624847792. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:51.758424330.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.625353859. -- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:51.767062425.
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-OBJECT:1 endDelimiter:--// --changeset test-package-rbac-OBJECT:1 endDelimiter:--//
@ -33,9 +34,12 @@ declare
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM test_customer c SELECT * FROM test_customer c
WHERE c.uuid= NEW.customerUuid WHERE c.uuid= NEW.customerUuid
into newCustomer; INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
perform createRoleWithGrants( perform createRoleWithGrants(
testPackageOwner(NEW), testPackageOwner(NEW),
@ -75,9 +79,9 @@ create trigger insertTriggerForTestPackage_tg
after insert on test_package after insert on test_package
for each row for each row
execute procedure insertTriggerForTestPackage_tf(); execute procedure insertTriggerForTestPackage_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-update-trigger:1 endDelimiter:--// --changeset test-package-rbac-update-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -101,14 +105,18 @@ begin
SELECT * FROM test_customer c SELECT * FROM test_customer c
WHERE c.uuid= OLD.customerUuid WHERE c.uuid= OLD.customerUuid
into oldCustomer; INTO oldCustomer;
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
SELECT * FROM test_customer c SELECT * FROM test_customer c
WHERE c.uuid= NEW.customerUuid WHERE c.uuid= NEW.customerUuid
into newCustomer; INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
if NEW.customerUuid <> OLD.customerUuid then if NEW.customerUuid <> OLD.customerUuid then
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer));
call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer)); call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer));
call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer)); call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer));
@ -138,9 +146,9 @@ create trigger updateTriggerForTestPackage_tg
after update on test_package after update on test_package
for each row for each row
execute procedure updateTriggerForTestPackage_tf(); execute procedure updateTriggerForTestPackage_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-INSERT:1 endDelimiter:--// --changeset test-package-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -160,7 +168,7 @@ do language plpgsql $$
LOOP LOOP
roleUuid := findRoleId(testCustomerAdmin(row)); roleUuid := findRoleId(testCustomerAdmin(row));
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package'); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package');
call grantPermissionToRole(roleUuid, permissionUuid); call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP; END LOOP;
END; END;
$$; $$;
@ -174,12 +182,13 @@ create or replace function test_package_test_customer_insert_tf()
strict as $$ strict as $$
begin begin
call grantPermissionToRole( call grantPermissionToRole(
testCustomerAdmin(NEW), createPermission(NEW.uuid, 'INSERT', 'test_package'),
createPermission(NEW.uuid, 'INSERT', 'test_package')); testCustomerAdmin(NEW));
return NEW; return NEW;
end; $$; end; $$;
create trigger test_package_test_customer_insert_tg -- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_test_package_test_customer_insert_tg
after insert on test_customer after insert on test_customer
for each row for each row
execute procedure test_package_test_customer_insert_tf(); execute procedure test_package_test_customer_insert_tf();
@ -191,17 +200,27 @@ create or replace function test_package_insert_permission_missing_tf()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
begin begin
raise exception '[403] insert into test_package not allowed for current subjects % (%)', if ( not hasInsertPermission(
currentSubjects(), currentSubjectsUuids(); ( SELECT customer.uuid FROM
(SELECT * FROM test_customer c
WHERE c.uuid= NEW.customerUuid
) AS customer
), 'INSERT', 'test_package') ) then
raise exception
'[403] insert into test_package not allowed for current subjects % (%)',
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();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -209,18 +228,19 @@ create trigger test_package_insert_permission_check_tg
call generateRbacIdentityViewFromProjection('test_package', $idName$ call generateRbacIdentityViewFromProjection('test_package', $idName$
name name
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_package', call generateRbacRestrictedView('test_package',
'name', $orderBy$
name
$orderBy$,
$updates$ $updates$
version = new.version, version = new.version,
customerUuid = new.customerUuid, customerUuid = new.customerUuid,
description = new.description description = new.description
$updates$); $updates$);
--// --//

View File

@ -1,6 +1,6 @@
### rbac domain ### rbac domain
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.644658132. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:31.860490657.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.645391647. -- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:31.873124905.
-- ============================================================================ -- ============================================================================
--changeset test-domain-rbac-OBJECT:1 endDelimiter:--// --changeset test-domain-rbac-OBJECT:1 endDelimiter:--//
@ -33,9 +34,12 @@ declare
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM test_package p SELECT * FROM test_package p
WHERE p.uuid= NEW.packageUuid WHERE p.uuid= NEW.packageUuid
into newPackage; INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
perform createRoleWithGrants( perform createRoleWithGrants(
testDomainOwner(NEW), testDomainOwner(NEW),
@ -71,9 +75,9 @@ create trigger insertTriggerForTestDomain_tg
after insert on test_domain after insert on test_domain
for each row for each row
execute procedure insertTriggerForTestDomain_tf(); execute procedure insertTriggerForTestDomain_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-domain-rbac-update-trigger:1 endDelimiter:--// --changeset test-domain-rbac-update-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -97,14 +101,18 @@ begin
SELECT * FROM test_package p SELECT * FROM test_package p
WHERE p.uuid= OLD.packageUuid WHERE p.uuid= OLD.packageUuid
into oldPackage; INTO oldPackage;
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
SELECT * FROM test_package p SELECT * FROM test_package p
WHERE p.uuid= NEW.packageUuid WHERE p.uuid= NEW.packageUuid
into newPackage; INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
if NEW.packageUuid <> OLD.packageUuid then if NEW.packageUuid <> OLD.packageUuid then
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage));
call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage));
@ -137,9 +145,9 @@ create trigger updateTriggerForTestDomain_tg
after update on test_domain after update on test_domain
for each row for each row
execute procedure updateTriggerForTestDomain_tf(); execute procedure updateTriggerForTestDomain_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-domain-rbac-INSERT:1 endDelimiter:--// --changeset test-domain-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -159,7 +167,7 @@ do language plpgsql $$
LOOP LOOP
roleUuid := findRoleId(testPackageAdmin(row)); roleUuid := findRoleId(testPackageAdmin(row));
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain'); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain');
call grantPermissionToRole(roleUuid, permissionUuid); call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP; END LOOP;
END; END;
$$; $$;
@ -173,12 +181,13 @@ create or replace function test_domain_test_package_insert_tf()
strict as $$ strict as $$
begin begin
call grantPermissionToRole( call grantPermissionToRole(
testPackageAdmin(NEW), createPermission(NEW.uuid, 'INSERT', 'test_domain'),
createPermission(NEW.uuid, 'INSERT', 'test_domain')); testPackageAdmin(NEW));
return NEW; return NEW;
end; $$; end; $$;
create trigger test_domain_test_package_insert_tg -- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_test_domain_test_package_insert_tg
after insert on test_package after insert on test_package
for each row for each row
execute procedure test_domain_test_package_insert_tf(); execute procedure test_domain_test_package_insert_tf();
@ -190,17 +199,27 @@ create or replace function test_domain_insert_permission_missing_tf()
returns trigger returns trigger
language plpgsql as $$ language plpgsql as $$
begin begin
raise exception '[403] insert into test_domain not allowed for current subjects % (%)', if ( not hasInsertPermission(
currentSubjects(), currentSubjectsUuids(); ( SELECT package.uuid FROM
(SELECT * FROM test_package p
WHERE p.uuid= NEW.packageUuid
) AS package
), 'INSERT', 'test_domain') ) then
raise exception
'[403] insert into test_domain not allowed for current subjects % (%)',
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();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -208,18 +227,19 @@ create trigger test_domain_insert_permission_check_tg
call generateRbacIdentityViewFromProjection('test_domain', $idName$ call generateRbacIdentityViewFromProjection('test_domain', $idName$
name name
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_domain', call generateRbacRestrictedView('test_domain',
'name', $orderBy$
name
$orderBy$,
$updates$ $updates$
version = new.version, version = new.version,
packageUuid = new.packageUuid, packageUuid = new.packageUuid,
description = new.description description = new.description
$updates$); $updates$);
--// --//