RBAC generator with conditional grants used for REPRESENTATIVE-Relation #33

Merged
hsh-michaelhoennig merged 31 commits from rbac-generator-with-conditional-grants into master 2024-04-08 11:16:07 +02:00
4 changed files with 117 additions and 40 deletions
Showing only changes of commit 92086d8634 - Show all commits

View File

@ -26,6 +26,7 @@ import static jakarta.persistence.CascadeType.PERSIST;
import static jakarta.persistence.CascadeType.REFRESH;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
@ -157,6 +158,7 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
.toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
usingCase("DEBITOR"),
directlyFetchedByDependsOnColumn(),
dependsOnColumn("debitorRelUuid"))
.createPermission(DELETE).grantedTo("debitorRel", OWNER)

View File

@ -29,6 +29,7 @@ import java.util.UUID;
import static jakarta.persistence.CascadeType.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -98,6 +99,7 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
.toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
usingDefaultCase(),
directlyFetchedByDependsOnColumn(),
dependsOnColumn("partnerRelUuid"))
.createPermission(DELETE).grantedTo("partnerRel", ADMIN)

View File

@ -11,17 +11,17 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import java.io.IOException;
import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
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.Nullable.NOT_NULL;
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.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@ -101,17 +101,39 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
dependsOnColumn("contactUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)
.createRole(OWNER, (with) -> {
.switchOnColumn("type",
inCaseOf("REPRESENTATIVE", then -> {
then.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.incomingSuperRole("holderPerson", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN);
with.outgoingSubRole("anchorPerson", OWNER);
with.permission(UPDATE);
})
.createSubRole(AGENT, (with) -> {
})
.createSubRole(TENANT, (with) -> {
with.incomingSuperRole("holderPerson", ADMIN);
with.incomingSuperRole("contact", ADMIN);
with.outgoingSubRole("anchorPerson", REFERRER);
with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER);
with.permission(SELECT);
});
}),
//FIXME: .inCaseOf("DEBITOR")
inOtherCases(then -> {
then.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.incomingSuperRole("holderPerson", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.outgoingSuperRole("anchorPerson", OWNER);
with.permission(UPDATE);
})
.createSubRole(AGENT, (with) -> {
@ -124,8 +146,8 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER);
with.permission(SELECT);
})
});
}))
.toRole("anchorPerson", ADMIN).grantPermission(INSERT);
}

View File

@ -34,10 +34,10 @@ import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static org.apache.commons.lang3.StringUtils.uncapitalize;
@Getter
// TODO.refa: rename to RbacDSL
public class RbacView {
public static final String GLOBAL = "global";
@ -65,6 +65,15 @@ public class RbacView {
private SQL orderBySqlExpression;
private EntityAlias rootEntityAliasProxy;
private RbacRoleDefinition previousRoleDef;
private final Map<String, String> cases = new HashMap<>() {
@Override
public String put(final String key, final String value) {
if (containsKey(key)) {
throw new IllegalArgumentException("duplicate case: " + key);
}
return super.put(key, value);
}
};
/** Crates an RBAC definition template for the given entity class and defining the given alias.
*
@ -277,6 +286,7 @@ public class RbacView {
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
final String aliasName,
final Class<? extends RbacObject> entityClass,
final ColumnValue forCase,
final SQL fetchSql,
final Column dependsOnColum) {
if (rootEntityAliasProxy != null) {
@ -339,14 +349,6 @@ public class RbacView {
return this;
}
// TODO: remove once it's not used in HsOffice...Entity anymore
public RbacView importEntityAlias(
final String aliasName, final Class<? extends RbacObject> entityClass,
final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, directlyFetchedByDependsOnColumn(), dependsOnColum, false, null);
return this;
}
private EntityAlias importEntityAliasImpl(
final String aliasName, final Class<? extends RbacObject> entityClass,
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
@ -397,6 +399,12 @@ public class RbacView {
return this;
}
public RbacView switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
// FIXME: currently only the default case is executed
stream(caseDefs).filter(caseDef -> caseDef.val == null).findAny().orElseThrow().def.accept(this);
return this;
}
private void verifyVersionColumnExists() {
if (stream(rootEntityAlias.entityClass.getDeclaredFields())
.noneMatch(f -> f.getAnnotation(Version.class) != null)) {
@ -480,6 +488,25 @@ public class RbacView {
}
public static class CaseDef {
private final String val;
private final Consumer<RbacView> def;
public CaseDef(final String discriminatorColumnValue, final Consumer<RbacView> def) {
this.val = discriminatorColumnValue;
this.def = def;
}
}
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacView> def) {
return new CaseDef(discriminatorColumnValue, def);
}
public static CaseDef inOtherCases(final Consumer<RbacView> def) {
return new CaseDef(null, def);
}
public enum Nullable {
NOT_NULL, // DEFAULT
NULLABLE
@ -495,7 +522,8 @@ public class RbacView {
private final RbacPermissionDefinition permDef;
private boolean assumed = true;
private boolean toCreate = false;
private String sqlWhere;
private String onlyInCaseOf;
private String exceptInCaseOf;
@Override
public String toString() {
@ -549,7 +577,7 @@ public class RbacView {
}
boolean isConditional() {
return sqlWhere != null;
return onlyInCaseOf != null;
}
boolean isToCreate() {
@ -578,8 +606,14 @@ public class RbacView {
return this;
}
public RbacGrantDefinition where(final String sqlWhere) {
this.sqlWhere = sqlWhere;
public RbacGrantDefinition onlyInCaseOf(final String caseName) {
this.onlyInCaseOf = caseName;
return this;
}
public RbacGrantDefinition exceptInCaseOf(final String caseName) {
this.exceptInCaseOf = caseName;
return this;
}
@ -1034,6 +1068,23 @@ public class RbacView {
}
}
public static class ColumnValue {
public static ColumnValue usingDefaultCase() {
return new ColumnValue(null);
}
public static ColumnValue usingCase(final String value) {
return new ColumnValue(value);
}
public final String value;
private ColumnValue(final String value) {
this.value = value;
}
}
private static class AliasNameMapper {
private final RbacView importedRbacView;