|
|
@ -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);
|
|
|
|