improved RBAC generators #26
@ -131,7 +131,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
|
||||
"vatBusiness",
|
||||
"vatReverseCharge",
|
||||
"defaultPrefix" /* TODO: do we want that updatable? */)
|
||||
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN)
|
||||
.createPermission(INSERT).grantedTo("global", ADMIN)
|
||||
|
||||
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
|
||||
fetchedBySql("""
|
||||
|
@ -84,7 +84,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
|
||||
"birthName",
|
||||
"birthday",
|
||||
"dateOfDeath")
|
||||
.createPermission(custom("new-partner-details")).grantedTo("global", ADMIN)
|
||||
.createPermission(INSERT).grantedTo("global", ADMIN)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
|
||||
fetchedBySql("""
|
||||
|
@ -90,7 +90,7 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
|
||||
"partnerRelUuid",
|
||||
"personUuid",
|
||||
"contactUuid")
|
||||
.createPermission(custom("new-partner")).grantedTo("global", ADMIN)
|
||||
.createPermission(INSERT).grantedTo("global", ADMIN)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
|
||||
fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"),
|
||||
|
@ -4,6 +4,7 @@ import java.util.Optional;
|
||||
import java.util.function.BinaryOperator;
|
||||
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.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||
@ -53,16 +54,16 @@ public class InsertTriggerGenerator {
|
||||
|
||||
FOR row IN SELECT * FROM ${rawSuperTableName}
|
||||
LOOP
|
||||
roleUuid := findRoleId(${rawSuperRoleDescriptor}(row));
|
||||
roleUuid := findRoleId(${rawSuperRoleDescriptor});
|
||||
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}');
|
||||
call grantPermissionToRole(roleUuid, permissionUuid);
|
||||
call grantPermissionToRole(permissionUuid, roleUuid);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
""",
|
||||
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
|
||||
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
|
||||
with("rawSuperRoleDescriptor", toVar(superRoleDef))
|
||||
with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row"))
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -79,24 +80,63 @@ public class InsertTriggerGenerator {
|
||||
strict as $$
|
||||
begin
|
||||
call grantPermissionToRole(
|
||||
${rawSuperRoleDescriptor}(NEW),
|
||||
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'));
|
||||
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'),
|
||||
${rawSuperRoleDescriptor});
|
||||
return NEW;
|
||||
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}
|
||||
for each row
|
||||
execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf();
|
||||
""",
|
||||
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
|
||||
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
|
||||
with("rawSuperRoleDescriptor", toVar(superRoleDef))
|
||||
with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name()))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void generateInsertCheckTrigger(final StringWriter plPgSql) {
|
||||
getOptionalInsertGrant().ifPresentOrElse(g -> {
|
||||
if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) {
|
||||
if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) {
|
||||
generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g);
|
||||
} else {
|
||||
generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g);
|
||||
}
|
||||
} else {
|
||||
switch (g.getSuperRoleDef().getRole()) {
|
||||
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("""
|
||||
-- FIXME: Where is this case necessary?
|
||||
create trigger ${rawSubTable}_insert_permission_check_tg
|
||||
before insert on ${rawSubTable}
|
||||
for each row
|
||||
-- As there is no explicit INSERT grant specified for this table,
|
||||
-- only global admins are allowed to insert any rows.
|
||||
when ( not isGlobalAdmin() )
|
||||
execute procedure ${rawSubTable}_insert_permission_missing_tf();
|
||||
""",
|
||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||
});
|
||||
}
|
||||
|
||||
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}.
|
||||
@ -108,10 +148,7 @@ public class InsertTriggerGenerator {
|
||||
raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
|
||||
currentSubjects(), currentSubjectsUuids();
|
||||
end; $$;
|
||||
""",
|
||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||
getOptionalInsertGrant().ifPresentOrElse(g -> {
|
||||
plPgSql.writeLn("""
|
||||
|
||||
create trigger ${rawSubTable}_insert_permission_check_tg
|
||||
before insert on ${rawSubTable}
|
||||
for each row
|
||||
@ -119,20 +156,70 @@ public class InsertTriggerGenerator {
|
||||
execute procedure ${rawSubTable}_insert_permission_missing_tf();
|
||||
""",
|
||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
|
||||
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() ));
|
||||
},
|
||||
() -> {
|
||||
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
|
||||
-- As there is no explicit INSERT grant specified for this table,
|
||||
-- only global admins are allowed to insert any rows.
|
||||
when ( not isGlobalAdmin() )
|
||||
execute procedure ${rawSubTable}_insert_permission_missing_tf();
|
||||
""",
|
||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||
});
|
||||
}
|
||||
|
||||
private Stream<RbacView.RbacGrantDefinition> getInsertGrants() {
|
||||
@ -162,4 +249,12 @@ public class InsertTriggerGenerator {
|
||||
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 + ")";
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class RbacIdentityViewGenerator {
|
||||
$idName$);
|
||||
""";
|
||||
case SQL_QUERY -> """
|
||||
call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$
|
||||
call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$
|
||||
${identityViewSqlPart}
|
||||
$idName$);
|
||||
""";
|
||||
|
@ -24,7 +24,9 @@ public class RbacRestrictedViewGenerator {
|
||||
--changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRbacRestrictedView('${rawTableName}',
|
||||
'${orderBy}',
|
||||
$orderBy$
|
||||
${orderBy}
|
||||
$orderBy$,
|
||||
$updates$
|
||||
${updates}
|
||||
$updates$);
|
||||
|
@ -32,6 +32,7 @@ import java.util.stream.Stream;
|
||||
import static java.lang.reflect.Modifier.isStatic;
|
||||
import static java.util.Arrays.stream;
|
||||
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.SQL.autoFetched;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
@ -141,35 +142,42 @@ public class RbacView {
|
||||
if (rootEntityAliasProxy != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
public RbacView importSubEntityAlias(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
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;
|
||||
}
|
||||
|
||||
public RbacView importEntityAlias(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final Column dependsOnColum, final SQL fetchSql) {
|
||||
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false);
|
||||
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView importEntityAlias(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final Column dependsOnColum) {
|
||||
importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false);
|
||||
importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
private EntityAlias importEntityAliasImpl(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) {
|
||||
final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity);
|
||||
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
|
||||
final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable);
|
||||
entityAliases.put(aliasName, entityAlias);
|
||||
try {
|
||||
importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity);
|
||||
@ -281,15 +289,19 @@ public class RbacView {
|
||||
return RbacView.this;
|
||||
}
|
||||
|
||||
public RbacView grantPermission(final String entityAliasName, final Permission perm) {
|
||||
final var entityAlias = findEntityAlias(entityAliasName);
|
||||
final var forTable = entityAlias.getRawTableName();
|
||||
findOrCreateGrantDef(findRbacPerm(entityAlias, perm, forTable), superRoleDef).toCreate();
|
||||
public RbacView grantPermission(final Permission perm) {
|
||||
final var forTable = rootEntityAlias.getRawTableName();
|
||||
findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate();
|
||||
return RbacView.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Nullable {
|
||||
NOT_NULL, // DEFAULT
|
||||
NULLABLE
|
||||
}
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class RbacGrantDefinition {
|
||||
@ -560,14 +572,14 @@ public class RbacView {
|
||||
.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) {
|
||||
this(aliasName, null, null, null, false);
|
||||
this(aliasName, null, null, null, false, null);
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -626,39 +638,35 @@ public class RbacView {
|
||||
return tableName.substring(0, tableName.length() - "_rv".length());
|
||||
}
|
||||
|
||||
public record Role(String roleName) {
|
||||
public enum Role {
|
||||
|
||||
public static final Role OWNER = new Role("owner");
|
||||
public static final Role ADMIN = new Role("admin");
|
||||
public static final Role AGENT = new Role("agent");
|
||||
public static final Role TENANT = new Role("tenant");
|
||||
public static final Role REFERRER = new Role("referrer");
|
||||
OWNER,
|
||||
ADMIN,
|
||||
AGENT,
|
||||
TENANT,
|
||||
REFERRER,
|
||||
|
||||
GUEST;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ":" + roleName;
|
||||
return ":" + roleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName));
|
||||
String roleName() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public record Permission(String permission) {
|
||||
|
||||
public static final Permission INSERT = new Permission("INSERT");
|
||||
public static final Permission DELETE = new Permission("DELETE");
|
||||
public static final Permission UPDATE = new Permission("UPDATE");
|
||||
public static final Permission SELECT = new Permission("SELECT");
|
||||
|
||||
public static Permission custom(final String permission) {
|
||||
return new Permission(permission);
|
||||
}
|
||||
public enum Permission {
|
||||
INSERT,
|
||||
DELETE,
|
||||
UPDATE,
|
||||
SELECT;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ":" + permission;
|
||||
return ":" + name();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,11 @@ public class RbacViewPostgresGenerator {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return plPgSql.toString();
|
||||
}
|
||||
return plPgSql.toString()
|
||||
.replace("\n\n\n", "\n\n")
|
||||
.replace("-- ====", "\n-- ====")
|
||||
.replace("\n\n--//", "\n--//");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void generateToChangeLog(final Path outputPath) {
|
||||
|
@ -82,6 +82,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn("begin");
|
||||
plPgSql.indented(() -> {
|
||||
plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);");
|
||||
plPgSql.writeLn();
|
||||
generateCreateRolesAndGrantsAfterInsert(plPgSql);
|
||||
plPgSql.ensureSingleEmptyLine();
|
||||
plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);");
|
||||
@ -90,6 +91,37 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
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) {
|
||||
plPgSql.writeLn("""
|
||||
/*
|
||||
@ -109,7 +141,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
plPgSql.chopEmptyLines();
|
||||
plPgSql.indented(() -> {
|
||||
updatableEntityAliases()
|
||||
referencedEntityAliases()
|
||||
.forEach((ea) -> {
|
||||
plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";");
|
||||
plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";");
|
||||
@ -120,6 +152,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn("begin");
|
||||
plPgSql.indented(() -> {
|
||||
plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);");
|
||||
plPgSql.writeLn();
|
||||
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
|
||||
plPgSql.ensureSingleEmptyLine();
|
||||
plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);");
|
||||
@ -132,11 +165,18 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
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) {
|
||||
referencedEntityAliases()
|
||||
.forEach((ea) -> plPgSql.writeLn(
|
||||
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
|
||||
with("ref", NEW.name())));
|
||||
.forEach((ea) -> {
|
||||
generateFetchedVars(plPgSql, ea, NEW);
|
||||
plPgSql.writeLn();
|
||||
});
|
||||
|
||||
createRolesWithGrantsSql(plPgSql, OWNER);
|
||||
createRolesWithGrantsSql(plPgSql, ADMIN);
|
||||
@ -165,14 +205,11 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
|
||||
plPgSql.ensureSingleEmptyLine();
|
||||
|
||||
updatableEntityAliases()
|
||||
referencedEntityAliases()
|
||||
.forEach((ea) -> {
|
||||
plPgSql.writeLn(
|
||||
ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";",
|
||||
with("ref", OLD.name()));
|
||||
plPgSql.writeLn(
|
||||
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
|
||||
with("ref", NEW.name()));
|
||||
generateFetchedVars(plPgSql, ea, OLD);
|
||||
generateFetchedVars(plPgSql, ea, NEW);
|
||||
plPgSql.writeLn();
|
||||
});
|
||||
|
||||
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) {
|
||||
return rbacDef.getUpdatableColumns().contains(c);
|
||||
}
|
||||
@ -222,7 +276,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
.replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef()))
|
||||
.replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef()));
|
||||
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()));
|
||||
};
|
||||
}
|
||||
@ -246,6 +300,10 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
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) {
|
||||
return permRef("createPermission", ref, permDef);
|
||||
}
|
||||
@ -256,7 +314,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
.replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias)
|
||||
? ref.name()
|
||||
: refVarName(ref, permDef.entityAlias))
|
||||
.replace("${perm}", permDef.permission.permission());
|
||||
.replace("${perm}", permDef.permission.name());
|
||||
}
|
||||
|
||||
private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) {
|
||||
@ -301,12 +359,12 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
generatePermissionsForRole(plPgSql, role);
|
||||
|
||||
generateUserGrantsForRole(plPgSql, role);
|
||||
|
||||
generateIncomingSuperRolesForRole(plPgSql, role);
|
||||
|
||||
generateOutgoingSubRolesForRole(plPgSql, role);
|
||||
|
||||
generateUserGrantsForRole(plPgSql, role);
|
||||
|
||||
plPgSql.chopTail(",\n");
|
||||
plPgSql.writeLn();
|
||||
});
|
||||
@ -333,7 +391,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
final var arrayElements = permissionGrantsForRole.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getPermDef)
|
||||
.map(RbacPermissionDefinition::getPermission)
|
||||
.map(RbacView.Permission::permission)
|
||||
.map(RbacView.Permission::name)
|
||||
.map(p -> "'" + p + "'")
|
||||
.sorted()
|
||||
.toList();
|
||||
@ -444,7 +502,11 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
private void generateUpdateTrigger(final StringWriter plPgSql) {
|
||||
|
||||
generateHeader(plPgSql, "update");
|
||||
if ( hasAnyUpdatableAndNullableEntityAliases() ) {
|
||||
generateSimplifiedUpdateTriggerFunction(plPgSql);
|
||||
} else {
|
||||
generateUpdateTriggerFunction(plPgSql);
|
||||
}
|
||||
|
||||
plPgSql.writeLn("""
|
||||
/*
|
||||
|
@ -38,12 +38,26 @@ public class StringWriter {
|
||||
--indentLevel;
|
||||
}
|
||||
|
||||
void indent(int levels) {
|
||||
indentLevel += levels;
|
||||
}
|
||||
|
||||
void unindent(int levels) {
|
||||
indentLevel -= levels;
|
||||
}
|
||||
|
||||
void indented(final Runnable indented) {
|
||||
indent();
|
||||
indented.run();
|
||||
unindent();
|
||||
}
|
||||
|
||||
void indented(int levels, final Runnable indented) {
|
||||
indent(levels);
|
||||
indented.run();
|
||||
unindent(levels);
|
||||
}
|
||||
|
||||
boolean chopTail(final String tail) {
|
||||
if (string.toString().endsWith(tail)) {
|
||||
string.setLength(string.length() - tail.length());
|
||||
@ -103,8 +117,8 @@ public class StringWriter {
|
||||
text = matcher.replaceAll(varDef.value());
|
||||
});
|
||||
return text;
|
||||
} catch (Exception exc) {
|
||||
throw exc;
|
||||
} catch (final RuntimeException exc) {
|
||||
throw exc; // FIXME: just for debugging, remove try/catch before merging to master
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.
|
||||
@Service
|
||||
public class RbacGrantsDiagramService {
|
||||
|
||||
private static final int GRANT_LIMIT = 500;
|
||||
|
||||
public static void writeToFile(final String title, final String graph, final String fileName) {
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
|
||||
@ -42,7 +44,11 @@ public class RbacGrantsDiagramService {
|
||||
PERMISSIONS,
|
||||
NOT_ASSUMED,
|
||||
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
|
||||
@ -55,7 +61,7 @@ public class RbacGrantsDiagramService {
|
||||
private EntityManager em;
|
||||
|
||||
public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
|
||||
final var graph = new HashSet<RawRbacGrantEntity>();
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
|
||||
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) {
|
||||
final var grants = rawGrantRepo.findByAscendingUuid(refUuid);
|
||||
grants.forEach(g -> {
|
||||
if ( g.getDescendantIdName() == null ) {
|
||||
// FIXME: what's that?
|
||||
return;
|
||||
}
|
||||
if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) {
|
||||
return;
|
||||
}
|
||||
@ -88,7 +98,7 @@ public class RbacGrantsDiagramService {
|
||||
.setParameter("targetObject", targetObject)
|
||||
.setParameter("op", op)
|
||||
.getSingleResult();
|
||||
final var graph = new HashSet<RawRbacGrantEntity>();
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
traverseGrantsFrom(graph, refUuid, includes);
|
||||
return toMermaidFlowchart(graph, includes);
|
||||
}
|
||||
@ -116,7 +126,7 @@ public class RbacGrantsDiagramService {
|
||||
)
|
||||
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
|
||||
.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()
|
||||
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
|
||||
.sorted()
|
||||
@ -127,14 +137,15 @@ public class RbacGrantsDiagramService {
|
||||
: "";
|
||||
|
||||
final var grants = graph.stream()
|
||||
.map(g -> quoted(g.getAscendantIdName())
|
||||
.map(g -> cleanId(g.getAscendantIdName())
|
||||
+ " -->" + (g.isAssumed() ? " " : "|XX| ")
|
||||
+ quoted(g.getDescendantIdName()))
|
||||
+ cleanId(g.getDescendantIdName()))
|
||||
.sorted()
|
||||
.collect(joining("\n"));
|
||||
|
||||
final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n";
|
||||
return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "")
|
||||
+ (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "")
|
||||
+ "flowchart TB\n\n"
|
||||
+ entities
|
||||
+ grants;
|
||||
@ -151,7 +162,7 @@ public class RbacGrantsDiagramService {
|
||||
// }
|
||||
// return "[" + table + "\n" + entity + "]";
|
||||
// }
|
||||
return "[" + entityId + "]";
|
||||
return "[" + cleanId(entityId) + "]";
|
||||
}
|
||||
|
||||
private static String renderEntityIdName(final Node node) {
|
||||
@ -170,7 +181,7 @@ public class RbacGrantsDiagramService {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -196,9 +207,24 @@ public class RbacGrantsDiagramService {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String quoted(final String idName) {
|
||||
return idName.replace(" ", ":").replaceAll("@.*", "");
|
||||
private static String cleanId(final String idName) {
|
||||
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) {
|
||||
|
@ -53,7 +53,7 @@ public class TestDomainEntity implements HasUuid {
|
||||
SELECT * FROM test_package p
|
||||
WHERE p.uuid= ${ref}.packageUuid
|
||||
"""))
|
||||
.toRole("package", ADMIN).grantPermission("domain", INSERT)
|
||||
.toRole("package", ADMIN).grantPermission(INSERT)
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.incomingSuperRole("package", ADMIN);
|
||||
|
@ -54,7 +54,7 @@ public class TestPackageEntity implements HasUuid {
|
||||
SELECT * FROM test_customer c
|
||||
WHERE c.uuid= ${ref}.customerUuid
|
||||
"""))
|
||||
.toRole("customer", ADMIN).grantPermission("package", INSERT)
|
||||
.toRole("customer", ADMIN).grantPermission(INSERT)
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.incomingSuperRole("customer", ADMIN);
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 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
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
|
@ -1,5 +1,6 @@
|
||||
--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:--//
|
||||
@ -36,8 +37,8 @@ begin
|
||||
perform createRoleWithGrants(
|
||||
testCustomerOwner(NEW),
|
||||
permissions => array['DELETE'],
|
||||
userUuids => array[currentUserUuid()],
|
||||
incomingSuperRoles => array[globalAdmin(unassumed())]
|
||||
incomingSuperRoles => array[globalAdmin(unassumed())],
|
||||
userUuids => array[currentUserUuid()]
|
||||
);
|
||||
|
||||
perform createRoleWithGrants(
|
||||
@ -72,9 +73,9 @@ create trigger insertTriggerForTestCustomer_tg
|
||||
after insert on test_customer
|
||||
for each row
|
||||
execute procedure insertTriggerForTestCustomer_tf();
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--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.
|
||||
when ( not isGlobalAdmin() )
|
||||
execute procedure test_customer_insert_permission_missing_tf();
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -106,13 +107,15 @@ create trigger test_customer_insert_permission_check_tg
|
||||
call generateRbacIdentityViewFromProjection('test_customer', $idName$
|
||||
prefix
|
||||
$idName$);
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRbacRestrictedView('test_customer',
|
||||
'reference',
|
||||
$orderBy$
|
||||
reference
|
||||
$orderBy$,
|
||||
$updates$
|
||||
reference = new.reference,
|
||||
prefix = new.prefix,
|
||||
@ -120,4 +123,3 @@ call generateRbacRestrictedView('test_customer',
|
||||
$updates$);
|
||||
--//
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 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
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
|
@ -1,5 +1,6 @@
|
||||
--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:--//
|
||||
@ -33,9 +34,12 @@ declare
|
||||
|
||||
begin
|
||||
call enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM test_customer c
|
||||
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(
|
||||
testPackageOwner(NEW),
|
||||
@ -75,9 +79,9 @@ create trigger insertTriggerForTestPackage_tg
|
||||
after insert on test_package
|
||||
for each row
|
||||
execute procedure insertTriggerForTestPackage_tf();
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-package-rbac-update-trigger:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -101,14 +105,18 @@ begin
|
||||
|
||||
SELECT * FROM test_customer c
|
||||
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
|
||||
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
|
||||
|
||||
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer));
|
||||
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer));
|
||||
|
||||
call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer));
|
||||
call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer));
|
||||
@ -138,9 +146,9 @@ create trigger updateTriggerForTestPackage_tg
|
||||
after update on test_package
|
||||
for each row
|
||||
execute procedure updateTriggerForTestPackage_tf();
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-package-rbac-INSERT:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -160,7 +168,7 @@ do language plpgsql $$
|
||||
LOOP
|
||||
roleUuid := findRoleId(testCustomerAdmin(row));
|
||||
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package');
|
||||
call grantPermissionToRole(roleUuid, permissionUuid);
|
||||
call grantPermissionToRole(permissionUuid, roleUuid);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
@ -174,12 +182,13 @@ create or replace function test_package_test_customer_insert_tf()
|
||||
strict as $$
|
||||
begin
|
||||
call grantPermissionToRole(
|
||||
testCustomerAdmin(NEW),
|
||||
createPermission(NEW.uuid, 'INSERT', 'test_package'));
|
||||
createPermission(NEW.uuid, 'INSERT', 'test_package'),
|
||||
testCustomerAdmin(NEW));
|
||||
return NEW;
|
||||
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
|
||||
for each row
|
||||
execute procedure test_package_test_customer_insert_tf();
|
||||
@ -191,17 +200,27 @@ create or replace function test_package_insert_permission_missing_tf()
|
||||
returns trigger
|
||||
language plpgsql as $$
|
||||
begin
|
||||
raise exception '[403] insert into test_package not allowed for current subjects % (%)',
|
||||
if ( not hasInsertPermission(
|
||||
( 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; $$;
|
||||
|
||||
create trigger test_package_insert_permission_check_tg
|
||||
before insert on test_package
|
||||
for each row
|
||||
when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') )
|
||||
execute procedure test_package_insert_permission_missing_tf();
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -209,13 +228,15 @@ create trigger test_package_insert_permission_check_tg
|
||||
call generateRbacIdentityViewFromProjection('test_package', $idName$
|
||||
name
|
||||
$idName$);
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRbacRestrictedView('test_package',
|
||||
'name',
|
||||
$orderBy$
|
||||
name
|
||||
$orderBy$,
|
||||
$updates$
|
||||
version = new.version,
|
||||
customerUuid = new.customerUuid,
|
||||
@ -223,4 +244,3 @@ call generateRbacRestrictedView('test_package',
|
||||
$updates$);
|
||||
--//
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 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
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
|
@ -1,5 +1,6 @@
|
||||
--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:--//
|
||||
@ -33,9 +34,12 @@ declare
|
||||
|
||||
begin
|
||||
call enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM test_package p
|
||||
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(
|
||||
testDomainOwner(NEW),
|
||||
@ -71,9 +75,9 @@ create trigger insertTriggerForTestDomain_tg
|
||||
after insert on test_domain
|
||||
for each row
|
||||
execute procedure insertTriggerForTestDomain_tf();
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-domain-rbac-update-trigger:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -97,14 +101,18 @@ begin
|
||||
|
||||
SELECT * FROM test_package p
|
||||
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
|
||||
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
|
||||
|
||||
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
|
||||
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
|
||||
|
||||
call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage));
|
||||
call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage));
|
||||
@ -137,9 +145,9 @@ create trigger updateTriggerForTestDomain_tg
|
||||
after update on test_domain
|
||||
for each row
|
||||
execute procedure updateTriggerForTestDomain_tf();
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-domain-rbac-INSERT:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -159,7 +167,7 @@ do language plpgsql $$
|
||||
LOOP
|
||||
roleUuid := findRoleId(testPackageAdmin(row));
|
||||
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain');
|
||||
call grantPermissionToRole(roleUuid, permissionUuid);
|
||||
call grantPermissionToRole(permissionUuid, roleUuid);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
@ -173,12 +181,13 @@ create or replace function test_domain_test_package_insert_tf()
|
||||
strict as $$
|
||||
begin
|
||||
call grantPermissionToRole(
|
||||
testPackageAdmin(NEW),
|
||||
createPermission(NEW.uuid, 'INSERT', 'test_domain'));
|
||||
createPermission(NEW.uuid, 'INSERT', 'test_domain'),
|
||||
testPackageAdmin(NEW));
|
||||
return NEW;
|
||||
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
|
||||
for each row
|
||||
execute procedure test_domain_test_package_insert_tf();
|
||||
@ -190,17 +199,27 @@ create or replace function test_domain_insert_permission_missing_tf()
|
||||
returns trigger
|
||||
language plpgsql as $$
|
||||
begin
|
||||
raise exception '[403] insert into test_domain not allowed for current subjects % (%)',
|
||||
if ( not hasInsertPermission(
|
||||
( 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; $$;
|
||||
|
||||
create trigger test_domain_insert_permission_check_tg
|
||||
before insert on test_domain
|
||||
for each row
|
||||
when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') )
|
||||
execute procedure test_domain_insert_permission_missing_tf();
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
@ -208,13 +227,15 @@ create trigger test_domain_insert_permission_check_tg
|
||||
call generateRbacIdentityViewFromProjection('test_domain', $idName$
|
||||
name
|
||||
$idName$);
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRbacRestrictedView('test_domain',
|
||||
'name',
|
||||
$orderBy$
|
||||
name
|
||||
$orderBy$,
|
||||
$updates$
|
||||
version = new.version,
|
||||
packageUuid = new.packageUuid,
|
||||
@ -222,4 +243,3 @@ call generateRbacRestrictedView('test_domain',
|
||||
$updates$);
|
||||
--//
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user