insert (into) table permission
This commit is contained in:
parent
fa15378fd2
commit
b2cea1e882
@ -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.apache.commons.lang3.StringUtils;
|
||||
@ -17,7 +18,6 @@ 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.projection;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@ -67,7 +67,7 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("person", HsOfficePersonEntity.class)
|
||||
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
|
||||
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
|
||||
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.permission(ALL);
|
||||
|
@ -85,7 +85,7 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
|
||||
|| '-with-' || target.relType || '-'
|
||||
|| (select idName from hs_office_person_iv p where p.uuid = relHolderUuid)
|
||||
"""))
|
||||
.withRestrictedViewOrderedBy(SQL.expression(
|
||||
.withRestrictedViewOrderBy(SQL.expression(
|
||||
"(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)"))
|
||||
.withUpdatableColumns("contactUuid")
|
||||
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||
|
||||
public class InsertTriggerGenerator {
|
||||
|
||||
private final RbacView rbacDef;
|
||||
private final String liquibaseTagPrefix;
|
||||
|
||||
public InsertTriggerGenerator(final RbacView rbacDef, final String liqibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.liquibaseTagPrefix = liqibaseTagPrefix;
|
||||
}
|
||||
|
||||
void generateTo(final StringWriter plPgSql) {
|
||||
generateLiquibaseChangesetHeader(plPgSql);
|
||||
generateGrantInsertRoleToExistingCustomers(plPgSql);
|
||||
rbacDef.getGrantDefs().stream()
|
||||
.filter(g -> g.isToCreate() && g.grantType() == PERM_TO_ROLE &&
|
||||
g.getPermDef().getPermission() == RbacView.Permission.INSERT )
|
||||
.forEach(g -> {
|
||||
plPgSql.writeLn("""
|
||||
/**
|
||||
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
|
||||
*/
|
||||
create trigger ${rawSubTable}_it
|
||||
before insert
|
||||
on ${rawSubTable}
|
||||
for each row
|
||||
when ( hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') )
|
||||
execute procedure insertNotAllowedForCurrentSubjects('${rawSubTable}');
|
||||
""",
|
||||
with("rawSubTable", g.getPermDef().entityAlias.getRawTableName()),
|
||||
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() ));
|
||||
});
|
||||
|
||||
plPgSql.writeLn("--//");
|
||||
}
|
||||
|
||||
private void generateLiquibaseChangesetHeader(final StringWriter plPgSql) {
|
||||
plPgSql.writeLn("""
|
||||
-- ============================================================================
|
||||
--changeset ${liquibaseTagPrefix}-rbac-INSERT:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
""",
|
||||
with("liquibaseTagPrefix", liquibaseTagPrefix));
|
||||
}
|
||||
|
||||
private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) {
|
||||
plPgSql.writeLn("""
|
||||
/*
|
||||
Creates an INSERT INTO ${rawSubTableName} permission for the related ${rawSuperTableName} row.
|
||||
*/
|
||||
do language plpgsql $$
|
||||
declare
|
||||
row ${rawSuperTableName};
|
||||
permissionUuids uuid[];
|
||||
roleUuid uuid;
|
||||
begin
|
||||
FOR row IN SELECT * FROM ${rawSuperTableName}
|
||||
LOOP
|
||||
roleUuid := ${rawSuperRoleDescriptor}(row);
|
||||
permissionUuids := createPermissions(row.uuid, array ['INSERT:${rawSubTableName}']);
|
||||
call grantPermissionsToRole(roleUuid, permissionUuids);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
""",
|
||||
with("rawSubTableName", "test_package"), // TODO
|
||||
with("rawSuperTableName", "test_customer"), // TODO
|
||||
with("rawSuperRoleDescriptor", "testCustomerAdmin") // TODO
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||
|
||||
public class RbacObjectGenerator {
|
||||
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String rawTableName;
|
||||
|
||||
public RbacObjectGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
|
||||
}
|
||||
|
||||
void generateTo(final StringWriter plPgSql) {
|
||||
plPgSql.writeLn("""
|
||||
|
||||
-- ============================================================================
|
||||
--changeset ${liquibaseTagPrefix}-rbac-OBJECT:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRelatedRbacObject('${rawTableName}');
|
||||
--//
|
||||
|
||||
""",
|
||||
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||
with("rawTableName", rawTableName));
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.indented;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||
|
||||
public class RbacRestrictedViewGenerator {
|
||||
@ -33,9 +34,9 @@ public class RbacRestrictedViewGenerator {
|
||||
""",
|
||||
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||
with("orderBy", rbacDef.getOrderBySqlExpression().sql),
|
||||
with("updates", rbacDef.getUpdatableColumns().stream()
|
||||
with("updates", indented(rbacDef.getUpdatableColumns().stream()
|
||||
.map(c -> c + " = new." + c)
|
||||
.collect(joining("\n"))),
|
||||
.collect(joining(",\n")), 2)),
|
||||
with("rawTableName", rawTableName));
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,30 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
|
||||
import net.hostsharing.hsadminng.test.pac.TestPackageEntity;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.reflect.Modifier.isStatic;
|
||||
@ -60,6 +72,7 @@ public class RbacView {
|
||||
new RbacUserReference(CREATOR);
|
||||
entityAliases.put("global", new EntityAlias("global"));
|
||||
}
|
||||
|
||||
public RbacView withUpdatableColumns(final String... columnNames) {
|
||||
Collections.addAll(updatableColumns, columnNames);
|
||||
return this;
|
||||
@ -70,7 +83,7 @@ public class RbacView {
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView withRestrictedViewOrderedBy(final SQL orderBySqlExpression) {
|
||||
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
||||
this.orderBySqlExpression = orderBySqlExpression;
|
||||
return this;
|
||||
}
|
||||
@ -106,9 +119,7 @@ public class RbacView {
|
||||
}
|
||||
|
||||
private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) {
|
||||
final RbacPermissionDefinition permDef = new RbacPermissionDefinition(entityAlias, permission, true);
|
||||
permDefs.add(permDef);
|
||||
return permDef;
|
||||
return new RbacPermissionDefinition(entityAlias, permission, null, true);
|
||||
}
|
||||
|
||||
public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
|
||||
@ -119,7 +130,10 @@ public class RbacView {
|
||||
}
|
||||
|
||||
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
|
||||
final String aliasName, final Class<? extends HasUuid> entityClass, final SQL fetchSql, final Column dependsOnColum) {
|
||||
final String aliasName,
|
||||
final Class<? extends HasUuid> entityClass,
|
||||
final SQL fetchSql,
|
||||
final Column dependsOnColum) {
|
||||
if (rootEntityAliasProxy != null) {
|
||||
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);
|
||||
}
|
||||
@ -183,8 +197,12 @@ public class RbacView {
|
||||
importedRbacView.getGrantDefs().forEach(grantDef -> {
|
||||
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
|
||||
findOrCreateGrantDef(
|
||||
findRbacRole(mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName), grantDef.getSubRoleDef().getRole()),
|
||||
findRbacRole(mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName), grantDef.getSuperRoleDef().getRole())
|
||||
findRbacRole(
|
||||
mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName),
|
||||
grantDef.getSubRoleDef().getRole()),
|
||||
findRbacRole(
|
||||
mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName),
|
||||
grantDef.getSuperRoleDef().getRole())
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -199,16 +217,19 @@ public class RbacView {
|
||||
return new RbacExampleRole(entityAlias, role);
|
||||
}
|
||||
|
||||
|
||||
private RbacGrantDefinition grantRoleToUser(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
|
||||
return findOrCreateGrantDef(roleDefinition, user).toCreate();
|
||||
}
|
||||
|
||||
private RbacGrantDefinition grantPermissionToRole(final RbacPermissionDefinition permDef , final RbacRoleDefinition roleDef) {
|
||||
private RbacGrantDefinition grantPermissionToRole(
|
||||
final RbacPermissionDefinition permDef,
|
||||
final RbacRoleDefinition roleDef) {
|
||||
return findOrCreateGrantDef(permDef, roleDef).toCreate();
|
||||
}
|
||||
|
||||
private RbacGrantDefinition grantSubRoleToSuperRole(final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) {
|
||||
private RbacGrantDefinition grantSubRoleToSuperRole(
|
||||
final RbacRoleDefinition subRoleDefinition,
|
||||
final RbacRoleDefinition superRoleDefinition) {
|
||||
return findOrCreateGrantDef(subRoleDefinition, superRoleDefinition).toCreate();
|
||||
}
|
||||
|
||||
@ -220,6 +241,13 @@ public class RbacView {
|
||||
return entityAlias == rootEntityAliasProxy;
|
||||
}
|
||||
|
||||
public SQL getOrderBySqlExpression() {
|
||||
if (orderBySqlExpression == null) {
|
||||
return identityViewSqlQuery;
|
||||
}
|
||||
return orderBySqlExpression;
|
||||
}
|
||||
|
||||
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"));
|
||||
@ -238,16 +266,25 @@ public class RbacView {
|
||||
return RbacView.this;
|
||||
}
|
||||
|
||||
public RbacView grantPermission(final String entityAliasName, final Permission perm) {
|
||||
final var entityAlias = findEntityAlias(entityAliasName);
|
||||
final var forTable = entityAlias.getRawTableName();
|
||||
findOrCreateGrantDef(findRbacPerm(entityAlias, perm, forTable), superRoleDef).toCreate();
|
||||
return RbacView.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class RbacGrantDefinition {
|
||||
|
||||
private final RbacUserReference userDef;
|
||||
private final RbacRoleDefinition superRoleDef;
|
||||
private final RbacRoleDefinition subRoleDef;
|
||||
private final RbacPermissionDefinition permDef;
|
||||
private boolean toCreate;
|
||||
private boolean assumed = true;
|
||||
private boolean toCreate = false;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@ -295,8 +332,7 @@ public class RbacView {
|
||||
}
|
||||
|
||||
boolean isAssumed() {
|
||||
// TODO: not implemented yet
|
||||
return true;
|
||||
return assumed;
|
||||
}
|
||||
|
||||
boolean isToCreate() {
|
||||
@ -320,6 +356,10 @@ public class RbacView {
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public void unassumed() {
|
||||
this.assumed = false;
|
||||
}
|
||||
|
||||
public enum GrantType {
|
||||
ROLE_TO_USER,
|
||||
ROLE_TO_ROLE,
|
||||
@ -352,12 +392,15 @@ public class RbacView {
|
||||
|
||||
final EntityAlias entityAlias;
|
||||
final Permission permission;
|
||||
final String tableName;
|
||||
final boolean toCreate;
|
||||
|
||||
private RbacPermissionDefinition(final EntityAlias entityAlias, final Permission permission, final boolean toCreate) {
|
||||
private RbacPermissionDefinition(final EntityAlias entityAlias, final Permission permission, final String tableName, final boolean toCreate) {
|
||||
this.entityAlias = entityAlias;
|
||||
this.permission = permission;
|
||||
this.tableName = tableName;
|
||||
this.toCreate = toCreate;
|
||||
permDefs.add(this);
|
||||
}
|
||||
|
||||
public RbacView grantedTo(final String entityAlias, final Role role) {
|
||||
@ -367,7 +410,7 @@ public class RbacView {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "perm:" + entityAlias.aliasName + permission;
|
||||
return "perm:" + entityAlias.aliasName + permission + ofNullable(tableName).map(tn -> ":" + tn).orElse("");
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,26 +433,22 @@ public class RbacView {
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition owningUser(final RbacUserReference.UserRole userRole) {
|
||||
grantRoleToUser(this, findUserRef(userRole));
|
||||
return this;
|
||||
public RbacGrantDefinition owningUser(final RbacUserReference.UserRole userRole) {
|
||||
return grantRoleToUser(this, findUserRef(userRole));
|
||||
}
|
||||
|
||||
public RbacRoleDefinition permission(final Permission permission) {
|
||||
grantPermissionToRole( createPermission(entityAlias, permission) , this);
|
||||
return this;
|
||||
public RbacGrantDefinition permission(final Permission permission) {
|
||||
return grantPermissionToRole(createPermission(entityAlias, permission), this);
|
||||
}
|
||||
|
||||
public RbacRoleDefinition incomingSuperRole(final String entityAlias, final Role role) {
|
||||
public RbacGrantDefinition incomingSuperRole(final String entityAlias, final Role role) {
|
||||
final var incomingSuperRole = findRbacRole(entityAlias, role);
|
||||
grantSubRoleToSuperRole(this, incomingSuperRole);
|
||||
return this;
|
||||
return grantSubRoleToSuperRole(this, incomingSuperRole);
|
||||
}
|
||||
|
||||
public RbacRoleDefinition outgoingSubRole(final String entityAlias, final Role role) {
|
||||
public RbacGrantDefinition outgoingSubRole(final String entityAlias, final Role role) {
|
||||
final var outgoingSubRole = findRbacRole(entityAlias, role);
|
||||
grantSubRoleToSuperRole(outgoingSubRole, this);
|
||||
return this;
|
||||
return grantSubRoleToSuperRole(outgoingSubRole, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -430,6 +469,7 @@ public class RbacView {
|
||||
}
|
||||
|
||||
final UserRole role;
|
||||
|
||||
public RbacUserReference(final UserRole creator) {
|
||||
this.role = creator;
|
||||
userDefs.add(this);
|
||||
@ -461,6 +501,26 @@ public class RbacView {
|
||||
|
||||
}
|
||||
|
||||
RbacPermissionDefinition findRbacPerm(final EntityAlias entityAlias, final Permission perm, String tableName) {
|
||||
return permDefs.stream()
|
||||
.filter(p -> p.getEntityAlias() == entityAlias && p.getPermission() == perm)
|
||||
.findFirst()
|
||||
.orElseGet(() -> new RbacPermissionDefinition(entityAlias, perm, tableName, true)); // TODO: true => toCreate
|
||||
}
|
||||
|
||||
|
||||
RbacPermissionDefinition findRbacPerm(final EntityAlias entityAlias, final Permission perm) {
|
||||
return findRbacPerm(entityAlias, perm, null);
|
||||
}
|
||||
|
||||
public RbacPermissionDefinition findRbacPerm(final String entityAliasName, final Permission perm, String tableName) {
|
||||
return findRbacPerm(findEntityAlias(entityAliasName), perm, tableName);
|
||||
}
|
||||
|
||||
public RbacPermissionDefinition findRbacPerm(final String entityAliasName, final Permission perm) {
|
||||
return findRbacPerm(findEntityAlias(entityAliasName), perm);
|
||||
}
|
||||
|
||||
private RbacGrantDefinition findOrCreateGrantDef(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
|
||||
return grantDefs.stream()
|
||||
.filter(g -> g.subRoleDef == roleDefinition && g.userDef == user)
|
||||
@ -475,7 +535,9 @@ public class RbacView {
|
||||
.orElseGet(() -> new RbacGrantDefinition(permDef, roleDef));
|
||||
}
|
||||
|
||||
private RbacGrantDefinition findOrCreateGrantDef(final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) {
|
||||
private RbacGrantDefinition findOrCreateGrantDef(
|
||||
final RbacRoleDefinition subRoleDefinition,
|
||||
final RbacRoleDefinition superRoleDefinition) {
|
||||
return grantDefs.stream()
|
||||
.filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition)
|
||||
.findFirst()
|
||||
@ -508,7 +570,8 @@ public class RbacView {
|
||||
}
|
||||
return switch (fetchSql.part) {
|
||||
case SQL_QUERY -> fetchSql;
|
||||
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName() + " WHERE uuid = ${ref}." + dependsOnColum.column);
|
||||
case AUTO_FETCH ->
|
||||
SQL.query("SELECT * FROM " + getRawTableName() + " WHERE uuid = ${ref}." + dependsOnColum.column);
|
||||
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
|
||||
};
|
||||
}
|
||||
@ -530,12 +593,22 @@ public class RbacView {
|
||||
String getRawTableName() {
|
||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||
}
|
||||
|
||||
String dependsOnColumName() {
|
||||
if (dependsOnColum == null) {
|
||||
throw new IllegalStateException(
|
||||
"Entity " + aliasName + "(" + entityClass.getSimpleName() + ")" + ": please add dependsOnColum");
|
||||
}
|
||||
return dependsOnColum.column;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
public static final Role AGENT = new Role("agent");
|
||||
@ -554,6 +627,8 @@ public class RbacView {
|
||||
}
|
||||
|
||||
public record Permission(String permission) {
|
||||
|
||||
public static final Permission INSERT = new Permission("insert");
|
||||
public static final Permission ALL = new Permission("*");
|
||||
public static final Permission EDIT = new Permission("edit");
|
||||
public static final Permission VIEW = new Permission("view");
|
||||
@ -604,7 +679,8 @@ public class RbacView {
|
||||
return new SQL(null, Part.NOOP);
|
||||
}
|
||||
|
||||
/** Generic DSL method to specify an SQL SELECT expression.
|
||||
/**
|
||||
* Generic DSL method to specify an SQL SELECT expression.
|
||||
*
|
||||
* @param sql an SQL SELECT expression (not ending with ';)
|
||||
* @return the wrapped SQL expression
|
||||
@ -614,7 +690,8 @@ public class RbacView {
|
||||
return new SQL(sql, Part.SQL_QUERY);
|
||||
}
|
||||
|
||||
/** Generic DSL method to specify an SQL SELECT expression by just the projection part.
|
||||
/**
|
||||
* 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
|
||||
@ -700,17 +777,19 @@ public class RbacView {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Stream.of(
|
||||
net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity.class,
|
||||
net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity.class
|
||||
TestCustomerEntity.class,
|
||||
TestPackageEntity.class,
|
||||
HsOfficePersonEntity.class,
|
||||
HsOfficePartnerEntity.class,
|
||||
HsOfficePartnerDetailsEntity.class,
|
||||
HsOfficeBankAccountEntity.class,
|
||||
HsOfficeDebitorEntity.class,
|
||||
HsOfficeRelationshipEntity.class,
|
||||
HsOfficeCoopAssetsTransactionEntity.class,
|
||||
HsOfficeContactEntity.class,
|
||||
HsOfficeSepaMandateEntity.class,
|
||||
HsOfficeCoopSharesTransactionEntity.class,
|
||||
HsOfficeMembershipEntity.class
|
||||
).forEach(c -> {
|
||||
final Method mainMethod = Arrays.stream(c.getMethods()).filter(
|
||||
m -> isStatic(m.getModifiers()) && m.getName().equals("main")
|
||||
|
@ -97,21 +97,21 @@ public class RbacViewMermaidFlowchart {
|
||||
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
||||
}
|
||||
|
||||
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType f, final String t) {
|
||||
final var userGrants = rbacDef.getGrantDefs().stream()
|
||||
.filter(g -> g.grantType() == f)
|
||||
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
|
||||
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
|
||||
.filter(g -> g.grantType() == grantType)
|
||||
.toList();
|
||||
if ( !userGrants.isEmpty()) {
|
||||
if ( !grantsOfRequestedType.isEmpty()) {
|
||||
flowchart.ensureSingleEmptyLine();
|
||||
flowchart.writeLn(t);
|
||||
userGrants.forEach(g -> flowchart.writeLn(grantDef(g)));
|
||||
flowchart.writeLn(comment);
|
||||
grantsOfRequestedType.forEach(g -> flowchart.writeLn(grantDef(g)));
|
||||
}
|
||||
}
|
||||
|
||||
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
||||
final var arrow = grant.isToCreate()
|
||||
? grant.isAssumed() ? " ==> " : " == // ==> "
|
||||
: grant.isAssumed() ? " -.-> " : " -.- // -.-> ";
|
||||
? grant.isAssumed() ? " ==> " : " == /// ==> "
|
||||
: grant.isAssumed() ? " -.-> " : " -.- /// -.-> ";
|
||||
return switch (grant.grantType()) {
|
||||
case ROLE_TO_USER ->
|
||||
// TODO: other user types not implemented yet
|
||||
|
@ -30,6 +30,7 @@ public class RbacViewPostgresGenerator {
|
||||
new RbacObjectGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacRoleDescriptorsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RolesGrantsAndPermissionsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new InsertTriggerGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
}
|
||||
|
@ -68,10 +68,7 @@ public class StringWriter {
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
private String indented(final String text) {
|
||||
if ( indentLevel == 0) {
|
||||
return text;
|
||||
}
|
||||
public static String indented(final String text, final int indentLevel) {
|
||||
final var indentation = StringUtils.repeat(" ", indentLevel);
|
||||
final var indented = stream(text.split("\n"))
|
||||
.map(line -> line.trim().isBlank() ? "" : indentation + line)
|
||||
@ -79,6 +76,13 @@ public class StringWriter {
|
||||
return indented;
|
||||
}
|
||||
|
||||
private String indented(final String text) {
|
||||
if ( indentLevel == 0) {
|
||||
return text;
|
||||
}
|
||||
return indented(text, indentLevel);
|
||||
}
|
||||
|
||||
record VarDef(String name, String value){}
|
||||
|
||||
private static final class VarReplacer {
|
||||
|
@ -0,0 +1,5 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
// TODO: The whole code in this package is more like a quick hack to solve an urgent problem.
|
||||
// It should be re-written in PostgreSQL pl/pgsql,
|
||||
// so that no Java is needed to use this RBAC system in it's full extend.
|
@ -4,16 +4,16 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
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.rbac.rbacobject.RbacObject;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
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.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;
|
||||
@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TestCustomerEntity implements RbacObject {
|
||||
public class TestCustomerEntity implements HasUuid {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@ -36,22 +36,24 @@ public class TestCustomerEntity implements RbacObject {
|
||||
@Column(name = "adminusername")
|
||||
private String adminUserName;
|
||||
|
||||
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("customer", TestCustomerEntity.class)
|
||||
.withIdentityView(SQL.projection("prefix"))
|
||||
.withRestrictedViewOrderBy(SQL.expression("reference"))
|
||||
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||
with.permission(ALL);
|
||||
})
|
||||
.createSubRole(ADMIN, (with) -> {
|
||||
with.permission(RbacView.Permission.custom("add-package"));
|
||||
})
|
||||
.createSubRole(ADMIN)
|
||||
.createSubRole(TENANT, (with) -> {
|
||||
with.permission(VIEW);
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("113-test-customer-rbac");
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,29 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
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.test.cust.TestCustomerEntity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
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.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;
|
||||
|
||||
@Entity
|
||||
@Table(name = "test_package_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TestPackageEntity {
|
||||
public class TestPackageEntity implements HasUuid {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@ -31,4 +42,35 @@ public class TestPackageEntity {
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("package", TestPackageEntity.class)
|
||||
.withIdentityView(SQL.projection("name"))
|
||||
.withUpdatableColumns("customerUuid", "description")
|
||||
|
||||
.importEntityAlias("customer", TestCustomerEntity.class,
|
||||
dependsOnColumn("customerUuid"),
|
||||
fetchedBySql("""
|
||||
SELECT * FROM test_customer c
|
||||
WHERE c.uuid= ${ref}.customerUuid
|
||||
"""))
|
||||
.toRole("customer", ADMIN).grantPermission("package", INSERT)
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole("customer", ADMIN).unassumed();
|
||||
with.permission(ALL);
|
||||
with.permission(EDIT);
|
||||
})
|
||||
.createSubRole(ADMIN)
|
||||
.createSubRole(TENANT, (with) -> {
|
||||
with.outgoingSubRole("customer", TENANT);
|
||||
with.permission(VIEW);
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("123-test-package-rbac");
|
||||
}
|
||||
}
|
||||
|
@ -381,6 +381,7 @@ create table RbacPermission
|
||||
uuid uuid primary key references RbacReference (uuid) on delete cascade,
|
||||
objectUuid uuid not null references RbacObject,
|
||||
op RbacOp not null,
|
||||
opTableName RbacOp,
|
||||
unique (objectUuid, op)
|
||||
);
|
||||
|
||||
@ -397,6 +398,37 @@ select exists(
|
||||
);
|
||||
$$;
|
||||
|
||||
create or replace function createPermissions(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
|
||||
returns uuid[]
|
||||
language plpgsql as $$
|
||||
declare
|
||||
permissionId uuid;
|
||||
begin
|
||||
if (forObjectUuid is null) then
|
||||
raise exception 'forObjectUuid must not be null';
|
||||
end if;
|
||||
if (forOp = 'INSERT' && forOpTableName is null) then
|
||||
raise exception 'INSERT permissions needs forOpTableName';
|
||||
end if;
|
||||
if (forOp <> 'INSERT' && forOpTableName is not null) then
|
||||
raise exception 'forOpTableName must only be specified for ops: [INSERT]'; -- currently no other
|
||||
end if;
|
||||
|
||||
permissionId = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = forOp and opTableName = forOpTableName);
|
||||
if (permissionId is null) then
|
||||
insert
|
||||
into RbacReference ("type")
|
||||
values ('RbacPermission')
|
||||
returning uuid into permissionId;
|
||||
insert
|
||||
into RbacPermission (uuid, objectUuid, op, opTableName)
|
||||
values (permissionId, forObjectUuid, forOp, opTableName);
|
||||
end if;
|
||||
return permissionId;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- TODO: deprecated, remove and amend all usages to createPermission
|
||||
create or replace function createPermissions(forObjectUuid uuid, permitOps RbacOp[])
|
||||
returns uuid[]
|
||||
language plpgsql as $$
|
||||
@ -430,7 +462,7 @@ begin
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp)
|
||||
create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp, opTableName text = null )
|
||||
returns uuid
|
||||
returns null on null input
|
||||
stable -- leakproof
|
||||
@ -439,6 +471,7 @@ select uuid
|
||||
from RbacPermission p
|
||||
where p.objectUuid = forObjectUuid
|
||||
and p.op = forOp
|
||||
and p.opTableName = opTableName
|
||||
$$;
|
||||
|
||||
create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp)
|
||||
@ -552,6 +585,18 @@ select exists(
|
||||
);
|
||||
$$;
|
||||
|
||||
create or replace function hasInsertPermission(objectUuid uuid, forOp RbacOp, tableName text )
|
||||
returns BOOL
|
||||
stable -- leakproof
|
||||
language plpgsql as $$
|
||||
declare
|
||||
permissionUuid uuid;
|
||||
begin
|
||||
permissionUuid = findPermissionId(objectUuid, forOp);
|
||||
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function hasGlobalRoleGranted(userUuid uuid)
|
||||
returns bool
|
||||
stable -- leakproof
|
||||
|
@ -16,10 +16,10 @@ class TestCustomerEntityTest {
|
||||
|
||||
subgraph customer["`**customer**`"]
|
||||
direction TB
|
||||
style customer fill:#dd4901,stroke:darkblue,stroke-width:8px
|
||||
style customer fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||
|
||||
subgraph customer:roles[ ]
|
||||
style customer:roles fill: #dd4901
|
||||
style customer:roles fill:#dd4901,stroke:white
|
||||
|
||||
role:customer:owner[[customer:owner]]
|
||||
role:customer:admin[[customer:admin]]
|
||||
@ -27,10 +27,9 @@ class TestCustomerEntityTest {
|
||||
end
|
||||
|
||||
subgraph customer:permissions[ ]
|
||||
style customer:permissions fill: #dd4901
|
||||
style customer:permissions fill:#dd4901,stroke:white
|
||||
|
||||
perm:customer:*{{customer:*}}
|
||||
perm:customer:add-package{{customer:add-package}}
|
||||
perm:customer:view{{customer:view}}
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,71 @@
|
||||
package net.hostsharing.hsadminng.test.pac;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewMermaidFlowchart;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TestPackageEntityTest {
|
||||
|
||||
@Test
|
||||
void definesRbac() {
|
||||
final var rbacFlowchart = new RbacViewMermaidFlowchart(TestPackageEntity.rbac()).toString();
|
||||
assertThat(rbacFlowchart).isEqualTo("""
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
flowchart TB
|
||||
|
||||
subgraph package["`**package**`"]
|
||||
direction TB
|
||||
style package fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||
|
||||
subgraph package:roles[ ]
|
||||
style package:roles fill:#dd4901,stroke:white
|
||||
|
||||
role:package:owner[[package:owner]]
|
||||
role:package:admin[[package:admin]]
|
||||
role:package:tenant[[package:tenant]]
|
||||
end
|
||||
|
||||
subgraph package:permissions[ ]
|
||||
style package:permissions fill:#dd4901,stroke:white
|
||||
|
||||
perm:package:insert{{package:insert}}
|
||||
perm:package:*{{package:*}}
|
||||
perm:package:edit{{package:edit}}
|
||||
perm:package:view{{package:view}}
|
||||
end
|
||||
end
|
||||
|
||||
subgraph customer["`**customer**`"]
|
||||
direction TB
|
||||
style customer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||
|
||||
subgraph customer:roles[ ]
|
||||
style customer:roles fill:#99bcdb,stroke:white
|
||||
|
||||
role:customer:owner[[customer:owner]]
|
||||
role:customer:admin[[customer:admin]]
|
||||
role:customer:tenant[[customer:tenant]]
|
||||
end
|
||||
end
|
||||
|
||||
%% granting roles to users
|
||||
user:creator ==> role:package:owner
|
||||
|
||||
%% granting roles to roles
|
||||
role:global:admin -.-> role:customer:owner
|
||||
role:customer:owner -.-> role:customer:admin
|
||||
role:customer:admin -.-> role:customer:tenant
|
||||
role:customer:admin == /// ==> role:package:owner
|
||||
role:package:owner ==> role:package:admin
|
||||
role:package:admin ==> role:package:tenant
|
||||
role:package:tenant ==> role:customer:tenant
|
||||
|
||||
%% granting permissions to roles
|
||||
role:customer:admin ==> perm:package:insert
|
||||
role:package:owner ==> perm:package:*
|
||||
role:package:owner ==> perm:package:edit
|
||||
role:package:tenant ==> perm:package:view
|
||||
""");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user