RBAC Diagram+PostgreSQL Generator #21
@ -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()));
|
||||||
|
|
||||||
|
@ -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(");
|
||||||
perform createRoleWithGrants(
|
plPgSql.indented( () -> {
|
||||||
%{simpleEntityVarName)%{roleSuffix}(NEW),
|
plPgSql.writeLn("%{simpleVarName)%{roleSuffix}(NEW),"
|
||||||
"""
|
.replace("%{simpleVarName)", simpleEntityVarName)
|
||||||
.replace("%{simpleEntityVarName)", simpleEntityVarName)
|
.replace("%{roleSuffix}", capitalize(role.roleName())));
|
||||||
.replace("%{roleSuffix}", capitalize(role.roleName())));
|
|
||||||
|
|
||||||
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!permissionGrantsForRole.isEmpty()) {
|
if (!permissionGrantsForRole.isEmpty()) {
|
||||||
final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream()
|
final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream()
|
||||||
.map(RbacView.RbacGrantDefinition::getPermDef)
|
.map(RbacView.RbacGrantDefinition::getPermDef)
|
||||||
.map(RbacPermissionDefinition::getPermission)
|
.map(RbacPermissionDefinition::getPermission)
|
||||||
.map(RbacView.Permission::permission)
|
.map(RbacView.Permission::permission)
|
||||||
.map(p -> "'" + p + "'")
|
.map(p -> "'" + p + "'")
|
||||||
.collect(joining(", "));
|
.collect(joining(", "));
|
||||||
plPgSql.append(indent(3) + "permissions => array[" + permissionsForRoleInPlPgSql + "],\n");
|
plPgSql.indented( () ->
|
||||||
rbacGrants.removeAll(permissionGrantsForRole);
|
plPgSql.writeLn("permissions => array[" + permissionsForRoleInPlPgSql + "],\n"));
|
||||||
}
|
rbacGrants.removeAll(permissionGrantsForRole);
|
||||||
|
}
|
||||||
|
|
||||||
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!grantsToUsers.isEmpty()) {
|
if (!grantsToUsers.isEmpty()) {
|
||||||
final var grantsToUsersPlPgSql = grantsToUsers.stream()
|
final var grantsToUsersPlPgSql = grantsToUsers.stream()
|
||||||
.map(RbacView.RbacGrantDefinition::getUserDef)
|
.map(RbacView.RbacGrantDefinition::getUserDef)
|
||||||
.map(this::toPlPgSqlReference)
|
.map(this::toPlPgSqlReference)
|
||||||
.collect(joining(", "));
|
.collect(joining(", "));
|
||||||
plPgSql.append(indent(3) + "userUuids => array[" + grantsToUsersPlPgSql + "],\n");
|
plPgSql.indented(() ->
|
||||||
rbacGrants.removeAll(grantsToUsers);
|
plPgSql.writeLn("userUuids => array[" + grantsToUsersPlPgSql + "],\n"));
|
||||||
}
|
rbacGrants.removeAll(grantsToUsers);
|
||||||
|
}
|
||||||
|
|
||||||
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!incomingGrants.isEmpty()) {
|
if (!incomingGrants.isEmpty()) {
|
||||||
final var incomingGrantsInPlPgSql = incomingGrants.stream()
|
final var incomingGrantsInPlPgSql = incomingGrants.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("incomingSuperRoles => array[" + incomingGrantsInPlPgSql + "],\n"));
|
||||||
}
|
rbacGrants.removeAll(incomingGrants);
|
||||||
|
}
|
||||||
|
|
||||||
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!outgoingGrants.isEmpty()) {
|
if (!outgoingGrants.isEmpty()) {
|
||||||
final var outgoingGrantsInPlPgSql = outgoingGrants.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) + "outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n");
|
plPgSql.indented(() ->
|
||||||
rbacGrants.removeAll(outgoingGrants);
|
plPgSql.writeLn("outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n"));
|
||||||
}
|
rbacGrants.removeAll(outgoingGrants);
|
||||||
|
}
|
||||||
|
|
||||||
if (!rbacGrants.isEmpty()) {
|
plPgSql.chopTail(",\n");
|
||||||
throw new IllegalStateException("unprocessed grants: " + rbacGrants);
|
plPgSql.writeLn();
|
||||||
}
|
});
|
||||||
|
|
||||||
chopTail(plPgSql, ",\n");
|
plPgSql.writeLn(");");
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user