RBAC Diagram+PostgreSQL Generator #21
@ -1,9 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -37,10 +38,13 @@ public String toString() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
final var rbac = HsOfficeRelationshipEntity.rbac();
|
||||||
|
final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql");
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
Paths.get("doc", "hsOfficeBankAccount.sql"),
|
outputPath,
|
||||||
new RbacViewPostgresGenerator(HsOfficeBankAccountEntity.rbac()).toString(),
|
new RbacViewPostgresGenerator(rbac).toString(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
|
||||||
|
System.out.println(outputPath.toAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,8 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
|
||||||
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.*;
|
||||||
@ -26,13 +25,15 @@ 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());
|
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
|
||||||
|
.filter(RbacView.RbacGrantDefinition::isToCreate)
|
||||||
|
.collect(toSet()));
|
||||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
|
|
||||||
entityClass = rbacDef.getRootEntityAlias().entityClass();
|
entityClass = rbacDef.getRootEntityAlias().entityClass();
|
||||||
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||||
simpleEntityName = capitalize(simpleEntityVarName);
|
simpleEntityName = capitalize(simpleEntityVarName);
|
||||||
rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
rawTableName = getRawTableName(entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateTo(final StringWriter plPgSql) {
|
void generateTo(final StringWriter plPgSql) {
|
||||||
@ -45,10 +46,10 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
private void generateHeader(final StringWriter plPgSql) {
|
private void generateHeader(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn("""
|
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 StringWriter plPgSql) {
|
private void generateTriggerFunction(final StringWriter plPgSql) {
|
||||||
@ -57,28 +58,53 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace function createRbacRolesFor%{simpleEntityName}()
|
create or replace function createRbacRolesFor${simpleEntityName}()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
|
declare
|
||||||
|
"""
|
||||||
|
.replace("${simpleEntityName}", simpleEntityName));
|
||||||
|
|
||||||
|
plPgSql.indented(() -> {
|
||||||
|
rbacDef.getEntityAliases().values().stream()
|
||||||
|
.filter((ea) -> !rbacDef.isRootEntityAlias(ea))
|
||||||
|
.filter((ea) -> ea.fetchSql() != null)
|
||||||
|
.forEach((ea) -> {
|
||||||
|
plPgSql.writeLn( entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
plPgSql.writeLn("""
|
||||||
begin
|
begin
|
||||||
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;
|
||||||
"""
|
""");
|
||||||
.replace("%{simpleEntityName}", simpleEntityName));
|
|
||||||
|
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
|
|
||||||
|
plPgSql.writeLn();
|
||||||
|
rbacDef.getEntityAliases().values().stream()
|
||||||
|
.filter((ea) -> !rbacDef.isRootEntityAlias(ea))
|
||||||
|
.filter((ea) -> ea.fetchSql() != null)
|
||||||
|
.forEach((ea) -> {
|
||||||
|
plPgSql.writeLn( ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";");
|
||||||
|
});
|
||||||
|
|
||||||
createRolesWithGrantsSql(plPgSql, OWNER);
|
createRolesWithGrantsSql(plPgSql, OWNER);
|
||||||
createRolesWithGrantsSql(plPgSql, ADMIN);
|
createRolesWithGrantsSql(plPgSql, ADMIN);
|
||||||
createRolesWithGrantsSql(plPgSql, AGENT);
|
createRolesWithGrantsSql(plPgSql, AGENT);
|
||||||
createRolesWithGrantsSql(plPgSql, TENANT);
|
createRolesWithGrantsSql(plPgSql, TENANT);
|
||||||
createRolesWithGrantsSql(plPgSql, REFERRER);
|
createRolesWithGrantsSql(plPgSql, REFERRER);
|
||||||
|
|
||||||
if (!rbacGrants.isEmpty()) {
|
plPgSql.writeLn();
|
||||||
throw new IllegalStateException("unprocessed grants: " + rbacGrants);
|
rbacGrants
|
||||||
// rbacGrants.forEach(g -> plPgSql.writeLn("-- unprocessed grant: " + g));
|
.forEach(g -> plPgSql.writeLn(
|
||||||
}
|
"call grantRoleToRole(${subRoleRef}, ${superRoleRef});"
|
||||||
|
.replace("${subRoleRef}", roleRef(NEW, g.getSubRoleDef()) )
|
||||||
|
.replace("${superRoleRef}", roleRef(NEW, g.getSuperRoleDef()) ))
|
||||||
|
);
|
||||||
|
|
||||||
plPgSql.writeLn("return NEW;");
|
plPgSql.writeLn("return NEW;");
|
||||||
});
|
});
|
||||||
@ -87,6 +113,24 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getRawTableName(final Class<?> entityClass) {
|
||||||
|
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
|
||||||
|
if ( roleDef.getEntityAlias().isGlobal()) {
|
||||||
|
return "globalAdmin()";
|
||||||
|
}
|
||||||
|
final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias());
|
||||||
|
return roleDef.getEntityAlias().simpleName() + capitalize(roleDef.getRole().roleName())
|
||||||
|
+ "(" + entityRefVar + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String entityRefVar(
|
||||||
|
final PostgresTriggerReference rootRefVar,
|
||||||
|
final RbacView.EntityAlias entityAlias) {
|
||||||
|
return rootRefVar.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||||
|
}
|
||||||
|
|
||||||
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
|
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
|
||||||
|
|
||||||
@ -100,9 +144,9 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
plPgSql.writeLn("perform createRoleWithGrants(");
|
plPgSql.writeLn("perform createRoleWithGrants(");
|
||||||
plPgSql.indented( () -> {
|
plPgSql.indented( () -> {
|
||||||
plPgSql.writeLn("%{simpleVarName)%{roleSuffix}(NEW),"
|
plPgSql.writeLn("${simpleVarName)${roleSuffix}(NEW),"
|
||||||
.replace("%{simpleVarName)", simpleEntityVarName)
|
.replace("${simpleVarName)", 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()) {
|
||||||
@ -161,21 +205,21 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef )
|
.filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef )
|
||||||
.collect(Collectors.toSet());
|
.collect(toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef )
|
.filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef )
|
||||||
.collect(Collectors.toSet());
|
.collect(toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef()==roleDef )
|
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef()==roleDef )
|
||||||
.collect(Collectors.toSet());
|
.collect(toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
||||||
@ -183,24 +227,24 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef()==roleDef )
|
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef()==roleDef )
|
||||||
.filter(g -> g.getSubRoleDef().getEntityAlias() != entityAlias)
|
.filter(g -> g.getSubRoleDef().getEntityAlias() != entityAlias)
|
||||||
.collect(Collectors.toSet());
|
.collect(toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generageInsertTrigger(final StringWriter plPgSql) {
|
private void generageInsertTrigger(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn("""
|
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}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create trigger createRbacRolesFor%{simpleEntityName}_Trigger
|
create trigger createRbacRolesFor${simpleEntityName}_Trigger
|
||||||
after insert
|
after insert
|
||||||
on %{rawTableName}
|
on ${rawTableName}
|
||||||
for each row
|
for each row
|
||||||
execute procedure createRbacRolesFor%{simpleEntityName}();
|
execute procedure createRbacRolesFor${simpleEntityName}();
|
||||||
--//
|
--//
|
||||||
"""
|
"""
|
||||||
.replace("%{simpleEntityName}", simpleEntityName)
|
.replace("${simpleEntityName}", simpleEntityName)
|
||||||
.replace("%{rawTableName}", rawTableName)
|
.replace("${rawTableName}", rawTableName)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user