RBAC Diagram+PostgreSQL Generator #21
@ -58,7 +58,7 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
||||
.withIdentityView(SQL.query("target.iban || ':' || target.holder"))
|
||||
.withIdentityView(SQL.projection("iban || ':' || holder"))
|
||||
.withUpdatableColumns("holder", "iban", "bic")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
|
@ -5,6 +5,7 @@ import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
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.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
@ -61,7 +62,7 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("contact", HsOfficeContactEntity.class)
|
||||
.withIdentityView(RbacView.SQL.query("target.label"))
|
||||
.withIdentityView(SQL.projection("label"))
|
||||
.withUpdatableColumns("label", "postalAddress", "emailAddresses", "phoneNumbers")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
|
@ -144,22 +144,22 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
|
||||
.createPermission(VIEW).grantedTo("debitorRel", TENANT)
|
||||
|
||||
.importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class,
|
||||
fetchedBySql("""
|
||||
dependsOnColumn("bankAccountUuid"), fetchedBySql("""
|
||||
SELECT *
|
||||
FROM hs_office_relationship AS r
|
||||
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid
|
||||
"""),
|
||||
dependsOnColumn("bankAccountUuid"))
|
||||
""")
|
||||
)
|
||||
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
|
||||
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
|
||||
|
||||
.importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class,
|
||||
fetchedBySql("""
|
||||
dependsOnColumn("debitorRelUuid"), fetchedBySql("""
|
||||
SELECT *
|
||||
FROM hs_office_relationship AS partnerRel
|
||||
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid
|
||||
"""),
|
||||
dependsOnColumn("debitorRelUuid"))
|
||||
""")
|
||||
)
|
||||
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
|
||||
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
|
||||
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
|
||||
|
@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
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.Stringifyable;
|
||||
|
||||
@ -69,7 +70,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
||||
.withIdentityView(RbacView.SQL.query("""
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT partner_iv.idName || '-details'
|
||||
FROM hs_office_partner_details AS partnerDetails
|
||||
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
|
||||
|
@ -7,8 +7,7 @@ import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewMermaidFlowchart;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewPostgresGenerator;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
@ -81,11 +80,11 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("partner", HsOfficePartnerEntity.class)
|
||||
.withIdentityView(RbacView.SQL.query("""
|
||||
.withIdentityView(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
|
||||
|| ':' || (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 AS partner
|
||||
"""))
|
||||
.withUpdatableColumns(
|
||||
"partnerRoleUuid",
|
||||
@ -109,9 +108,6 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
final RbacView rbac = HsOfficePartnerEntity.rbac();
|
||||
new RbacViewMermaidFlowchart(rbac).generateToMarkdownFile();
|
||||
new RbacViewPostgresGenerator(rbac).generateToChangeLog("233-hs-office-partner-rbac.sql");
|
||||
HsOfficePartnerEntity.rbac().generateWithBaseFileName("233-hs-office-partner-rbac");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.query;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.projection;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@ -66,7 +66,7 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("person", HsOfficePersonEntity.class)
|
||||
.withIdentityView(query("concat(target.tradeName, target.familyName, target.givenName)"))
|
||||
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
|
||||
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.permission(ALL);
|
||||
|
@ -79,21 +79,21 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("relationship", HsOfficeRelationshipEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
|
||||
.withIdentityView(SQL.projection("""
|
||||
(select idName from hs_office_person_iv p where p.uuid = relAnchorUuid)
|
||||
|| '-with-' || target.relType || '-'
|
||||
|| (select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)
|
||||
|| (select idName from hs_office_person_iv p where p.uuid = relHolderUuid)
|
||||
"""))
|
||||
.withUpdatableColumns("contactUuid")
|
||||
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
|
||||
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid"),
|
||||
dependsOnColumn("relAnchorUuid"))
|
||||
dependsOnColumn("relAnchorUuid"), fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid")
|
||||
)
|
||||
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
|
||||
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid"),
|
||||
dependsOnColumn("relHolderUuid"))
|
||||
dependsOnColumn("relHolderUuid"), fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid")
|
||||
)
|
||||
.importEntityAlias("contact", HsOfficeContactEntity.class,
|
||||
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
|
||||
dependsOnColumn("contactUuid"))
|
||||
dependsOnColumn("contactUuid"), fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid")
|
||||
)
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||
|
@ -6,16 +6,26 @@ import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -84,4 +94,33 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
|
||||
return reference;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
||||
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
|
||||
.withUpdatableColumns("reference", "agreement", "validity")
|
||||
|
||||
.importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid"))
|
||||
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid"))
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||
with.outgoingSubRole("bankAccount", REFERRER);
|
||||
with.permission(ALL);
|
||||
})
|
||||
.createSubRole(ADMIN, (with) -> {
|
||||
with.permission(EDIT);
|
||||
})
|
||||
.createSubRole(AGENT, (with) -> {
|
||||
with.outgoingSubRole("debitorRel", AGENT);
|
||||
})
|
||||
.createSubRole(REFERRER, (with) -> {
|
||||
with.incomingSuperRole("debitorRel", AGENT);
|
||||
with.permission(VIEW);
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
HsOfficeSepaMandateEntity.rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac");
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -7,17 +8,21 @@ import lombok.Getter;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
|
||||
@Getter
|
||||
public class RbacView {
|
||||
|
||||
public static final String GLOBAL = "global";
|
||||
public static final String OUTPUT_BASEDIR = "src/main/resources/db/changelog";
|
||||
|
||||
|
||||
private final EntityAlias rootEntityAlias;
|
||||
|
||||
@ -123,11 +128,18 @@ public class RbacView {
|
||||
|
||||
public RbacView importEntityAlias(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final SQL fetchSql, final Column dependsOnColum) {
|
||||
final Column dependsOnColum, final SQL fetchSql) {
|
||||
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView importEntityAlias(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final Column dependsOnColum) {
|
||||
importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private EntityAlias importEntityAliasImpl(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass,
|
||||
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) {
|
||||
@ -200,6 +212,11 @@ public class RbacView {
|
||||
return entityAlias == rootEntityAliasProxy;
|
||||
}
|
||||
|
||||
public void generateWithBaseFileName(final String baseFileName) {
|
||||
new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.md"));
|
||||
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.sql"));
|
||||
}
|
||||
|
||||
public class RbacGrantBuilder {
|
||||
|
||||
private final RbacRoleDefinition superRoleDef;
|
||||
@ -463,6 +480,18 @@ public class RbacView {
|
||||
return entityClass == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQL fetchSql() {
|
||||
if ( fetchSql == null ) {
|
||||
return null;
|
||||
}
|
||||
return switch (fetchSql.part) {
|
||||
case SQL_QUERY -> fetchSql;
|
||||
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName(entityClass) + " WHERE uuid = ${ref}." + dependsOnColum.column);
|
||||
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
|
||||
};
|
||||
}
|
||||
|
||||
private String withoutEntitySuffix(final String simpleEntityName) {
|
||||
return simpleEntityName.substring(0, simpleEntityName.length()-"Entity".length());
|
||||
}
|
||||
@ -474,6 +503,13 @@ public class RbacView {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRawTableName(final Class<?> entityClass) {
|
||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||
}
|
||||
public static String withoutRvSuffix(final String tableName) {
|
||||
return tableName.substring(0, tableName.length()-"_rv".length());
|
||||
}
|
||||
|
||||
public record Role(String roleName) {
|
||||
public static final Role OWNER = new Role("owner");
|
||||
public static final Role ADMIN = new Role("admin");
|
||||
@ -510,7 +546,7 @@ public class RbacView {
|
||||
public static class SQL {
|
||||
|
||||
/**
|
||||
* DSL methid to specify an SQL SELECT expression which fetches the related entity,
|
||||
* DSL method to specify an SQL SELECT expression which fetches the related entity,
|
||||
* using the reference `${ref}` of the root entity.
|
||||
* `${ref}` is going to be replaced by either `NEW` or `OLD` of the trigger function.
|
||||
* `into ...` will be added with a variable name prefixed with either `new` or `old`.
|
||||
@ -520,7 +556,18 @@ public class RbacView {
|
||||
*/
|
||||
public static SQL fetchedBySql(final String sql) {
|
||||
validateExpression(sql);
|
||||
return new SQL(sql);
|
||||
return new SQL(sql, Part.SQL_QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* DSL method to specify that a related entity is to be fetched by a simple SELECT statement
|
||||
* using the raw table from the @Table statement of the entity to fetch
|
||||
* and the dependent column of the root entity.
|
||||
*
|
||||
* @return the wrapped SQL definition object
|
||||
*/
|
||||
public static SQL autoFetched() {
|
||||
return new SQL(null, Part.AUTO_FETCH);
|
||||
}
|
||||
|
||||
/** Generic DSL method to specify an SQL SELECT expression.
|
||||
@ -530,13 +577,39 @@ public class RbacView {
|
||||
*/
|
||||
public static SQL query(final String sql) {
|
||||
validateExpression(sql);
|
||||
return new SQL(sql);
|
||||
return new SQL(sql, Part.SQL_QUERY);
|
||||
}
|
||||
|
||||
public final String sql;
|
||||
/** Generic DSL method to specify an SQL SELECT expression by just the projection part.
|
||||
*
|
||||
* @param projection an SQL SELECT expression, the list of columns after 'SELECT'
|
||||
* @return the wrapped SQL projection
|
||||
*/
|
||||
public static SQL projection(final String projection) {
|
||||
validateProjection(projection);
|
||||
return new SQL(projection, Part.SQL_PROJECTION);
|
||||
}
|
||||
|
||||
private SQL(final String sql) {
|
||||
enum Part {
|
||||
SQL_QUERY,
|
||||
AUTO_FETCH, SQL_PROJECTION
|
||||
}
|
||||
|
||||
final String sql;
|
||||
final Part part;
|
||||
|
||||
private SQL(final String sql, final Part part) {
|
||||
this.sql = sql;
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
private static void validateProjection(final String projection) {
|
||||
if (projection.toUpperCase().matches("[ \t]*$SELECT[ \t]")) {
|
||||
throw new IllegalArgumentException("SQL projection must not start with 'SELECT': " + projection);
|
||||
}
|
||||
if (projection.matches(";[ \t]*$")) {
|
||||
throw new IllegalArgumentException("SQL projection must not end with ';': " + projection);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateExpression(final String sql) {
|
||||
|
@ -1,13 +1,8 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ -46,10 +41,11 @@ public class RbacViewMermaidFlowchart {
|
||||
flowchart.writeLn("""
|
||||
subgraph %{aliasName}["`**%{aliasName}**`"]
|
||||
direction TB
|
||||
style %{aliasName} fill:%{color},stroke:darkblue,stroke-width:8px
|
||||
style %{aliasName} fill:%{fillColor},stroke:%{strokeColor},stroke-width:8px
|
||||
"""
|
||||
.replace("%{aliasName}", entity.aliasName())
|
||||
.replace("%{color}", color ));
|
||||
.replace("%{fillColor}", color )
|
||||
.replace("%{strokeColor}", HOSTSHARING_DARK_BLUE ));
|
||||
|
||||
flowchart.indented( () -> {
|
||||
rbacDef.getEntityAliases().values().stream()
|
||||
@ -83,9 +79,9 @@ public class RbacViewMermaidFlowchart {
|
||||
flowchart.ensureEmptyLine();
|
||||
flowchart.writeLn("subgraph " + name + "[ ]\n");
|
||||
flowchart.indented(() -> {
|
||||
flowchart.writeLn("style %{aliasName} fill: %{color}"
|
||||
flowchart.writeLn("style %{aliasName} fill:%{fillColor},stroke:white"
|
||||
.replace("%{aliasName}", name)
|
||||
.replace("%{color}", color));
|
||||
.replace("%{fillColor}", color));
|
||||
flowchart.writeLn();
|
||||
flowchart.writeLn(content);
|
||||
});
|
||||
@ -147,8 +143,8 @@ public class RbacViewMermaidFlowchart {
|
||||
return flowchart.toString();
|
||||
}
|
||||
|
||||
public void generateToMarkdownFile() throws IOException {
|
||||
final Path path = Paths.get("doc", rbacDef.getRootEntityAlias().simpleName() + ".md");
|
||||
@SneakyThrows
|
||||
public void generateToMarkdownFile(final Path path) {
|
||||
Files.writeString(
|
||||
path,
|
||||
"""
|
||||
@ -164,12 +160,4 @@ public class RbacViewMermaidFlowchart {
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
System.out.println("Markdown-File: " + path.toAbsolutePath());
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficePartnerEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficePartnerDetailsEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).generateToMarkdownFile();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
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 lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -37,7 +34,8 @@ public class RbacViewPostgresGenerator {
|
||||
return plPgSql.toString();
|
||||
}
|
||||
|
||||
private static void generatePostgres(final RbacView rbac) throws IOException {
|
||||
@SneakyThrows
|
||||
private static void generatePostgres(final RbacView rbac) {
|
||||
final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql");
|
||||
Files.writeString(
|
||||
outputPath,
|
||||
@ -47,8 +45,8 @@ public class RbacViewPostgresGenerator {
|
||||
System.out.println(outputPath.toAbsolutePath());
|
||||
}
|
||||
|
||||
public void generateToChangeLog(final String fileName) throws IOException {
|
||||
final Path outputPath = Path.of("src/main/resources/db/changelog", fileName);
|
||||
@SneakyThrows
|
||||
public void generateToChangeLog(final Path outputPath) {
|
||||
Files.writeString(
|
||||
outputPath,
|
||||
toString(),
|
||||
@ -56,10 +54,4 @@ public class RbacViewPostgresGenerator {
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
System.out.println(outputPath.toAbsolutePath());
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
generatePostgres(HsOfficeRelationshipEntity.rbac());
|
||||
generatePostgres(HsOfficePartnerEntity.rbac());
|
||||
generatePostgres(HsOfficeDebitorEntity.rbac());
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,15 @@ package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
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.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.getRawTableName;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
|
||||
@ -141,10 +142,6 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
return ref.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||
}
|
||||
|
||||
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 == null ) {
|
||||
System.out.println("null");
|
||||
@ -179,51 +176,13 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
.replace("${simpleVarName)", simpleEntityVarName)
|
||||
.replace("${roleSuffix}", capitalize(role.roleName())));
|
||||
|
||||
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!permissionGrantsForRole.isEmpty()) {
|
||||
final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getPermDef)
|
||||
.map(RbacPermissionDefinition::getPermission)
|
||||
.map(RbacView.Permission::permission)
|
||||
.map(p -> "'" + p + "'")
|
||||
.collect(joining(", "));
|
||||
plPgSql.indented( () ->
|
||||
plPgSql.writeLn("permissions => array[" + permissionsForRoleInPlPgSql + "],\n"));
|
||||
rbacGrants.removeAll(permissionGrantsForRole);
|
||||
}
|
||||
generatePermissionsForRole(plPgSql, role);
|
||||
|
||||
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!grantsToUsers.isEmpty()) {
|
||||
final var grantsToUsersPlPgSql = grantsToUsers.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getUserDef)
|
||||
.map(this::toPlPgSqlReference)
|
||||
.collect(joining(", "));
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("userUuids => array[" + grantsToUsersPlPgSql + "],\n"));
|
||||
rbacGrants.removeAll(grantsToUsers);
|
||||
}
|
||||
generateUserGrantsForRole(plPgSql, role);
|
||||
|
||||
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!incomingGrants.isEmpty()) {
|
||||
final var incomingGrantsInPlPgSql = incomingGrants.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
||||
.map(r -> toPlPgSqlReference(NEW, r))
|
||||
.collect(joining(", "));
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("incomingSuperRoles => array[" + incomingGrantsInPlPgSql + "],\n"));
|
||||
rbacGrants.removeAll(incomingGrants);
|
||||
}
|
||||
generateIncomingSuperRolesForRole(plPgSql, role);
|
||||
|
||||
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!outgoingGrants.isEmpty()) {
|
||||
final var outgoingGrantsInPlPgSql = outgoingGrants.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
||||
.map(r -> toPlPgSqlReference(NEW, r))
|
||||
.collect(joining(", "));
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n"));
|
||||
rbacGrants.removeAll(outgoingGrants);
|
||||
}
|
||||
generateOutgoingSubRolesForRole(plPgSql, role);
|
||||
|
||||
plPgSql.chopTail(",\n");
|
||||
plPgSql.writeLn();
|
||||
@ -232,6 +191,66 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn(");");
|
||||
}
|
||||
|
||||
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!grantsToUsers.isEmpty()) {
|
||||
final var arrayElements = grantsToUsers.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getUserDef)
|
||||
.map(this::toPlPgSqlReference)
|
||||
.toList();
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("userUuids => array[" + joinArrayElements(arrayElements, 2) + "],\n"));
|
||||
rbacGrants.removeAll(grantsToUsers);
|
||||
}
|
||||
}
|
||||
|
||||
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!permissionGrantsForRole.isEmpty()) {
|
||||
final var arrayElements = permissionGrantsForRole.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getPermDef)
|
||||
.map(RbacPermissionDefinition::getPermission)
|
||||
.map(RbacView.Permission::permission)
|
||||
.map(p -> "'" + p + "'")
|
||||
.toList();
|
||||
plPgSql.indented( () ->
|
||||
plPgSql.writeLn("permissions => array[" + joinArrayElements(arrayElements, 3) + "],\n"));
|
||||
rbacGrants.removeAll(permissionGrantsForRole);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!incomingGrants.isEmpty()) {
|
||||
final var arraElements = incomingGrants.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
||||
.map(r -> toPlPgSqlReference(NEW, r))
|
||||
.toList();
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arraElements, 1) + "],\n"));
|
||||
rbacGrants.removeAll(incomingGrants);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!outgoingGrants.isEmpty()) {
|
||||
final var arrayElements = outgoingGrants.stream()
|
||||
.map(RbacView.RbacGrantDefinition::getSubRoleDef)
|
||||
.map(r -> toPlPgSqlReference(NEW, r))
|
||||
.toList();
|
||||
plPgSql.indented(() ->
|
||||
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
|
||||
rbacGrants.removeAll(outgoingGrants);
|
||||
}
|
||||
}
|
||||
|
||||
private String joinArrayElements(final List<String> arrayElements, final int singleLineLimit) {
|
||||
return arrayElements.size() <= singleLineLimit
|
||||
? String.join(", ", arrayElements)
|
||||
: arrayElements.stream().collect(joining(",\n\t", "\n\t", ""));
|
||||
}
|
||||
|
||||
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
|
||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||
return rbacGrants.stream()
|
||||
@ -284,10 +303,6 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn();
|
||||
}
|
||||
|
||||
private String withoutRvSuffix(final String tableName) {
|
||||
return tableName.substring(0, tableName.length()-"_rv".length());
|
||||
}
|
||||
|
||||
private String toPlPgSqlReference(final RbacView.RbacUserReference userRef) {
|
||||
return switch (userRef.role) {
|
||||
case CREATOR -> "currentUserUuid()";
|
||||
|
@ -5,6 +5,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
@ -39,7 +40,7 @@ public class TestCustomerEntity implements RbacObject {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("customer", TestCustomerEntity.class)
|
||||
.withIdentityView(RbacView.SQL.query("target.prefix"))
|
||||
.withIdentityView(SQL.projection("prefix"))
|
||||
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
|
Loading…
Reference in New Issue
Block a user