improve RBAC definition DSL

This commit is contained in:
Michael Hoennig 2024-02-25 13:19:27 +01:00
parent b4d6930fbe
commit 5ac616e425
9 changed files with 135 additions and 122 deletions

View File

@ -16,6 +16,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
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.stringify.Stringify.stringify;
@ -56,19 +57,19 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
}
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.query("target.iban || ':' || target.holder"))
.withUpdatableColumns("holder", "iban", "bic")
.createRole(OWNER)
.withCurrentUserAsOwner()
.withPermission(ALL)
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createSubRole(REFERRER)
.withPermission(VIEW)
.pop();
// @formatter:on
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.permission(ALL);
})
.createSubRole(ADMIN, (with) -> {
with.permission(EDIT);
})
.createSubRole(REFERRER, (with) -> {
with.permission(VIEW);
});
}
}

View File

@ -14,6 +14,7 @@ import java.util.UUID;
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.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -33,7 +34,6 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
.withProp(Fields.label, HsOfficeContactEntity::getLabel)
.withProp(Fields.emailAddresses, HsOfficeContactEntity::getEmailAddresses);
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@ -60,19 +60,19 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
}
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("contact", HsOfficeContactEntity.class)
.withIdentityView(RbacView.SQL.query("target.label"))
.withUpdatableColumns("label", "postalAddress", "emailAddresses", "phoneNumbers")
.createRole(OWNER)
.withPermission(ALL)
.withCurrentUserAsOwner()
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createSubRole(REFERRER)
.withPermission(VIEW)
.pop();
// @formatter:on
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.permission(ALL);
})
.createSubRole(ADMIN, (with) -> {
with.permission(EDIT);
})
.createSubRole(REFERRER, (with) -> {
with.permission(VIEW);
});
}
}

View File

@ -4,9 +4,9 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -18,9 +18,7 @@ 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.Role.REFERRER;
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;
@ -87,7 +85,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
private String defaultPrefix;
private String getDebitorNumberString() {
if (partner == null || partner.getPartnerNumber() == null || debitorNumberSuffix == null ) {
if (partner == null || partner.getPartnerNumber() == null || debitorNumberSuffix == null) {
return null;
}
return partner.getPartnerNumber() + String.format("%02d", debitorNumberSuffix);
@ -108,7 +106,6 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
}
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(RbacView.SQL.query("""
SELECT debitor.uuid,
@ -131,7 +128,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
"vatCountryCode",
"vatBusiness",
"vatReverseCharge",
"defaultPrefix" /* TODO: do we want that updatable? */ )
"defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop()
.importProxyEntity("debitorRel", HsOfficeRelationshipEntity.class,
@ -169,6 +166,5 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
.forExampleRole("partnerPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("operationalPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("partnerRel", TENANT).wouldBeGrantedTo("partnerPerson", REFERRER);
// @formatter:on
}
}

View File

@ -14,6 +14,7 @@ import java.util.UUID;
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.rbacViewFor;
@ -64,19 +65,19 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
}
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(query("concat(target.tradeName, target.familyName, target.givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.createRole(OWNER)
.withPermission(ALL)
.withCurrentUserAsOwner()
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createSubRole(REFERRER)
.withPermission(VIEW)
.pop();
// @formatter:on
.createRole(OWNER, (with) -> {
with.permission(ALL);
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
})
.createSubRole(ADMIN, (with) -> {
with.permission(EDIT);
})
.createSubRole(REFERRER, (with) -> {
with.permission(VIEW);
});
}
}

View File

@ -3,8 +3,8 @@ package net.hostsharing.hsadminng.hs.office.relationship;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
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;
@ -16,6 +16,7 @@ import java.util.UUID;
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.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@ -77,7 +78,6 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
}
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("relationship", HsOfficeRelationshipEntity.class)
.withIdentityView(SQL.query("""
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
@ -94,23 +94,24 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
.importEntityAlias("contact", HsOfficeContactEntity.class,
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
dependsOnColumn("contactUuid"))
.createRole(OWNER)
.withCurrentUserAsOwner()
.withPermission(ALL)
.withIncomingSuperRole(GLOBAL, ADMIN)
.withIncomingSuperRole("anchorPerson", ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.incomingSuperRole("anchorPerson", ADMIN);
with.permission(ALL);
})
.createSubRole(ADMIN, (with) -> {
with.permission(EDIT);
})
.createSubRole(AGENT)
.createSubRole(TENANT)
.withPermission(VIEW)
.withIncomingSuperRole("anchorPerson", ADMIN)
.withIncomingSuperRole("holderPerson", ADMIN)
.withIncomingSuperRole("contact", ADMIN)
.withOutgoingSubRole("anchorPerson", REFERRER)
.withOutgoingSubRole("holderPerson", REFERRER)
.withOutgoingSubRole("contact", REFERRER)
.pop();
// @formatter:on
.createSubRole(TENANT, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN);
with.incomingSuperRole("holderPerson", ADMIN);
with.incomingSuperRole("contact", ADMIN);
with.outgoingSubRole("anchorPerson", REFERRER);
with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER);
with.permission(VIEW);
});
}
}

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacdef;
import java.util.function.Consumer;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
@ -37,6 +39,7 @@ public class RbacView {
private SQL identityViewSqlQuery;
private EntityAlias entityAliasProxy;
private RbacRoleDefinition previousRoleDef;
public static <E extends RbacObject> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
return new RbacView(alias, entityClass);
@ -59,8 +62,26 @@ public class RbacView {
return this;
}
public RbacRoleDefinition createRole(final Role role) {
return findRbacRole(entityAlias, role).toCreate();
public RbacView createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(entityAlias, role).toCreate();
with.accept(newRoleDef);
previousRoleDef = newRoleDef;
return this;
}
public RbacView createSubRole(final Role role) {
final RbacRoleDefinition newRoleDef = findRbacRole(entityAlias, role).toCreate();
new RbacGrantDefinition(newRoleDef, previousRoleDef).toCreate();
previousRoleDef = newRoleDef;
return this;
}
public RbacView createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(entityAlias, role).toCreate();
new RbacGrantDefinition(newRoleDef, previousRoleDef).toCreate();
with.accept(newRoleDef);
previousRoleDef = newRoleDef;
return this;
}
public RbacPermissionDefinition createPermission(final Permission permission) {
@ -143,8 +164,8 @@ public class RbacView {
}
private RbacGrantDefinition grantRoleToCurrentUser(final RbacRoleDefinition roleDefinition) {
return new RbacGrantDefinition(roleDefinition, currentUser()).toCreate();
private RbacGrantDefinition grantRoleToUser(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
return new RbacGrantDefinition(roleDefinition, user).toCreate();
}
private RbacGrantDefinition grantPermissionToRole(final RbacPermissionDefinition permDef , final RbacRoleDefinition roleDef) {
@ -325,46 +346,36 @@ public class RbacView {
return this;
}
public RbacRoleDefinition withCurrentUserAsOwner() {
addGrant(grantRoleToCurrentUser(this));
public RbacRoleDefinition owningUser(final RbacUserReference.UserRole userRole) {
addGrant(grantRoleToUser(this, findUserRef(userRole)));
return this;
}
public RbacRoleDefinition withPermission(final Permission permission) {
public RbacRoleDefinition permission(final Permission permission) {
addGrant(grantPermissionToRole( createPermission(entityAlias, permission) , this));
return this;
}
public RbacRoleDefinition withIncomingSuperRole(final String entityAlias, final Role role) {
public RbacRoleDefinition incomingSuperRole(final String entityAlias, final Role role) {
final var incomingSuperRole = findRbacRole(entityAlias, role);
addGrant(grantSubRoleToSuperRole(this, incomingSuperRole));
return this;
}
public RbacRoleDefinition withOutgoingSubRole(final String entityAlias, final Role role) {
public RbacRoleDefinition outgoingSubRole(final String entityAlias, final Role role) {
final var outgoingSubRole = findRbacRole(entityAlias, role);
addGrant(grantSubRoleToSuperRole(outgoingSubRole, this));
return this;
}
public RbacRoleDefinition createSubRole(final Role role) {
final var roleDef = findRbacRole(entityAlias, role).toCreate();
new RbacGrantDefinition(roleDef, this).toCreate();
return roleDef;
}
public RbacView pop() {
return RbacView.this;
}
@Override
public String toString() {
return "role:" + entityAlias.aliasName + role;
}
}
public RbacUserReference currentUser() {
return userDefs.stream().filter(u -> u.role == CREATOR).findFirst().orElseThrow();
public RbacUserReference findUserRef(final RbacUserReference.UserRole userRole) {
return userDefs.stream().filter(u -> u.role == userRole).findFirst().orElseThrow();
}
@EqualsAndHashCode

View File

@ -22,6 +22,7 @@ public class RbacViewMermaidFlowchart {
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
""");
flowchart.writeLn();
renderEntitySubgraphs();
renderGrants();
}

View File

@ -11,7 +11,9 @@ import jakarta.persistence.*;
import java.util.UUID;
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.Permission.ALL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.VIEW;
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.rbacViewFor;
@ -36,19 +38,19 @@ public class TestCustomerEntity implements RbacObject {
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("contact", TestCustomerEntity.class)
.withIdentityView(RbacView.SQL.query("target.prefix"))
.withUpdatableColumns("reference", "prefix", "adminUserName")
.createRole(OWNER)
.withPermission(ALL)
.withCurrentUserAsOwner()
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(RbacView.Permission.custom("add-package"))
.createSubRole(TENANT)
.withPermission(VIEW)
.pop();
// @formatter:on
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.permission(ALL);
})
.createSubRole(ADMIN, (with) -> {
with.permission(RbacView.Permission.custom("add-package"));
})
.createSubRole(TENANT, (with) -> {
with.permission(VIEW);
});
}
}

View File

@ -35,11 +35,11 @@ class TestCustomerEntityTest {
end
end
role:contact:owner ==> perm:contact:*
role:contact:owner ==> perm:contact:*
user:creator ==> role:contact:owner
role:global:admin ==> role:contact:owner
role:global:admin ==> role:contact:owner
role:contact:owner ==> perm:contact:*
role:contact:owner ==> perm:contact:*
role:contact:owner ==> role:contact:admin
role:contact:admin ==> perm:contact:add-package
role:contact:admin ==> perm:contact:add-package