RBAC generator with conditional grants used for REPRESENTATIVE-Relation #33

Merged
hsh-michaelhoennig merged 31 commits from rbac-generator-with-conditional-grants into master 2024-04-08 11:16:07 +02:00
5 changed files with 40 additions and 31 deletions
Showing only changes of commit 6346dff3ef - Show all commits

View File

@ -118,7 +118,6 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
with.incomingSuperRole("holderPerson", ADMIN); with.incomingSuperRole("holderPerson", ADMIN);
}) })
.createSubRole(TENANT, (with) -> { .createSubRole(TENANT, (with) -> {
with.incomingSuperRole("holderPerson", ADMIN);
with.incomingSuperRole("contact", ADMIN); with.incomingSuperRole("contact", ADMIN);
with.outgoingSubRole("anchorPerson", REFERRER); with.outgoingSubRole("anchorPerson", REFERRER);
with.outgoingSubRole("holderPerson", REFERRER); with.outgoingSubRole("holderPerson", REFERRER);

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
import java.util.HashSet; import java.util.HashSet;
@ -22,7 +23,7 @@ import static org.apache.commons.lang3.StringUtils.uncapitalize;
class RolesGrantsAndPermissionsGenerator { class RolesGrantsAndPermissionsGenerator {
private final RbacView rbacDef; private final RbacView rbacDef;
private final Set<RbacView.RbacGrantDefinition> rbacGrants = new HashSet<>(); private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
private final String liquibaseTagPrefix; private final String liquibaseTagPrefix;
private final String simpleEntityName; private final String simpleEntityName;
private final String simpleEntityVarName; private final String simpleEntityVarName;
@ -31,7 +32,7 @@ class RolesGrantsAndPermissionsGenerator {
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
this.rbacDef = rbacDef; this.rbacDef = rbacDef;
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream() this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
.filter(RbacView.RbacGrantDefinition::isToCreate) .filter(RbacGrantDefinition::isToCreate)
.collect(toSet())); .collect(toSet()));
this.liquibaseTagPrefix = liquibaseTagPrefix; this.liquibaseTagPrefix = liquibaseTagPrefix;
@ -67,13 +68,11 @@ class RolesGrantsAndPermissionsGenerator {
NEW ${rawTableName} NEW ${rawTableName}
) )
language plpgsql as $$ language plpgsql as $$
declare
""" """
.replace("${simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName)); .replace("${rawTableName}", rawTableName));
plPgSql.chopEmptyLines(); plPgSql.writeLn("declare");
plPgSql.indented(() -> { plPgSql.indented(() -> {
referencedEntityAliases() referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";")); .forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"));
@ -172,6 +171,10 @@ class RolesGrantsAndPermissionsGenerator {
.anyMatch(e -> true); .anyMatch(e -> true);
} }
private boolean hasAnyConditionalGrants() {
return rbacDef.getGrantDefs().stream().anyMatch(RbacGrantDefinition::isConditional);
}
private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) {
referencedEntityAliases() referencedEntityAliases()
.forEach((ea) -> { .forEach((ea) -> {
@ -248,7 +251,7 @@ class RolesGrantsAndPermissionsGenerator {
private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) { private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) {
rbacDef.getGrantDefs().stream() rbacDef.getGrantDefs().stream()
.filter(RbacView.RbacGrantDefinition::isToCreate) .filter(RbacGrantDefinition::isToCreate)
.filter(g -> g.dependsOnColumn(columnName)) .filter(g -> g.dependsOnColumn(columnName))
.filter(g -> !isInsertPermissionGrant(g)) .filter(g -> !isInsertPermissionGrant(g))
.forEach(g -> { .forEach(g -> {
@ -259,21 +262,21 @@ class RolesGrantsAndPermissionsGenerator {
}); });
} }
private static Boolean isInsertPermissionGrant(final RbacView.RbacGrantDefinition g) { private static Boolean isInsertPermissionGrant(final RbacGrantDefinition g) {
final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false); final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false);
return isInsertPermissionGrant; return isInsertPermissionGrant;
} }
private void generateGrants(final StringWriter plPgSql, final RbacView.RbacGrantDefinition.GrantType grantType) { private void generateGrants(final StringWriter plPgSql, final RbacGrantDefinition.GrantType grantType) {
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
rbacGrants.stream() rbacGrants.stream()
.filter(g -> g.grantType() == grantType) .filter(g -> g.grantType() == grantType)
.map(this::generateGrant) .map(this::generateGrant)
.sorted() .sorted()
.forEach(text -> plPgSql.writeLn(text)); .forEach(text -> plPgSql.writeLn(text, with("ref", NEW.name())));
} }
private String generateRevoke(RbacView.RbacGrantDefinition grantDef) { private String generateRevoke(RbacGrantDefinition grantDef) {
return switch (grantDef.grantType()) { return switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call revokeRoleFromRole(${subRoleRef}, ${superRoleRef});" case ROLE_TO_ROLE -> "call revokeRoleFromRole(${subRoleRef}, ${superRoleRef});"
@ -285,8 +288,8 @@ class RolesGrantsAndPermissionsGenerator {
}; };
} }
private String generateGrant(RbacView.RbacGrantDefinition grantDef) { private String generateGrant(RbacGrantDefinition grantDef) {
return switch (grantDef.grantType()) { final var grantSql = switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});" case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});"
.replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()") .replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()")
@ -298,6 +301,12 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${permRef}", createPerm(NEW, grantDef.getPermDef())) .replace("${permRef}", createPerm(NEW, grantDef.getPermDef()))
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
}; };
if (grantDef.isConditional()) {
return "if " + grantDef.getSqlWhere() + " then\n"
+ " " + grantSql + "\n"
+ "end if;";
}
return grantSql;
} }
private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
@ -380,7 +389,7 @@ class RolesGrantsAndPermissionsGenerator {
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role); final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
if (!grantsToUsers.isEmpty()) { if (!grantsToUsers.isEmpty()) {
final var arrayElements = grantsToUsers.stream() final var arrayElements = grantsToUsers.stream()
.map(RbacView.RbacGrantDefinition::getUserDef) .map(RbacGrantDefinition::getUserDef)
.map(this::toPlPgSqlReference) .map(this::toPlPgSqlReference)
.toList(); .toList();
plPgSql.indented(() -> plPgSql.indented(() ->
@ -393,7 +402,7 @@ class RolesGrantsAndPermissionsGenerator {
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) { if (!permissionGrantsForRole.isEmpty()) {
final var arrayElements = permissionGrantsForRole.stream() final var arrayElements = permissionGrantsForRole.stream()
.map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacGrantDefinition::getPermDef)
.map(RbacPermissionDefinition::getPermission) .map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::name) .map(RbacView.Permission::name)
.map(p -> "'" + p + "'") .map(p -> "'" + p + "'")
@ -406,26 +415,30 @@ class RolesGrantsAndPermissionsGenerator {
} }
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
if (!incomingGrants.isEmpty()) { .filter(g -> !g.isConditional())
final var arrayElements = incomingGrants.stream() .toList();
if (!unconditionalIncomingGrants.isEmpty()) {
final var arrayElements = unconditionalIncomingGrants.stream()
.map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed())) .map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed()))
.sorted().toList(); .sorted().toList();
plPgSql.indented(() -> plPgSql.indented(() ->
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
rbacGrants.removeAll(incomingGrants); rbacGrants.removeAll(unconditionalIncomingGrants);
} }
} }
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
if (!outgoingGrants.isEmpty()) { .filter(g -> !g.isConditional())
final var arrayElements = outgoingGrants.stream() .toList();
if (!unconditionalOutgoingGrants.isEmpty()) {
final var arrayElements = unconditionalOutgoingGrants.stream()
.map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed())) .map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed()))
.sorted().toList(); .sorted().toList();
plPgSql.indented(() -> plPgSql.indented(() ->
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
rbacGrants.removeAll(outgoingGrants); rbacGrants.removeAll(unconditionalOutgoingGrants);
} }
} }
@ -435,7 +448,7 @@ class RolesGrantsAndPermissionsGenerator {
: arrayElements.stream().collect(joining(",\n\t", "\n\t", "")); : arrayElements.stream().collect(joining(",\n\t", "\n\t", ""));
} }
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole( private Set<RbacGrantDefinition> findPermissionsGrantsForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -444,7 +457,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole( private Set<RbacGrantDefinition> findGrantsToUserForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -453,7 +466,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole( private Set<RbacGrantDefinition> findIncomingSuperRolesForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -462,7 +475,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole( private Set<RbacGrantDefinition> findOutgoingSuperRolesForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -506,7 +519,7 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateTrigger(final StringWriter plPgSql) { private void generateUpdateTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "update"); generateHeader(plPgSql, "update");
if ( hasAnyUpdatableAndNullableEntityAliases() ) { if ( hasAnyUpdatableAndNullableEntityAliases() || hasAnyConditionalGrants() ) {
generateSimplifiedUpdateTriggerFunction(plPgSql); generateSimplifiedUpdateTriggerFunction(plPgSql);
} else { } else {
generateUpdateTriggerFunction(plPgSql); generateUpdateTriggerFunction(plPgSql);

View File

@ -87,7 +87,6 @@ role:anchorPerson:ADMIN ==> role:relation:ADMIN
role:relation:ADMIN ==> role:relation:AGENT role:relation:ADMIN ==> role:relation:AGENT
role:holderPerson:ADMIN ==> role:relation:AGENT role:holderPerson:ADMIN ==> role:relation:AGENT
role:relation:AGENT ==> role:relation:TENANT role:relation:AGENT ==> role:relation:TENANT
role:holderPerson:ADMIN ==> role:relation:TENANT
role:contact:ADMIN ==> role:relation:TENANT role:contact:ADMIN ==> role:relation:TENANT
role:relation:TENANT ==> role:anchorPerson:REFERRER role:relation:TENANT ==> role:anchorPerson:REFERRER
role:relation:TENANT ==> role:holderPerson:REFERRER role:relation:TENANT ==> role:holderPerson:REFERRER

View File

@ -74,7 +74,6 @@ begin
permissions => array['SELECT'], permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficeContactADMIN(newContact), hsOfficeContactADMIN(newContact),
hsOfficePersonADMIN(newHolderPerson),
hsOfficeRelationAGENT(NEW)], hsOfficeRelationAGENT(NEW)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsOfficeContactREFERRER(newContact), hsOfficeContactREFERRER(newContact),

View File

@ -153,7 +153,6 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
// REPRESENTATIVE holder person -> (represented) anchor person // REPRESENTATIVE holder person -> (represented) anchor person
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_contact#fourthcontact:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_contact#fourthcontact:ADMIN by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_person#BesslerBert:ADMIN by system and assume }",
null) null)
); );