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
7 changed files with 75 additions and 21 deletions
Showing only changes of commit 12010b4dae - Show all commits

View File

@ -8,6 +8,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
@ -107,7 +108,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("debitor", HsOfficeDebitorEntity.class) return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(RbacView.SQL.query(""" .withIdentityView(SQL.query("""
SELECT debitor.uuid, SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber 'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner FROM hs_office_partner partner
@ -117,7 +118,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'ACCOUNTING' ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'ACCOUNTING'
WHERE debitorRel.uuid = debitor.debitorRelUuid) WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00') || to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor; from hs_office_debitor as debitor
""")) """))
.withUpdatableColumns( .withUpdatableColumns(
"debitorRel", "debitorRel",
@ -131,11 +132,11 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
"defaultPrefix" /* TODO: do we want that updatable? */) "defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop() .createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop()
.importProxyEntity("debitorRel", HsOfficeRelationshipEntity.class, .importRootEntityAliasProxy("debitorRel", HsOfficeRelationshipEntity.class,
fetchedBySql(""" fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid; WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid
"""), """),
dependsOnColumn("debitorRelUuid")) dependsOnColumn("debitorRelUuid"))
.createPermission(ALL).grantedTo("debitorRel", OWNER).pop() .createPermission(ALL).grantedTo("debitorRel", OWNER).pop()
@ -146,7 +147,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
fetchedBySql(""" fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid; WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid
"""), """),
dependsOnColumn("bankAccountUuid")) dependsOnColumn("bankAccountUuid"))
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
@ -156,7 +157,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
fetchedBySql(""" fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS partnerRel FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid; WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid
"""), """),
dependsOnColumn("debitorRelUuid")) dependsOnColumn("debitorRelUuid"))
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)

View File

@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
@ -15,6 +16,12 @@ import jakarta.persistence.*;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.VIEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -68,4 +75,27 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
public String toShortString() { public String toShortString() {
return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>"); return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>");
} }
public static RbacView rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(RbacView.SQL.query("""
SELECT partner.partnerNumber
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personuuid)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactuuid)
FROM hs_office_partner AD partner
$idName$)
"""))
.withUpdatableColumns(
"partnerRoleUuid",
"personUuid",
"contactUuid")
.createPermission(custom("new-partner")).grantedTo("global", ADMIN).pop()
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class,
fetchedBySql("SELECT * FROM hs_office_relationship AS r WHERE r.uuid = ${ref}.partnerRoleUuid"),
dependsOnColumn("partnerRelUuid"))
.createPermission(ALL).grantedTo("partnerRel", ADMIN).pop()
.createPermission(EDIT).grantedTo("partnerRel", AGENT).pop()
.createPermission(VIEW).grantedTo("partnerRel", TENANT).pop();
}
} }

View File

@ -97,10 +97,10 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR); with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
with.incomingSuperRole("anchorPerson", ADMIN);
with.permission(ALL); with.permission(ALL);
}) })
.createSubRole(ADMIN, (with) -> { .createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN);
with.permission(EDIT); with.permission(EDIT);
}) })
.createSubRole(AGENT, (with) -> { .createSubRole(AGENT, (with) -> {

View File

@ -101,7 +101,7 @@ public class RbacView {
return this; return this;
} }
public <EC extends RbacObject> RbacView importProxyEntity( public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
if ( rootEntityAliasProxy != null ) { if ( rootEntityAliasProxy != null ) {
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);

View File

@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -162,6 +163,7 @@ public class RbacViewMermaidFlowchart {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).generateToMarkdownFile(); new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).generateToMarkdownFile(); new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficePartnerEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).generateToMarkdownFile(); new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).generateToMarkdownFile();
} }
} }

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import java.io.IOException; import java.io.IOException;
@ -26,19 +28,15 @@ public class RbacViewPostgresGenerator {
""" """
.replace("%{timestamp}", LocalDateTime.now().toString())); .replace("%{timestamp}", LocalDateTime.now().toString()));
// generateSqlForRelatedRbacObject();
new RolesGrantsAndPermissionsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql); new RolesGrantsAndPermissionsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
} }
@Override @Override
public String toString() { public String toString() {
return plPgSql.toString(); return plPgSql.toString();
} }
public static void main(String[] args) throws IOException { private static void generatePostgres(final RbacView rbac) throws IOException {
final var rbac = HsOfficeRelationshipEntity.rbac();
final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql"); final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql");
Files.writeString( Files.writeString(
outputPath, outputPath,
@ -47,4 +45,10 @@ public class RbacViewPostgresGenerator {
System.out.println(outputPath.toAbsolutePath()); System.out.println(outputPath.toAbsolutePath());
} }
public static void main(String[] args) throws IOException {
generatePostgres(HsOfficeRelationshipEntity.rbac());
generatePostgres(HsOfficePartnerEntity.rbac());
generatePostgres(HsOfficeDebitorEntity.rbac());
}
} }

View File

@ -84,12 +84,11 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.indented(() -> { plPgSql.indented(() -> {
plPgSql.writeLn();
rbacDef.getEntityAliases().values().stream() rbacDef.getEntityAliases().values().stream()
.filter((ea) -> !rbacDef.isRootEntityAlias(ea)) .filter((ea) -> !rbacDef.isRootEntityAlias(ea))
.filter((ea) -> ea.fetchSql() != null) .filter((ea) -> ea.fetchSql() != null)
.forEach((ea) -> { .forEach((ea) -> {
plPgSql.writeLn( ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";"); plPgSql.writeLn( ea.fetchSql().sql.replace("${ref}", NEW.name()) + " into " + entityRefVar(NEW, ea) + ";");
}); });
createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, OWNER);
@ -99,12 +98,7 @@ class RolesGrantsAndPermissionsGenerator {
createRolesWithGrantsSql(plPgSql, REFERRER); createRolesWithGrantsSql(plPgSql, REFERRER);
plPgSql.writeLn(); plPgSql.writeLn();
rbacGrants rbacGrants.forEach(g -> plPgSql.writeLn(generateGrant(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;");
}); });
@ -113,11 +107,34 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn(); plPgSql.writeLn();
} }
private String generateGrant(RbacView.RbacGrantDefinition grantDef) {
return switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}));"
.replace("${subRoleRef}", roleRef(NEW, grantDef.getSubRoleDef()) )
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()) );
case PERM_TO_ROLE -> "call grantPermissionsToRole(${permRef}, ${superRoleRef}));"
.replace("${permRef}", permRef(NEW, grantDef.getPermDef()) )
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()) );
};
}
private String permRef(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
return "createPermissions(${entityRef}.uuid, array ['${perm}']"
.replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias)
? ref.name()
: "not implemented yet" ) // TODO
.replace("${perm}", permDef.permission.permission());
}
private String getRawTableName(final Class<?> entityClass) { private String getRawTableName(final Class<?> entityClass) {
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
} }
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) { private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
if ( roleDef == null ) {
System.out.println("null");
}
if ( roleDef.getEntityAlias().isGlobal()) { if ( roleDef.getEntityAlias().isGlobal()) {
return "globalAdmin()"; return "globalAdmin()";
} }