RBAC Diagram+PostgreSQL Generator #21
@ -503,7 +503,7 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
return switch (fetchSql.part) {
|
return switch (fetchSql.part) {
|
||||||
case SQL_QUERY -> fetchSql;
|
case SQL_QUERY -> fetchSql;
|
||||||
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName(entityClass) + " WHERE uuid = ${ref}." + dependsOnColum.column);
|
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName() + " WHERE uuid = ${ref}." + dependsOnColum.column);
|
||||||
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
|
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -521,10 +521,10 @@ public class RbacView {
|
|||||||
? aliasName
|
? aliasName
|
||||||
: uncapitalize(withoutEntitySuffix(entityClass.getSimpleName()));
|
: uncapitalize(withoutEntitySuffix(entityClass.getSimpleName()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRawTableName(final Class<?> entityClass) {
|
String getRawTableName() {
|
||||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static String withoutRvSuffix(final String tableName) {
|
public static String withoutRvSuffix(final String tableName) {
|
||||||
return tableName.substring(0, tableName.length()-"_rv".length());
|
return tableName.substring(0, tableName.length()-"_rv".length());
|
||||||
|
@ -20,7 +20,7 @@ public class RbacViewPostgresGenerator {
|
|||||||
|
|
||||||
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
||||||
rbacDef = forRbacDef;
|
rbacDef = forRbacDef;
|
||||||
liqibaseTagPrefix = rbacDef.getRootEntityAlias().entityClass().getSimpleName();
|
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableName().replace("_", "-");
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by ${generator} at ${timestamp}.
|
-- This code generated was by ${generator} at ${timestamp}.
|
||||||
|
@ -13,7 +13,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NE
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.OLD;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.OLD;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.getRawTableName;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||||
@ -38,36 +37,65 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
entityClass = rbacDef.getRootEntityAlias().entityClass();
|
entityClass = rbacDef.getRootEntityAlias().entityClass();
|
||||||
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||||
simpleEntityName = capitalize(simpleEntityVarName);
|
simpleEntityName = capitalize(simpleEntityVarName);
|
||||||
rawTableName = getRawTableName(entityClass);
|
rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateTo(final StringWriter plPgSql) {
|
void generateTo(final StringWriter plPgSql) {
|
||||||
generateHeader(plPgSql);
|
|
||||||
generateTriggerFunction(plPgSql);
|
|
||||||
generateInsertTrigger(plPgSql);
|
generateInsertTrigger(plPgSql);
|
||||||
if (hasAnyUpdatableEntityAliases()) {
|
if (hasAnyUpdatableEntityAliases()) {
|
||||||
generateUpdateTrigger(plPgSql);
|
generateUpdateTrigger(plPgSql);
|
||||||
}
|
}
|
||||||
generateFooter(plPgSql);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateHeader(final StringWriter plPgSql) {
|
private void generateHeader(final StringWriter plPgSql, final String triggerType) {
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset ${liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--//
|
--changeset ${liquibaseTagPrefix}-rbac-${triggerType}-trigger:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
"""
|
""",
|
||||||
.replace("${liquibaseTagPrefix}", liquibaseTagPrefix));
|
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||||
|
with("triggerType", triggerType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateTriggerFunction(final StringWriter plPgSql) {
|
private void generateInsertTriggerFunction(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
/*
|
/*
|
||||||
A Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
A Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace procedure buildRbacSystemFor${simpleEntityName}(
|
create or replace procedure buildRbacSystemFor${simpleEntityName}(
|
||||||
TG_OP text,
|
NEW ${rawTableName}
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
"""
|
||||||
|
.replace("${simpleEntityName}", simpleEntityName)
|
||||||
|
.replace("${rawTableName}", rawTableName));
|
||||||
|
|
||||||
|
plPgSql.chopEmptyLines();
|
||||||
|
plPgSql.indented(() -> {
|
||||||
|
referencedEntityAliases()
|
||||||
|
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"));
|
||||||
|
});
|
||||||
|
|
||||||
|
plPgSql.writeLn();
|
||||||
|
plPgSql.writeLn("begin");
|
||||||
|
plPgSql.indented(() -> {
|
||||||
|
generateCreateRolesAndGrantsAfterInsert(plPgSql);
|
||||||
|
plPgSql.ensureSingleEmptyLine();
|
||||||
|
});
|
||||||
|
plPgSql.writeLn("end; $$;");
|
||||||
|
plPgSql.writeLn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateUpdateTriggerFunction(final StringWriter plPgSql) {
|
||||||
|
plPgSql.writeLn("""
|
||||||
|
/*
|
||||||
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure updateRbacGrantsFor${simpleEntityName}(
|
||||||
OLD ${rawTableName},
|
OLD ${rawTableName},
|
||||||
NEW ${rawTableName}
|
NEW ${rawTableName}
|
||||||
)
|
)
|
||||||
@ -80,27 +108,17 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
plPgSql.chopEmptyLines();
|
plPgSql.chopEmptyLines();
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
|
|
||||||
referencedEntityAliases()
|
|
||||||
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";"));
|
|
||||||
|
|
||||||
updatableEntityAliases()
|
updatableEntityAliases()
|
||||||
.forEach((ea) -> plPgSql.writeLn(entityRefVar(OLD, ea) + " " + getRawTableName(ea.entityClass()) + ";"));
|
.forEach((ea) -> {
|
||||||
|
plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";");
|
||||||
|
plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
plPgSql.writeLn("begin");
|
plPgSql.writeLn("begin");
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
|
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
|
||||||
generateCreateRolesAndGrantsAfterInsert(plPgSql);
|
|
||||||
if (hasAnyUpdatableEntityAliases()) {
|
|
||||||
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
|
|
||||||
}
|
|
||||||
plPgSql.writeLn("""
|
|
||||||
else
|
|
||||||
raise exception 'invalid usage of TRIGGER';
|
|
||||||
end if;
|
|
||||||
""");
|
|
||||||
plPgSql.ensureSingleEmptyLine();
|
plPgSql.ensureSingleEmptyLine();
|
||||||
});
|
});
|
||||||
plPgSql.writeLn("end; $$;");
|
plPgSql.writeLn("end; $$;");
|
||||||
@ -117,23 +135,15 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
|
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
|
||||||
with("ref", NEW.name())));
|
with("ref", NEW.name())));
|
||||||
|
|
||||||
plPgSql.ensureSingleEmptyLine();
|
createRolesWithGrantsSql(plPgSql, OWNER);
|
||||||
plPgSql.writeLn("if TG_OP = 'INSERT' then");
|
createRolesWithGrantsSql(plPgSql, ADMIN);
|
||||||
|
createRolesWithGrantsSql(plPgSql, AGENT);
|
||||||
|
createRolesWithGrantsSql(plPgSql, TENANT);
|
||||||
|
createRolesWithGrantsSql(plPgSql, REFERRER);
|
||||||
|
|
||||||
plPgSql.indented(() -> {
|
generateGrants(plPgSql, ROLE_TO_USER);
|
||||||
|
generateGrants(plPgSql, ROLE_TO_ROLE);
|
||||||
plPgSql.chopEmptyLines();
|
generateGrants(plPgSql, PERM_TO_ROLE);
|
||||||
createRolesWithGrantsSql(plPgSql, OWNER);
|
|
||||||
createRolesWithGrantsSql(plPgSql, ADMIN);
|
|
||||||
createRolesWithGrantsSql(plPgSql, AGENT);
|
|
||||||
createRolesWithGrantsSql(plPgSql, TENANT);
|
|
||||||
createRolesWithGrantsSql(plPgSql, REFERRER);
|
|
||||||
|
|
||||||
generateGrants(plPgSql, ROLE_TO_USER);
|
|
||||||
generateGrants(plPgSql, ROLE_TO_ROLE);
|
|
||||||
generateGrants(plPgSql, PERM_TO_ROLE);
|
|
||||||
plPgSql.ensureSingleEmptyLine();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<RbacView.EntityAlias> referencedEntityAliases() {
|
private Stream<RbacView.EntityAlias> referencedEntityAliases() {
|
||||||
@ -151,29 +161,30 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
|
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
|
||||||
plPgSql.ensureSingleEmptyLine();
|
plPgSql.ensureSingleEmptyLine();
|
||||||
plPgSql.writeLn("elsif TG_OP = 'UPDATE' then");
|
|
||||||
|
|
||||||
plPgSql.indented(() -> {
|
updatableEntityAliases()
|
||||||
|
.forEach((ea) -> {
|
||||||
updatableEntityAliases()
|
plPgSql.writeLn(
|
||||||
.forEach((ea) -> plPgSql.writeLn(
|
|
||||||
ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";",
|
ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";",
|
||||||
with("ref", OLD.name())));
|
with("ref", OLD.name()));
|
||||||
|
plPgSql.writeLn(
|
||||||
|
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
|
||||||
|
with("ref", NEW.name()));
|
||||||
|
});
|
||||||
|
|
||||||
updatableEntityAliases()
|
updatableEntityAliases()
|
||||||
.map(RbacView.EntityAlias::dependsOnColum)
|
.map(RbacView.EntityAlias::dependsOnColum)
|
||||||
.map(c -> c.column)
|
.map(c -> c.column)
|
||||||
.sorted()
|
.sorted()
|
||||||
.distinct()
|
.distinct()
|
||||||
.forEach(columnName -> {
|
.forEach(columnName -> {
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then");
|
plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then");
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
updateGrantsDependingOn(plPgSql, columnName);
|
updateGrantsDependingOn(plPgSql, columnName);
|
||||||
});
|
|
||||||
plPgSql.writeLn("end if;");
|
|
||||||
});
|
});
|
||||||
});
|
plPgSql.writeLn("end if;");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUpdatable(final RbacView.Column c) {
|
private boolean isUpdatable(final RbacView.Column c) {
|
||||||
@ -386,6 +397,10 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateInsertTrigger(final StringWriter plPgSql) {
|
private void generateInsertTrigger(final StringWriter plPgSql) {
|
||||||
|
|
||||||
|
generateHeader(plPgSql, "insert");
|
||||||
|
generateInsertTriggerFunction(plPgSql);
|
||||||
|
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
/*
|
/*
|
||||||
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
|
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
|
||||||
@ -405,14 +420,19 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
on ${rawTableName}
|
on ${rawTableName}
|
||||||
for each row
|
for each row
|
||||||
execute procedure insertTriggerFor${simpleEntityName}_tf();
|
execute procedure insertTriggerFor${simpleEntityName}_tf();
|
||||||
--//
|
|
||||||
"""
|
"""
|
||||||
.replace("${simpleEntityName}", simpleEntityName)
|
.replace("${simpleEntityName}", simpleEntityName)
|
||||||
.replace("${rawTableName}", rawTableName)
|
.replace("${rawTableName}", rawTableName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
generateFooter(plPgSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateUpdateTrigger(final StringWriter plPgSql) {
|
private void generateUpdateTrigger(final StringWriter plPgSql) {
|
||||||
|
|
||||||
|
generateHeader(plPgSql, "update");
|
||||||
|
generateUpdateTriggerFunction(plPgSql);
|
||||||
|
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
/*
|
/*
|
||||||
AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row.
|
AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row.
|
||||||
@ -423,7 +443,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
begin
|
begin
|
||||||
call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW);
|
call buildRbacSystemFor${simpleEntityName}(NEW);
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
@ -437,10 +457,12 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
.replace("${simpleEntityName}", simpleEntityName)
|
.replace("${simpleEntityName}", simpleEntityName)
|
||||||
.replace("${rawTableName}", rawTableName)
|
.replace("${rawTableName}", rawTableName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
generateFooter(plPgSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void generateFooter(final StringWriter plPgSql) {
|
private static void generateFooter(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn("--//");
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +90,18 @@ public class StringWriter {
|
|||||||
this.varDefs = varDefs;
|
this.varDefs = varDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
String apply(final String text) {
|
String apply(final String textToAppend) {
|
||||||
this.text = text;
|
try {
|
||||||
stream(varDefs).forEach(varDef -> {
|
text = textToAppend;
|
||||||
final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE);
|
stream(varDefs).forEach(varDef -> {
|
||||||
final var matcher = pattern.matcher(text);
|
final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE);
|
||||||
this.text = matcher.replaceAll(varDef.value());
|
final var matcher = pattern.matcher(text);
|
||||||
});
|
text = matcher.replaceAll(varDef.value());
|
||||||
return this.text;
|
});
|
||||||
|
return text;
|
||||||
|
} catch (Exception exc) {
|
||||||
|
throw exc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user