RBAC Diagram+PostgreSQL Generator #21

Merged
hsh-michaelhoennig merged 54 commits from experimental-rbacview-generator into master 2024-03-11 12:30:44 +01:00
2 changed files with 86 additions and 92 deletions
Showing only changes of commit 86cf4f6c97 - Show all commits

View File

@ -14,15 +14,14 @@ public class RbacViewPostgresGenerator {
private final RbacView rbacDef; private final RbacView rbacDef;
private final String liqibaseTagPrefix; private final String liqibaseTagPrefix;
private final StringBuilder plPgSql = new StringBuilder(); private final StringWriter plPgSql = new StringWriter();
public RbacViewPostgresGenerator(final RbacView forRbacDef) { public RbacViewPostgresGenerator(final RbacView forRbacDef) {
rbacDef = forRbacDef; rbacDef = forRbacDef;
liqibaseTagPrefix = rbacDef.getRootEntityAlias().entityClass().getSimpleName(); liqibaseTagPrefix = rbacDef.getRootEntityAlias().entityClass().getSimpleName();
plPgSql.append(""" plPgSql.writeLn("""
--liquibase formatted sql --liquibase formatted sql
-- generated at: %{timestamp} -- generated at: %{timestamp}
""" """
.replace("%{timestamp}", LocalDateTime.now().toString())); .replace("%{timestamp}", LocalDateTime.now().toString()));

View File

@ -26,34 +26,33 @@ 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(rbacGrants); this.rbacGrants.addAll(rbacDef.getGrantDefs());
this.liquibaseTagPrefix = liquibaseTagPrefix; this.liquibaseTagPrefix = liquibaseTagPrefix;
entityClass = rbacDef.getRootEntityAlias().entityClass(); entityClass = rbacDef.getRootEntityAlias().entityClass();
simpleEntityName = entityClass.getSimpleName(); simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
simpleEntityVarName = uncapitalize(simpleEntityName); simpleEntityName = capitalize(simpleEntityVarName);
rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
} }
void generateTo(final StringBuilder plPgSql) { void generateTo(final StringWriter plPgSql) {
generateHeader(plPgSql); generateHeader(plPgSql);
generateTriggerFunction(plPgSql); generateTriggerFunction(plPgSql);
generageInsertTrigger(plPgSql); generageInsertTrigger(plPgSql);
generateFooter(plPgSql); generateFooter(plPgSql);
} }
private void generateHeader(final StringBuilder plPgSql) { private void generateHeader(final StringWriter plPgSql) {
plPgSql.append(""" plPgSql.writeLn("""
-- ============================================================================ -- ============================================================================
--changeset %{liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--// --changeset %{liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
""" """
.replace("%{liquibaseTagPrefix}", liquibaseTagPrefix)); .replace("%{liquibaseTagPrefix}", liquibaseTagPrefix));
} }
private void generateTriggerFunction(final StringBuilder plPgSql) { private void generateTriggerFunction(final StringWriter plPgSql) {
plPgSql.append(""" plPgSql.writeLn("""
/* /*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
@ -66,27 +65,30 @@ class RolesGrantsAndPermissionsGenerator {
if TG_OP <> 'INSERT' then if TG_OP <> 'INSERT' then
raise exception 'invalid usage of TRIGGER AFTER INSERT function'; raise exception 'invalid usage of TRIGGER AFTER INSERT function';
end if; end if;
%{createRolesWithGrantsSql}
return NEW;
end; $$;
""" """
.replace("%{simpleEntityName}", simpleEntityName) .replace("%{simpleEntityName}", simpleEntityName));
.replace("%{createRolesWithGrantsSql}", createRolesWithGrantsSql())
);
plPgSql.indented(() -> {
createRolesWithGrantsSql(plPgSql, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT);
createRolesWithGrantsSql(plPgSql, TENANT);
createRolesWithGrantsSql(plPgSql, REFERRER);
if (!rbacGrants.isEmpty()) {
throw new IllegalStateException("unprocessed grants: " + rbacGrants);
// rbacGrants.forEach(g -> plPgSql.writeLn("-- unprocessed grant: " + g));
}
plPgSql.writeLn("return NEW;");
});
plPgSql.writeLn("end; $$;");
plPgSql.writeLn();
} }
private String createRolesWithGrantsSql() {
final var plPgSql = new StringBuilder();
createRolesWithGrantsSql(plPgSql, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT);
createRolesWithGrantsSql(plPgSql, TENANT);
createRolesWithGrantsSql(plPgSql, REFERRER);
return plPgSql.toString();
}
private void createRolesWithGrantsSql(final StringBuilder plPgSql, final RbacView.Role role) { private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
final var isToCreate = rbacDef.getRoleDefs().stream() final var isToCreate = rbacDef.getRoleDefs().stream()
.filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role ) .filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role )
@ -95,62 +97,64 @@ class RolesGrantsAndPermissionsGenerator {
return; return;
} }
plPgSql.append(""" plPgSql.writeLn();
plPgSql.writeLn("perform createRoleWithGrants(");
plPgSql.indented( () -> {
plPgSql.writeLn("%{simpleVarName)%{roleSuffix}(NEW),"
.replace("%{simpleVarName)", simpleEntityVarName)
.replace("%{roleSuffix}", capitalize(role.roleName())));
perform createRoleWithGrants( final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
%{simpleEntityVarName)%{roleSuffix}(NEW), if (!permissionGrantsForRole.isEmpty()) {
""" final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream()
.replace("%{simpleEntityVarName)", simpleEntityVarName) .map(RbacView.RbacGrantDefinition::getPermDef)
.replace("%{roleSuffix}", capitalize(role.roleName()))); .map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::permission)
.map(p -> "'" + p + "'")
.collect(joining(", "));
plPgSql.indented( () ->
plPgSql.writeLn("permissions => array[" + permissionsForRoleInPlPgSql + "],\n"));
rbacGrants.removeAll(permissionGrantsForRole);
}
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) { if (!grantsToUsers.isEmpty()) {
final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream() final var grantsToUsersPlPgSql = grantsToUsers.stream()
.map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacView.RbacGrantDefinition::getUserDef)
.map(RbacPermissionDefinition::getPermission) .map(this::toPlPgSqlReference)
.map(RbacView.Permission::permission) .collect(joining(", "));
.map(p -> "'" + p + "'") plPgSql.indented(() ->
.collect(joining(", ")); plPgSql.writeLn("userUuids => array[" + grantsToUsersPlPgSql + "],\n"));
plPgSql.append(indent(3) + "permissions => array[" + permissionsForRoleInPlPgSql + "],\n"); rbacGrants.removeAll(grantsToUsers);
rbacGrants.removeAll(permissionGrantsForRole); }
}
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role); final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!grantsToUsers.isEmpty()) { if (!incomingGrants.isEmpty()) {
final var grantsToUsersPlPgSql = grantsToUsers.stream() final var incomingGrantsInPlPgSql = incomingGrants.stream()
.map(RbacView.RbacGrantDefinition::getUserDef) .map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(this::toPlPgSqlReference) .map(r -> toPlPgSqlReference(NEW, r))
.collect(joining(", ")); .collect(joining(", "));
plPgSql.append(indent(3) + "userUuids => array[" + grantsToUsersPlPgSql + "],\n"); plPgSql.indented(() ->
rbacGrants.removeAll(grantsToUsers); plPgSql.writeLn("incomingSuperRoles => array[" + incomingGrantsInPlPgSql + "],\n"));
} rbacGrants.removeAll(incomingGrants);
}
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!incomingGrants.isEmpty()) { if (!outgoingGrants.isEmpty()) {
final var incomingGrantsInPlPgSql = incomingGrants.stream() final var outgoingGrantsInPlPgSql = outgoingGrants.stream()
.map(RbacView.RbacGrantDefinition::getSuperRoleDef) .map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(r -> toPlPgSqlReference(NEW, r)) .map(r -> toPlPgSqlReference(NEW, r))
.collect(joining(", ")); .collect(joining(", "));
plPgSql.append(indent(3) + "incomingSuperRoles => array[" + incomingGrantsInPlPgSql + "],\n"); plPgSql.indented(() ->
rbacGrants.removeAll(incomingGrants); plPgSql.writeLn("outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n"));
} rbacGrants.removeAll(outgoingGrants);
}
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); plPgSql.chopTail(",\n");
if (!outgoingGrants.isEmpty()) { plPgSql.writeLn();
final var outgoingGrantsInPlPgSql = outgoingGrants.stream() });
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(r -> toPlPgSqlReference(NEW, r))
.collect(joining(", "));
plPgSql.append(indent(3) + "outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n");
rbacGrants.removeAll(outgoingGrants);
}
if (!rbacGrants.isEmpty()) { plPgSql.writeLn(");");
throw new IllegalStateException("unprocessed grants: " + rbacGrants);
}
chopTail(plPgSql, ",\n");
plPgSql.append("\n" + indent(2) + ");\n");
} }
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
@ -182,8 +186,8 @@ class RolesGrantsAndPermissionsGenerator {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
private void generageInsertTrigger(final StringBuilder plPgSql) { private void generageInsertTrigger(final StringWriter plPgSql) {
plPgSql.append(""" plPgSql.writeLn("""
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new %{simpleEntityName} An AFTER INSERT TRIGGER which creates the role structure for a new %{simpleEntityName}
*/ */
@ -200,8 +204,9 @@ class RolesGrantsAndPermissionsGenerator {
); );
} }
private static void generateFooter(final StringBuilder plPgSql) { private static void generateFooter(final StringWriter plPgSql) {
plPgSql.append("\n\n"); plPgSql.writeLn();
plPgSql.writeLn();
} }
private String withoutRvSuffix(final String tableName) { private String withoutRvSuffix(final String tableName) {
@ -229,14 +234,4 @@ class RolesGrantsAndPermissionsGenerator {
private static String toTriggerReference(final PostgresTriggerReference triggerRef, final RbacView.EntityAlias entityAlias) { private static String toTriggerReference(final PostgresTriggerReference triggerRef, final RbacView.EntityAlias entityAlias) {
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName()); return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
} }
private String indent(final int tabs) {
return " ".repeat(4*tabs);
}
private void chopTail(final StringBuilder plPgSql, final String tail) {
if (plPgSql.toString().endsWith(tail)) {
plPgSql.setLength(plPgSql.length() - tail.length());
}
}
} }