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.errors.DisplayName;
|
||||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
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.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.rbac.rbacdef.RbacView.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
|
|||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacView rbac() {
|
||||||
return rbacViewFor("person", HsOfficePersonEntity.class)
|
return rbacViewFor("person", HsOfficePersonEntity.class)
|
||||||
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
|
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
|
||||||
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
|
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
|
||||||
.createRole(OWNER, (with) -> {
|
.createRole(OWNER, (with) -> {
|
||||||
with.permission(ALL);
|
with.permission(ALL);
|
||||||
|
@ -85,7 +85,7 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
|
|||||||
|| '-with-' || target.relType || '-'
|
|| '-with-' || target.relType || '-'
|
||||||
|| (select idName from hs_office_person_iv p where p.uuid = relHolderUuid)
|
|| (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)"))
|
"(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)"))
|
||||||
.withUpdatableColumns("contactUuid")
|
.withUpdatableColumns("contactUuid")
|
||||||
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
|
.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 java.util.stream.Collectors.joining;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.indented;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
|
||||||
|
|
||||||
public class RbacRestrictedViewGenerator {
|
public class RbacRestrictedViewGenerator {
|
||||||
@ -26,16 +27,16 @@ public class RbacRestrictedViewGenerator {
|
|||||||
call generateRbacRestrictedView('${rawTableName}',
|
call generateRbacRestrictedView('${rawTableName}',
|
||||||
'${orderBy}',
|
'${orderBy}',
|
||||||
$updates$
|
$updates$
|
||||||
${updates}
|
${updates}
|
||||||
$updates$);
|
$updates$);
|
||||||
--//
|
--//
|
||||||
|
|
||||||
""",
|
""",
|
||||||
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||||
with("orderBy", rbacDef.getOrderBySqlExpression().sql),
|
with("orderBy", rbacDef.getOrderBySqlExpression().sql),
|
||||||
with("updates", rbacDef.getUpdatableColumns().stream()
|
with("updates", indented(rbacDef.getUpdatableColumns().stream()
|
||||||
.map(c -> c + " = new." + c)
|
.map(c -> c + " = new." + c)
|
||||||
.collect(joining("\n"))),
|
.collect(joining(",\n")), 2)),
|
||||||
with("rawTableName", rawTableName));
|
with("rawTableName", rawTableName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,30 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
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.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
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.persistence.HasUuid;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
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.persistence.Table;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.lang.reflect.Modifier.isStatic;
|
import static java.lang.reflect.Modifier.isStatic;
|
||||||
@ -36,7 +48,7 @@ public class RbacView {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityAlias put(final String key, final EntityAlias value) {
|
public EntityAlias put(final String key, final EntityAlias value) {
|
||||||
if ( containsKey(key) ) {
|
if (containsKey(key)) {
|
||||||
throw new IllegalArgumentException("duplicate entityAlias: " + key);
|
throw new IllegalArgumentException("duplicate entityAlias: " + key);
|
||||||
}
|
}
|
||||||
return super.put(key, value);
|
return super.put(key, value);
|
||||||
@ -60,6 +72,7 @@ public class RbacView {
|
|||||||
new RbacUserReference(CREATOR);
|
new RbacUserReference(CREATOR);
|
||||||
entityAliases.put("global", new EntityAlias("global"));
|
entityAliases.put("global", new EntityAlias("global"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView withUpdatableColumns(final String... columnNames) {
|
public RbacView withUpdatableColumns(final String... columnNames) {
|
||||||
Collections.addAll(updatableColumns, columnNames);
|
Collections.addAll(updatableColumns, columnNames);
|
||||||
return this;
|
return this;
|
||||||
@ -70,7 +83,7 @@ public class RbacView {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView withRestrictedViewOrderedBy(final SQL orderBySqlExpression) {
|
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
||||||
this.orderBySqlExpression = orderBySqlExpression;
|
this.orderBySqlExpression = orderBySqlExpression;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -106,21 +119,22 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) {
|
private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) {
|
||||||
final RbacPermissionDefinition permDef = new RbacPermissionDefinition(entityAlias, permission, true);
|
return new RbacPermissionDefinition(entityAlias, permission, null, true);
|
||||||
permDefs.add(permDef);
|
|
||||||
return permDef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
|
public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
|
||||||
for ( String alias: aliasNames ) {
|
for (String alias : aliasNames) {
|
||||||
entityAliases.put(alias, new EntityAlias(alias));
|
entityAliases.put(alias, new EntityAlias(alias));
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
|
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
|
||||||
final String aliasName, final Class<? extends HasUuid> entityClass, final SQL fetchSql, final Column dependsOnColum) {
|
final String aliasName,
|
||||||
if ( rootEntityAliasProxy != null ) {
|
final Class<? extends HasUuid> entityClass,
|
||||||
|
final SQL fetchSql,
|
||||||
|
final Column dependsOnColum) {
|
||||||
|
if (rootEntityAliasProxy != null) {
|
||||||
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);
|
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);
|
||||||
}
|
}
|
||||||
rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false);
|
rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false);
|
||||||
@ -155,7 +169,7 @@ public class RbacView {
|
|||||||
entityAliases.put(aliasName, entityAlias);
|
entityAliases.put(aliasName, entityAlias);
|
||||||
try {
|
try {
|
||||||
importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity);
|
importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity);
|
||||||
} catch ( final ReflectiveOperationException exc) {
|
} catch (final ReflectiveOperationException exc) {
|
||||||
throw new RuntimeException("cannot import entity: " + entityClass, exc);
|
throw new RuntimeException("cannot import entity: " + entityClass, exc);
|
||||||
}
|
}
|
||||||
return entityAlias;
|
return entityAlias;
|
||||||
@ -178,13 +192,17 @@ public class RbacView {
|
|||||||
entityAliases.put(mappedAliasName, new EntityAlias(mappedAliasName, entityAlias.entityClass));
|
entityAliases.put(mappedAliasName, new EntityAlias(mappedAliasName, entityAlias.entityClass));
|
||||||
});
|
});
|
||||||
importedRbacView.getRoleDefs().forEach(roleDef -> {
|
importedRbacView.getRoleDefs().forEach(roleDef -> {
|
||||||
new RbacRoleDefinition( findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
|
new RbacRoleDefinition(findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
|
||||||
});
|
});
|
||||||
importedRbacView.getGrantDefs().forEach(grantDef -> {
|
importedRbacView.getGrantDefs().forEach(grantDef -> {
|
||||||
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
|
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
|
||||||
findOrCreateGrantDef(
|
findOrCreateGrantDef(
|
||||||
findRbacRole(mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName), grantDef.getSubRoleDef().getRole()),
|
findRbacRole(
|
||||||
findRbacRole(mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName), grantDef.getSuperRoleDef().getRole())
|
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);
|
return new RbacExampleRole(entityAlias, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RbacGrantDefinition grantRoleToUser(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
|
||||||
private RbacGrantDefinition grantRoleToUser(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
|
|
||||||
return findOrCreateGrantDef(roleDefinition, user).toCreate();
|
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();
|
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();
|
return findOrCreateGrantDef(subRoleDefinition, superRoleDefinition).toCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +241,13 @@ public class RbacView {
|
|||||||
return entityAlias == rootEntityAliasProxy;
|
return entityAlias == rootEntityAliasProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SQL getOrderBySqlExpression() {
|
||||||
|
if (orderBySqlExpression == null) {
|
||||||
|
return identityViewSqlQuery;
|
||||||
|
}
|
||||||
|
return orderBySqlExpression;
|
||||||
|
}
|
||||||
|
|
||||||
public void generateWithBaseFileName(final String baseFileName) {
|
public void generateWithBaseFileName(final String baseFileName) {
|
||||||
new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.md"));
|
new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.md"));
|
||||||
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.sql"));
|
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.sql"));
|
||||||
@ -238,16 +266,25 @@ public class RbacView {
|
|||||||
return RbacView.this;
|
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
|
@Getter
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class RbacGrantDefinition {
|
public class RbacGrantDefinition {
|
||||||
|
|
||||||
private final RbacUserReference userDef;
|
private final RbacUserReference userDef;
|
||||||
private final RbacRoleDefinition superRoleDef;
|
private final RbacRoleDefinition superRoleDef;
|
||||||
private final RbacRoleDefinition subRoleDef;
|
private final RbacRoleDefinition subRoleDef;
|
||||||
private final RbacPermissionDefinition permDef;
|
private final RbacPermissionDefinition permDef;
|
||||||
private boolean toCreate;
|
private boolean assumed = true;
|
||||||
|
private boolean toCreate = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@ -295,8 +332,7 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isAssumed() {
|
boolean isAssumed() {
|
||||||
// TODO: not implemented yet
|
return assumed;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isToCreate() {
|
boolean isToCreate() {
|
||||||
@ -310,7 +346,7 @@ public class RbacView {
|
|||||||
|
|
||||||
boolean dependsOnColumn(final String columnName) {
|
boolean dependsOnColumn(final String columnName) {
|
||||||
return dependsRoleDefOnColumnName(this.superRoleDef, columnName)
|
return dependsRoleDefOnColumnName(this.superRoleDef, columnName)
|
||||||
|| dependsRoleDefOnColumnName(this.subRoleDef, columnName);
|
|| dependsRoleDefOnColumnName(this.subRoleDef, columnName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean dependsRoleDefOnColumnName(final RbacRoleDefinition superRoleDef, final String columnName) {
|
private Boolean dependsRoleDefOnColumnName(final RbacRoleDefinition superRoleDef, final String columnName) {
|
||||||
@ -320,6 +356,10 @@ public class RbacView {
|
|||||||
.orElse(false);
|
.orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void unassumed() {
|
||||||
|
this.assumed = false;
|
||||||
|
}
|
||||||
|
|
||||||
public enum GrantType {
|
public enum GrantType {
|
||||||
ROLE_TO_USER,
|
ROLE_TO_USER,
|
||||||
ROLE_TO_ROLE,
|
ROLE_TO_ROLE,
|
||||||
@ -352,22 +392,25 @@ public class RbacView {
|
|||||||
|
|
||||||
final EntityAlias entityAlias;
|
final EntityAlias entityAlias;
|
||||||
final Permission permission;
|
final Permission permission;
|
||||||
|
final String tableName;
|
||||||
final boolean toCreate;
|
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.entityAlias = entityAlias;
|
||||||
this.permission = permission;
|
this.permission = permission;
|
||||||
|
this.tableName = tableName;
|
||||||
this.toCreate = toCreate;
|
this.toCreate = toCreate;
|
||||||
|
permDefs.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView grantedTo(final String entityAlias, final Role role) {
|
public RbacView grantedTo(final String entityAlias, final Role role) {
|
||||||
findOrCreateGrantDef(this, findRbacRole(entityAlias, role) ).toCreate();
|
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
|
||||||
return RbacView.this;
|
return RbacView.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacRoleDefinition owningUser(final RbacUserReference.UserRole userRole) {
|
public RbacGrantDefinition owningUser(final RbacUserReference.UserRole userRole) {
|
||||||
grantRoleToUser(this, findUserRef(userRole));
|
return grantRoleToUser(this, findUserRef(userRole));
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacRoleDefinition permission(final Permission permission) {
|
public RbacGrantDefinition permission(final Permission permission) {
|
||||||
grantPermissionToRole( createPermission(entityAlias, permission) , this);
|
return grantPermissionToRole(createPermission(entityAlias, permission), this);
|
||||||
return 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);
|
final var incomingSuperRole = findRbacRole(entityAlias, role);
|
||||||
grantSubRoleToSuperRole(this, incomingSuperRole);
|
return grantSubRoleToSuperRole(this, incomingSuperRole);
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacRoleDefinition outgoingSubRole(final String entityAlias, final Role role) {
|
public RbacGrantDefinition outgoingSubRole(final String entityAlias, final Role role) {
|
||||||
final var outgoingSubRole = findRbacRole(entityAlias, role);
|
final var outgoingSubRole = findRbacRole(entityAlias, role);
|
||||||
grantSubRoleToSuperRole(outgoingSubRole, this);
|
return grantSubRoleToSuperRole(outgoingSubRole, this);
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -430,6 +469,7 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final UserRole role;
|
final UserRole role;
|
||||||
|
|
||||||
public RbacUserReference(final UserRole creator) {
|
public RbacUserReference(final UserRole creator) {
|
||||||
this.role = creator;
|
this.role = creator;
|
||||||
userDefs.add(this);
|
userDefs.add(this);
|
||||||
@ -443,7 +483,7 @@ public class RbacView {
|
|||||||
|
|
||||||
EntityAlias findEntityAlias(final String aliasName) {
|
EntityAlias findEntityAlias(final String aliasName) {
|
||||||
final var found = entityAliases.get(aliasName);
|
final var found = entityAliases.get(aliasName);
|
||||||
if ( found == null ) {
|
if (found == null) {
|
||||||
throw new IllegalArgumentException("entityAlias not found: " + aliasName);
|
throw new IllegalArgumentException("entityAlias not found: " + aliasName);
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
@ -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) {
|
private RbacGrantDefinition findOrCreateGrantDef(final RbacRoleDefinition roleDefinition, final RbacUserReference user) {
|
||||||
return grantDefs.stream()
|
return grantDefs.stream()
|
||||||
.filter(g -> g.subRoleDef == roleDefinition && g.userDef == user)
|
.filter(g -> g.subRoleDef == roleDefinition && g.userDef == user)
|
||||||
@ -475,7 +535,9 @@ public class RbacView {
|
|||||||
.orElseGet(() -> new RbacGrantDefinition(permDef, roleDef));
|
.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()
|
return grantDefs.stream()
|
||||||
.filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition)
|
.filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
@ -484,31 +546,32 @@ public class RbacView {
|
|||||||
|
|
||||||
record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity) {
|
record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity) {
|
||||||
|
|
||||||
public EntityAlias(final String aliasName) {
|
public EntityAlias(final String aliasName) {
|
||||||
this(aliasName, null, null, null, false);
|
this(aliasName, null, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) {
|
public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) {
|
||||||
this(aliasName, entityClass, null, null, false);
|
this(aliasName, entityClass, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isGlobal() {
|
boolean isGlobal() {
|
||||||
return aliasName().equals("global");
|
return aliasName().equals("global");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPlaceholder() {
|
boolean isPlaceholder() {
|
||||||
return entityClass == null;
|
return entityClass == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public SQL fetchSql() {
|
public SQL fetchSql() {
|
||||||
if ( fetchSql == null ) {
|
if (fetchSql == null) {
|
||||||
return SQL.noop();
|
return SQL.noop();
|
||||||
}
|
}
|
||||||
return switch (fetchSql.part) {
|
return switch (fetchSql.part) {
|
||||||
case SQL_QUERY -> fetchSql;
|
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);
|
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -518,7 +581,7 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String withoutEntitySuffix(final String simpleEntityName) {
|
private String withoutEntitySuffix(final String simpleEntityName) {
|
||||||
return simpleEntityName.substring(0, simpleEntityName.length()-"Entity".length());
|
return simpleEntityName.substring(0, simpleEntityName.length() - "Entity".length());
|
||||||
}
|
}
|
||||||
|
|
||||||
String simpleName() {
|
String simpleName() {
|
||||||
@ -530,12 +593,22 @@ public class RbacView {
|
|||||||
String getRawTableName() {
|
String getRawTableName() {
|
||||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
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) {
|
public static String withoutRvSuffix(final String tableName) {
|
||||||
return tableName.substring(0, tableName.length()-"_rv".length());
|
return tableName.substring(0, tableName.length() - "_rv".length());
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Role(String roleName) {
|
public record Role(String roleName) {
|
||||||
|
|
||||||
public static final Role OWNER = new Role("owner");
|
public static final Role OWNER = new Role("owner");
|
||||||
public static final Role ADMIN = new Role("admin");
|
public static final Role ADMIN = new Role("admin");
|
||||||
public static final Role AGENT = new Role("agent");
|
public static final Role AGENT = new Role("agent");
|
||||||
@ -549,11 +622,13 @@ public class RbacView {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object obj) {
|
public boolean equals(final Object obj) {
|
||||||
return ((obj instanceof Role) && ((Role)obj).roleName.equals(this.roleName));
|
return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Permission(String permission) {
|
public record Permission(String permission) {
|
||||||
|
|
||||||
|
public static final Permission INSERT = new Permission("insert");
|
||||||
public static final Permission ALL = new Permission("*");
|
public static final Permission ALL = new Permission("*");
|
||||||
public static final Permission EDIT = new Permission("edit");
|
public static final Permission EDIT = new Permission("edit");
|
||||||
public static final Permission VIEW = new Permission("view");
|
public static final Permission VIEW = new Permission("view");
|
||||||
@ -604,7 +679,8 @@ public class RbacView {
|
|||||||
return new SQL(null, Part.NOOP);
|
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 ';)
|
* @param sql an SQL SELECT expression (not ending with ';)
|
||||||
* @return the wrapped SQL expression
|
* @return the wrapped SQL expression
|
||||||
@ -614,7 +690,8 @@ public class RbacView {
|
|||||||
return new SQL(sql, Part.SQL_QUERY);
|
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'
|
* @param projection an SQL SELECT expression, the list of columns after 'SELECT'
|
||||||
* @return the wrapped SQL projection
|
* @return the wrapped SQL projection
|
||||||
@ -688,10 +765,10 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String map(final String originalAliasName) {
|
String map(final String originalAliasName) {
|
||||||
if (outerAliasNames.contains(originalAliasName) || originalAliasName.equals("global")) {
|
if (outerAliasNames.contains(originalAliasName) || originalAliasName.equals("global")) {
|
||||||
return originalAliasName;
|
return originalAliasName;
|
||||||
}
|
}
|
||||||
if (originalAliasName.equals(importedRbacView.rootEntityAlias.aliasName) ) {
|
if (originalAliasName.equals(importedRbacView.rootEntityAlias.aliasName)) {
|
||||||
return outerAliasName;
|
return outerAliasName;
|
||||||
}
|
}
|
||||||
return outerAliasName + "." + originalAliasName;
|
return outerAliasName + "." + originalAliasName;
|
||||||
@ -700,17 +777,19 @@ public class RbacView {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Stream.of(
|
Stream.of(
|
||||||
net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity.class,
|
TestCustomerEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity.class,
|
TestPackageEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity.class,
|
HsOfficePersonEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity.class,
|
HsOfficePartnerEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity.class,
|
HsOfficePartnerDetailsEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity.class,
|
HsOfficeBankAccountEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity.class,
|
HsOfficeDebitorEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity.class,
|
HsOfficeRelationshipEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity.class,
|
HsOfficeCoopAssetsTransactionEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity.class,
|
HsOfficeContactEntity.class,
|
||||||
net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity.class
|
HsOfficeSepaMandateEntity.class,
|
||||||
|
HsOfficeCoopSharesTransactionEntity.class,
|
||||||
|
HsOfficeMembershipEntity.class
|
||||||
).forEach(c -> {
|
).forEach(c -> {
|
||||||
final Method mainMethod = Arrays.stream(c.getMethods()).filter(
|
final Method mainMethod = Arrays.stream(c.getMethods()).filter(
|
||||||
m -> isStatic(m.getModifiers()) && m.getName().equals("main")
|
m -> isStatic(m.getModifiers()) && m.getName().equals("main")
|
||||||
@ -719,7 +798,7 @@ public class RbacView {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (mainMethod != null) {
|
if (mainMethod != null) {
|
||||||
try {
|
try {
|
||||||
mainMethod.invoke(null, new Object[]{null});
|
mainMethod.invoke(null, new Object[] { null });
|
||||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -97,21 +97,21 @@ public class RbacViewMermaidFlowchart {
|
|||||||
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType f, final String t) {
|
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
|
||||||
final var userGrants = rbacDef.getGrantDefs().stream()
|
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
|
||||||
.filter(g -> g.grantType() == f)
|
.filter(g -> g.grantType() == grantType)
|
||||||
.toList();
|
.toList();
|
||||||
if ( !userGrants.isEmpty()) {
|
if ( !grantsOfRequestedType.isEmpty()) {
|
||||||
flowchart.ensureSingleEmptyLine();
|
flowchart.ensureSingleEmptyLine();
|
||||||
flowchart.writeLn(t);
|
flowchart.writeLn(comment);
|
||||||
userGrants.forEach(g -> flowchart.writeLn(grantDef(g)));
|
grantsOfRequestedType.forEach(g -> flowchart.writeLn(grantDef(g)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
||||||
final var arrow = grant.isToCreate()
|
final var arrow = grant.isToCreate()
|
||||||
? grant.isAssumed() ? " ==> " : " == // ==> "
|
? grant.isAssumed() ? " ==> " : " == /// ==> "
|
||||||
: grant.isAssumed() ? " -.-> " : " -.- // -.-> ";
|
: grant.isAssumed() ? " -.-> " : " -.- /// -.-> ";
|
||||||
return switch (grant.grantType()) {
|
return switch (grant.grantType()) {
|
||||||
case ROLE_TO_USER ->
|
case ROLE_TO_USER ->
|
||||||
// TODO: other user types not implemented yet
|
// TODO: other user types not implemented yet
|
||||||
|
@ -30,6 +30,7 @@ public class RbacViewPostgresGenerator {
|
|||||||
new RbacObjectGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacObjectGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RbacRoleDescriptorsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacRoleDescriptorsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RolesGrantsAndPermissionsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RolesGrantsAndPermissionsGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
|
new InsertTriggerGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
}
|
}
|
||||||
|
@ -68,10 +68,7 @@ public class StringWriter {
|
|||||||
return string.toString();
|
return string.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String indented(final String text) {
|
public static String indented(final String text, final int indentLevel) {
|
||||||
if ( indentLevel == 0) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
final var indentation = StringUtils.repeat(" ", indentLevel);
|
final var indentation = StringUtils.repeat(" ", indentLevel);
|
||||||
final var indented = stream(text.split("\n"))
|
final var indented = stream(text.split("\n"))
|
||||||
.map(line -> line.trim().isBlank() ? "" : indentation + line)
|
.map(line -> line.trim().isBlank() ? "" : indentation + line)
|
||||||
@ -79,6 +76,13 @@ public class StringWriter {
|
|||||||
return indented;
|
return indented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String indented(final String text) {
|
||||||
|
if ( indentLevel == 0) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return indented(text, indentLevel);
|
||||||
|
}
|
||||||
|
|
||||||
record VarDef(String name, String value){}
|
record VarDef(String name, String value){}
|
||||||
|
|
||||||
private static final class VarReplacer {
|
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.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
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.*;
|
||||||
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.RbacUserReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||||
@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class TestCustomerEntity implements RbacObject {
|
public class TestCustomerEntity implements HasUuid {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
@ -36,22 +36,24 @@ public class TestCustomerEntity implements RbacObject {
|
|||||||
@Column(name = "adminusername")
|
@Column(name = "adminusername")
|
||||||
private String adminUserName;
|
private String adminUserName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacView rbac() {
|
||||||
return rbacViewFor("customer", TestCustomerEntity.class)
|
return rbacViewFor("customer", TestCustomerEntity.class)
|
||||||
.withIdentityView(SQL.projection("prefix"))
|
.withIdentityView(SQL.projection("prefix"))
|
||||||
|
.withRestrictedViewOrderBy(SQL.expression("reference"))
|
||||||
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
||||||
|
|
||||||
.createRole(OWNER, (with) -> {
|
.createRole(OWNER, (with) -> {
|
||||||
with.owningUser(CREATOR);
|
with.owningUser(CREATOR);
|
||||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||||
with.permission(ALL);
|
with.permission(ALL);
|
||||||
})
|
})
|
||||||
.createSubRole(ADMIN, (with) -> {
|
.createSubRole(ADMIN)
|
||||||
with.permission(RbacView.Permission.custom("add-package"));
|
|
||||||
})
|
|
||||||
.createSubRole(TENANT, (with) -> {
|
.createSubRole(TENANT, (with) -> {
|
||||||
with.permission(VIEW);
|
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.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
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 net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.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
|
@Entity
|
||||||
@Table(name = "test_package_rv")
|
@Table(name = "test_package_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class TestPackageEntity {
|
public class TestPackageEntity implements HasUuid {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
@ -31,4 +42,35 @@ public class TestPackageEntity {
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private String description;
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,9 +378,10 @@ create domain RbacOp as varchar(67)
|
|||||||
|
|
||||||
create table RbacPermission
|
create table RbacPermission
|
||||||
(
|
(
|
||||||
uuid uuid primary key references RbacReference (uuid) on delete cascade,
|
uuid uuid primary key references RbacReference (uuid) on delete cascade,
|
||||||
objectUuid uuid not null references RbacObject,
|
objectUuid uuid not null references RbacObject,
|
||||||
op RbacOp not null,
|
op RbacOp not null,
|
||||||
|
opTableName RbacOp,
|
||||||
unique (objectUuid, op)
|
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[])
|
create or replace function createPermissions(forObjectUuid uuid, permitOps RbacOp[])
|
||||||
returns uuid[]
|
returns uuid[]
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
@ -430,7 +462,7 @@ begin
|
|||||||
end;
|
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 uuid
|
||||||
returns null on null input
|
returns null on null input
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
@ -439,6 +471,7 @@ select uuid
|
|||||||
from RbacPermission p
|
from RbacPermission p
|
||||||
where p.objectUuid = forObjectUuid
|
where p.objectUuid = forObjectUuid
|
||||||
and p.op = forOp
|
and p.op = forOp
|
||||||
|
and p.opTableName = opTableName
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp)
|
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)
|
create or replace function hasGlobalRoleGranted(userUuid uuid)
|
||||||
returns bool
|
returns bool
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
|
@ -16,21 +16,20 @@ class TestCustomerEntityTest {
|
|||||||
|
|
||||||
subgraph customer["`**customer**`"]
|
subgraph customer["`**customer**`"]
|
||||||
direction TB
|
direction TB
|
||||||
style customer fill:#dd4901,stroke:darkblue,stroke-width:8px
|
style customer fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
subgraph customer:roles[ ]
|
subgraph customer:roles[ ]
|
||||||
style customer:roles fill: #dd4901
|
style customer:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
role:customer:owner[[customer:owner]]
|
role:customer:owner[[customer:owner]]
|
||||||
role:customer:admin[[customer:admin]]
|
role:customer:admin[[customer:admin]]
|
||||||
role:customer:tenant[[customer:tenant]]
|
role:customer:tenant[[customer:tenant]]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph customer:permissions[ ]
|
subgraph customer:permissions[ ]
|
||||||
style customer:permissions fill: #dd4901
|
style customer:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
perm:customer:*{{customer:*}}
|
perm:customer:*{{customer:*}}
|
||||||
perm:customer:add-package{{customer:add-package}}
|
|
||||||
perm:customer:view{{customer:view}}
|
perm:customer:view{{customer:view}}
|
||||||
end
|
end
|
||||||
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