fix indrirect permission by indirect foreign key

This commit is contained in:
Michael Hoennig 2024-03-25 05:57:58 +01:00
parent 78ecf98913
commit e6ef5b59c7
42 changed files with 532 additions and 245 deletions

View File

@ -21,6 +21,7 @@ import java.util.UUID;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -165,8 +166,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
FROM hs_office_bankaccount AS b
WHERE b.uuid = ${REF}.refundBankAccountUuid
"""),
NULLABLE
)
NULLABLE)
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
@ -179,8 +179,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
ON debitorRel.type = 'DEBITOR' AND debitorRel.anchorUuid = partnerRel.holderUuid
WHERE partnerRel.type = 'PARTNER'
AND ${REF}.debitorRelUuid = debitorRel.uuid
""")
)
"""),
NOT_NULL)
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)

View File

@ -20,6 +20,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
@ -135,7 +136,8 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
FROM hs_office_partner AS p
JOIN hs_office_relation AS r ON r.uuid = p.partnerRelUuid
WHERE p.uuid = ${REF}.partnerUuid
"""))
"""),
NOT_NULL)
.toRole("partnerRel", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {

View File

@ -16,6 +16,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -90,16 +91,16 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
dependsOnColumn("anchorUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid")
)
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid"),
NOT_NULL)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
dependsOnColumn("holderUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid")
)
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid"),
NOT_NULL)
.importEntityAlias("contact", HsOfficeContactEntity.class,
dependsOnColumn("contactUuid"),
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid")
)
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
NOT_NULL)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);

View File

@ -21,6 +21,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -110,12 +111,12 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
FROM hs_office_relation debitorRel
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = ${REF}.debitorUuid
""")
)
"""),
NOT_NULL)
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class,
dependsOnColumn("bankAccountUuid"),
autoFetched()
)
autoFetched(),
NOT_NULL)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);

View File

@ -114,7 +114,16 @@ public class InsertTriggerGenerator {
}
}
} else {
generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g);
final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias();
// TODO: Maybe this should depend on the indirection degree of the fetchSql?
// Maybe we need a separate fetchedBy method for all the simple, direct cases?
if (superRoleEntityAlias.fetchSql().sql.contains("JOIN ")) {
generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g);
} else {
generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g);
}
}
},
() -> {
@ -149,6 +158,50 @@ public class InsertTriggerGenerator {
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName()));
}
private void generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(
final StringWriter plPgSql,
final RbacView.RbacGrantDefinition g) {
plPgSql.writeLn("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable},
where the check is performed by an indirect role.
An indirect role is a role FIXME.
*/
create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
if ( not hasInsertPermission(
( SELECT ${varName}.uuid FROM
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()));
plPgSql.indented(3, () -> {
plPgSql.writeLn(
"(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}",
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()),
with("ref", NEW.name()));
});
plPgSql.writeLn("""
), 'INSERT', '${rawSubTable}') ) then
raise exception
'[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}
private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) {
plPgSql.writeLn("""
/**

View File

@ -66,6 +66,21 @@ public class RbacView {
private EntityAlias rootEntityAliasProxy;
private RbacRoleDefinition previousRoleDef;
/** Crates an RBAC definition template for the given entity class and defining the given alias.
*
* @param alias
* an alias name for this entity/table, which can be used in further grants
*
* @param entityClass
* the Java class for which this RBAC definition is to be defined
* (the class to which the calling method belongs)
*
* @return
* the newly created RBAC definition template
*
* @param <E>
* a JPA entity class extending RbacObject
*/
public static <E extends RbacObject> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
return new RbacView(alias, entityClass);
}
@ -77,22 +92,71 @@ public class RbacView {
entityAliases.put("global", new EntityAlias("global"));
}
/**
* Specifies, which columns of the restricted view are updatable at all.
*
* @param columnNames
* A list of the updatable columns.
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withUpdatableColumns(final String... columnNames) {
Collections.addAll(updatableColumns, columnNames);
verifyVersionColumnExists();
return this;
}
/** Specifies the SQL query which creates the identity view for this entity.
*
* <p>An identity view is a view which maps an objectUuid to an idName.
* The idName should be a human-readable representation of the row, but as short as possible.
* The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'.
* It's used to create the object-specific-role-names like test_customer#abc.admin - here 'abc' is the idName.
* The idName not necessarily unique in a table, but it should be avoided.
* </p>
*
* @param sqlExpression
* Either specify an SQL projection (the part between SELECT and FROM), e.g. `SQL.projection("columnName")
* or the whole SELECT query returning the uuid and idName columns,
* e.g. `SQL.query("SELECT ... AS uuid, ... AS idName FROM ... JOIN ...").
* Only add really important columns, just enough to create a short human-readable representation.
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withIdentityView(final SQL sqlExpression) {
this.identityViewSqlQuery = sqlExpression;
return this;
}
/**
* Specifies a ORDER BY clause for the generated restricted view.
*
* <p>A restricted view is generated, no matter if the order was specified or not.</p>
*
* @param orderBySqlExpression
* That's the part behind `ORDER BY`, e.g. `SQL.expression("prefix").
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
this.orderBySqlExpression = orderBySqlExpression;
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @param with
* a lambda which receives the created role to create grants and permissions to and from the newly created role,
* e.g. the owning user, incoming superroles, outgoing subroles
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
with.accept(newRoleDef);
@ -100,6 +164,15 @@ public class RbacView {
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table,
* which is becomes sub-role of the previously created role.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createSubRole(final Role role) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
@ -107,6 +180,19 @@ public class RbacView {
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table,
* which is becomes sub-role of the previously created role.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @param with
* a lambda which receives the created role to create grants and permissions to and from the newly created role,
* e.g. the owning user, incoming superroles, outgoing subroles
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
@ -115,10 +201,38 @@ public class RbacView {
return this;
}
/**
* Specifies that the given permission is to be created for each new row in the target table.
*
* <p>Grants to permissions created by this method have to be specified separately,
* often it's easier to read to use createRole/createSubRole and use with.permission(...).</p>
*
* @param permission
* e.g. INSERT, SELECT, UPDATE, DELETE
*
* @return
* the newly created permission definition
*/
public RbacPermissionDefinition createPermission(final Permission permission) {
return createPermission(rootEntityAlias, permission);
}
/**
* Specifies that the given permission is to be created for each new row in the target table,
* but for another table, e.g. a table with details data with different access rights.
*
* <p>Grants to permissions created by this method have to be specified separately,
* often it's easier to read to use createRole/createSubRole and use with.permission(...).</p>
*
* @param entityAliasName
* A previously defined entity alias name.
*
* @param permission
* e.g. INSERT, SELECT, UPDATE, DELETE
*
* @return
* the newly created permission definition
*/
public RbacPermissionDefinition createPermission(final String entityAliasName, final Permission permission) {
return createPermission(findEntityAlias(entityAliasName), permission);
}
@ -134,6 +248,32 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an alias name for it.
* This method is especially for proxy-entities, if the root entity does not have its own
* roles, a proxy-entity can be specified and its roles can be used instead.
*
* @param aliasName
* An alias name for the entity class. The same entity class can be imported multiple times,
* if multiple references to its table exist, then distinct alias names habe to be defined.
*
* @param entityClass
* A JPA entity class extending RbacObject which also implements an `rbac` method returning
* its RBAC specification.
*
* @param fetchSql
* An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the
* newly created or updated row (will be replaced by NEW/OLD from the trigger method).
*
* @param dependsOnColum
* The column, usually containing an uuid, on which this other table depends.
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
final String aliasName,
final Class<? extends HasUuid> entityClass,
@ -146,6 +286,18 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an alias name for it.
* This method is especially to declare sub-entities, e.g. details to a main object.
*
* @see {@link}
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public RbacView importSubEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum) {
@ -153,6 +305,33 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an anlias name for it.
*
* @param aliasName
* An alias name for the entity class. The same entity class can be imported multiple times,
* if multiple references to its table exist, then distinct alias names habe to be defined.
*
* @param entityClass
* A JPA entity class extending RbacObject which also implements an `rbac` method returning
* its RBAC specification.
*
* @param fetchSql
* An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the
* newly created or updated row (will be replaced by NEW/OLD from the trigger method).
*
* @param dependsOnColum
* The column, usually containing an uuid, on which this other table depends.
*
* @param nullable
* Specifies whether the dependsOnColum is nullable or not.
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
@ -160,13 +339,7 @@ public class RbacView {
return this;
}
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL);
return this;
}
// TODO: remove once it's not used in HsOffice...Entity anymore
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum) {
@ -232,6 +405,16 @@ public class RbacView {
}
}
/**
* Starts declaring a grant to a given role.
*
* @param entityAlias
* A previously speciried entity alias name.
* @param role
* OWNER, ADMIN, AGENT, ...
* @return
* a grant builder
*/
public RbacGrantBuilder toRole(final String entityAlias, final Role role) {
return new RbacGrantBuilder(entityAlias, role);
}
@ -430,6 +613,16 @@ public class RbacView {
permDefs.add(this);
}
/**
* Grants the permission under definition to the given role.
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The RbacView specification to which this permission definition belongs.
*/
public RbacView grantedTo(final String entityAlias, final Role role) {
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
return RbacView.this;
@ -460,19 +653,61 @@ public class RbacView {
return this;
}
/**
* Specifies which user becomes the owner of newly created objects.
* @param userRole
* GLOBAL_ADMIN, CREATOR, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition owningUser(final RbacUserReference.UserRole userRole) {
return grantRoleToUser(this, findUserRef(userRole));
}
/**
* Specifies which permission is to be created for newly created objects.
* @param permission
* INSERT, SELECT, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition permission(final Permission permission) {
return grantPermissionToRole(createPermission(entityAlias, permission), this);
}
/**
* Specifies in incoming super role which gets granted the role under definition.
*
* <p>Incoming means an incoming grant arrow in our grant-diagrams.
* Super-role means that it's the role to which another role is granted.
* Both means actually the same, just in different aspects.</p>
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition incomingSuperRole(final String entityAlias, final Role role) {
final var incomingSuperRole = findRbacRole(entityAlias, role);
return grantSubRoleToSuperRole(this, incomingSuperRole);
}
/**
* Specifies in outgoing sub role which gets granted the role under definition.
*
* <p>Outgoing means an outgoing grant arrow in our grant-diagrams.
* Sub-role means which is granted to another role.
* Both means actually the same, just in different aspects.</p>
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition outgoingSubRole(final String entityAlias, final Role role) {
final var outgoingSubRole = findRbacRole(entityAlias, role);
return grantSubRoleToSuperRole(outgoingSubRole, this);
@ -802,6 +1037,26 @@ public class RbacView {
}
}
private static void generateRbacView(final Class<? extends HasUuid> c) {
final Method mainMethod = stream(c.getMethods()).filter(
m -> isStatic(m.getModifiers()) && m.getName().equals("main")
)
.findFirst()
.orElse(null);
if (mainMethod != null) {
try {
mainMethod.invoke(null, new Object[] { null });
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated");
}
}
/**
* This main method generates the RbacViews (PostgreSQL+diagram) for all given entity classes.
*/
public static void main(String[] args) {
Stream.of(
TestCustomerEntity.class,
@ -818,21 +1073,6 @@ public class RbacView {
HsOfficeSepaMandateEntity.class,
HsOfficeCoopSharesTransactionEntity.class,
HsOfficeMembershipEntity.class
).forEach(c -> {
final Method mainMethod = stream(c.getMethods()).filter(
m -> isStatic(m.getModifiers()) && m.getName().equals("main")
)
.findFirst()
.orElse(null);
if (mainMethod != null) {
try {
mainMethod.invoke(null, new Object[] { null });
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated");
}
});
).forEach(RbacView::generateRbacView);
}
}

View File

@ -4,7 +4,6 @@ import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import java.nio.file.*;
import java.time.LocalDateTime;
import static java.util.stream.Collectors.joining;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
@ -149,14 +148,13 @@ public class RbacViewMermaidFlowchartGenerator {
"""
### rbac %{entityAlias}
This code generated was by RbacViewMermaidFlowchartGenerator at %{timestamp}.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%{flowchart}
```
"""
.replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName())
.replace("%{timestamp}", LocalDateTime.now().toString())
.replace("%{flowchart}", flowchart.toString()),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
System.out.println("Markdown-File: " + path.toAbsolutePath());

View File

@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
@ -21,10 +20,9 @@ public class RbacViewPostgresGenerator {
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableName().replace("_", "-");
plPgSql.writeLn("""
--liquibase formatted sql
-- This code generated was by ${generator} at ${timestamp}.
-- This code generated was by ${generator}, do not amend manually.
""",
with("generator", getClass().getSimpleName()),
with("timestamp", LocalDateTime.now().toString()),
with("ref", NEW.name()));
new RbacObjectGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);

View File

@ -28,8 +28,8 @@ public class RawRbacGrantEntity implements Comparable {
@Column(name = "grantedbyroleidname", updatable = false, insertable = false)
private String grantedByRoleIdName;
@Column(name = "usergrantsbyroleuuid", updatable = false, insertable = false)
private UUID userGrantsByRoleUuid;
@Column(name = "grantedbyroleuuid", updatable = false, insertable = false)
private UUID grantedByRoleUuid;
@Column(name = "ascendantidname", updatable = false, insertable = false)
private String ascendantIdName;
@ -50,7 +50,7 @@ public class RawRbacGrantEntity implements Comparable {
// @formatter:off
return "{ grant " + descendantIdName +
" to " + ascendantIdName +
" by " + ( userGrantsByRoleUuid == null
" by " + ( grantedByRoleUuid == null
? "system"
: grantedByRoleIdName ) +
( assumed ? " and assume" : "") +

View File

@ -22,8 +22,8 @@ public class RbacGrantEntity {
@Column(name = "grantedbyroleidname", updatable = false, insertable = false)
private String grantedByRoleIdName;
@Column(name = "usergrantsbyroleuuid", updatable = false, insertable = false)
private UUID userGrantsByRoleUuid;
@Column(name = "grantedbyroleuuid", updatable = false, insertable = false)
private UUID grantedByRoleUuid;
@Column(name = "grantedroleidname", updatable = false, insertable = false)
private String grantedRoleIdName;

View File

@ -14,6 +14,7 @@ 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.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
@ -52,7 +53,8 @@ public class TestDomainEntity implements HasUuid {
fetchedBySql("""
SELECT * FROM test_package p
WHERE p.uuid= ${ref}.packageUuid
"""))
"""),
NOT_NULL)
.toRole("package", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {

View File

@ -14,6 +14,7 @@ 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.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
@ -53,7 +54,8 @@ public class TestPackageEntity implements HasUuid {
fetchedBySql("""
SELECT * FROM test_customer c
WHERE c.uuid= ${ref}.customerUuid
"""))
"""),
NOT_NULL)
.toRole("customer", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {

View File

@ -160,6 +160,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar)
declare
cleanIdentifier varchar;
begin
-- TODO: remove the ':' from the list of allowed characters as soon as it's not used anymore
cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._:]+', '', 'g');
return cleanIdentifier;
end; $$;

View File

@ -300,7 +300,7 @@ create or replace function deleteRbacGrantsOfRbacRole()
strict as $$
begin
if TG_OP = 'DELETE' then
delete from RbacGrants g where old.uuid in (g.userGrantsByRoleUuid, g.ascendantuuid, g.descendantuuid);
delete from RbacGrants g where old.uuid in (g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid);
else
raise exception 'invalid usage of TRIGGER BEFORE DELETE';
end if;
@ -519,12 +519,12 @@ create table RbacGrants
(
uuid uuid primary key default uuid_generate_v4(),
grantedByTriggerOf uuid references RbacObject (uuid) on delete cascade initially deferred ,
userGrantsByRoleUuid uuid references RbacRole (uuid),
grantedByRoleUuid uuid references RbacRole (uuid),
ascendantUuid uuid references RbacReference (uuid),
descendantUuid uuid references RbacReference (uuid),
assumed boolean not null default true, -- auto assumed (true) vs. needs assumeRoles (false)
unique (ascendantUuid, descendantUuid),
constraint rbacGrant_createdBy check ( userGrantsByRoleUuid is null or grantedByTriggerOf is null) );
constraint rbacGrant_createdBy check ( grantedByRoleUuid is null or grantedByTriggerOf is null) );
create index on RbacGrants (ascendantUuid);
create index on RbacGrants (descendantUuid);

View File

@ -20,52 +20,50 @@ begin
return currentSubjectsUuids[1];
end; $$;
create or replace procedure grantRoleToUserUnchecked(userGrantsByRoleUuid uuid, roleUuid uuid, userUuid uuid, doAssume boolean = true)
create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, roleUuid uuid, userUuid uuid, doAssume boolean = true)
language plpgsql as $$
begin
perform assertReferenceType('grantingRoleUuid', userGrantsByRoleUuid, 'RbacRole');
perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole');
perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
raise notice 'role % grants role % to user %, assumed=%', userGrantsByRoleUuid, roleUuid, userUuid, doAssume;
insert
into RbacGrants (userGrantsByRoleUuid, ascendantUuid, descendantUuid, assumed)
values (userGrantsByRoleUuid, userUuid, roleUuid, doAssume);
into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
values (grantedByRoleUuid, userUuid, roleUuid, doAssume);
-- TODO.spec: What should happen on multiple grants? What if options (doAssume) are not the same?
-- Most powerful or latest grant wins? What about managed?
-- on conflict do nothing; -- allow granting multiple times
end; $$;
create or replace procedure grantRoleToUser(userGrantsByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)
create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)
language plpgsql as $$
declare
grantedByRoleIdName text;
grantedRoleIdName text;
begin
perform assertReferenceType('grantingRoleUuid', userGrantsByRoleUuid, 'RbacRole');
perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole');
perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser');
assert userGrantsByRoleUuid is not null, 'userGrantsByRoleUuid must not be null';
assert grantedByRoleUuid is not null, 'grantedByRoleUuid must not be null';
assert grantedRoleUuid is not null, 'grantedRoleUuid must not be null';
assert userUuid is not null, 'userUuid must not be null';
if NOT isGranted(currentSubjectsUuids(), userGrantsByRoleUuid) then
select roleIdName from rbacRole_ev where uuid=userGrantsByRoleUuid into grantedByRoleIdName;
if NOT isGranted(currentSubjectsUuids(), grantedByRoleUuid) then
select roleIdName from rbacRole_ev where uuid=grantedByRoleUuid into grantedByRoleIdName;
raise exception '[403] Access to granted-by-role % (%) forbidden for % (%)',
grantedByRoleIdName, userGrantsByRoleUuid, currentSubjects(), currentSubjectsUuids();
grantedByRoleIdName, grantedByRoleUuid, currentSubjects(), currentSubjectsUuids();
end if;
if NOT isGranted(userGrantsByRoleUuid, grantedRoleUuid) then
select roleIdName from rbacRole_ev where uuid=userGrantsByRoleUuid into grantedByRoleIdName;
if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then
select roleIdName from rbacRole_ev where uuid=grantedByRoleUuid into grantedByRoleIdName;
select roleIdName from rbacRole_ev where uuid=grantedRoleUuid into grantedRoleIdName;
raise exception '[403] Access to granted role % (%) forbidden for % (%)',
grantedRoleIdName, grantedRoleUuid, grantedByRoleIdName, userGrantsByRoleUuid;
grantedRoleIdName, grantedRoleUuid, grantedByRoleIdName, grantedByRoleUuid;
end if;
insert
into RbacGrants (userGrantsByRoleUuid, ascendantUuid, descendantUuid, assumed)
values (userGrantsByRoleUuid, userUuid, grantedRoleUuid, doAssume);
into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume);
-- TODO.spec: What should happen on mupltiple grants? What if options (doAssume) are not the same?
-- Most powerful or latest grant wins? What about managed?
-- on conflict do nothing; -- allow granting multiple times
@ -77,40 +75,40 @@ end; $$;
--changeset rbac-user-grant-REVOKE-ROLE-FROM-USER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure checkRevokeRoleFromUserPreconditions(userGrantsByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
create or replace procedure checkRevokeRoleFromUserPreconditions(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
language plpgsql as $$
begin
perform assertReferenceType('userGrantsByRoleUuid', userGrantsByRoleUuid, 'RbacRole');
perform assertReferenceType('grantedByRoleUuid', grantedByRoleUuid, 'RbacRole');
perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole');
perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser');
if NOT isGranted(currentSubjectsUuids(), userGrantsByRoleUuid) then
raise exception '[403] Revoking role created by % is forbidden for %.', userGrantsByRoleUuid, currentSubjects();
if NOT isGranted(currentSubjectsUuids(), grantedByRoleUuid) then
raise exception '[403] Revoking role created by % is forbidden for %.', grantedByRoleUuid, currentSubjects();
end if;
if NOT isGranted(userGrantsByRoleUuid, grantedRoleUuid) then
if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then
raise exception '[403] Revoking role % is forbidden for %.', grantedRoleUuid, currentSubjects();
end if;
--raise exception 'isGranted(%, %)', currentSubjectsUuids(), userGrantsByRoleUuid;
if NOT isGranted(currentSubjectsUuids(), userGrantsByRoleUuid) then
raise exception '[403] Revoking role granted by % is forbidden for %.', userGrantsByRoleUuid, currentSubjects();
--raise exception 'isGranted(%, %)', currentSubjectsUuids(), grantedByRoleUuid;
if NOT isGranted(currentSubjectsUuids(), grantedByRoleUuid) then
raise exception '[403] Revoking role granted by % is forbidden for %.', grantedByRoleUuid, currentSubjects();
end if;
if NOT isGranted(userUuid, grantedRoleUuid) then
raise exception '[404] No such grant found granted by % for user % to role %.', userGrantsByRoleUuid, userUuid, grantedRoleUuid;
raise exception '[404] No such grant found granted by % for user % to role %.', grantedByRoleUuid, userUuid, grantedRoleUuid;
end if;
end; $$;
create or replace procedure revokeRoleFromUser(userGrantsByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
create or replace procedure revokeRoleFromUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
language plpgsql as $$
begin
call checkRevokeRoleFromUserPreconditions(userGrantsByRoleUuid, grantedRoleUuid, userUuid);
call checkRevokeRoleFromUserPreconditions(grantedByRoleUuid, grantedRoleUuid, userUuid);
raise INFO 'delete from RbacGrants where ascendantUuid = % and descendantUuid = %', userUuid, grantedRoleUuid;
delete from RbacGrants as g
where g.ascendantUuid = userUuid and g.descendantUuid = grantedRoleUuid
and g.userGrantsByRoleUuid = revokeRoleFromUser.userGrantsByRoleUuid;
and g.grantedByRoleUuid = revokeRoleFromUser.grantedByRoleUuid;
end; $$;
--//

View File

@ -139,7 +139,6 @@ begin
raise exception '[401] currentUserUuid cannot be determined, please call `defineContext(...)` first;"';
end if;
end if;
raise notice 'currentUserUuid %', currentUserUuid;
return currentUserUuid::uuid;
end; $$;
--//

View File

@ -60,14 +60,14 @@ create or replace view rbacgrants_ev as
go.objectTable || '#' || findIdNameByObjectUuid(go.objectTable, go.uuid) || '.' || r.roletype as grantedByRoleIdName,
x.ascendingIdName as ascendantIdName,
x.descendingIdName as descendantIdName,
x.userGrantsByRoleUuid,
x.grantedByRoleUuid,
x.ascendantUuid as ascendantUuid,
x.descendantUuid as descendantUuid,
x.assumed
from (
select g.uuid as grantUuid,
g.grantedbytriggerof as grantedbytriggerof,
g.userGrantsByRoleUuid, g.ascendantuuid, g.descendantuuid, g.assumed,
g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
coalesce(
'user ' || au.name,
@ -91,7 +91,7 @@ create or replace view rbacgrants_ev as
left outer join rbacpermission dp on dp.uuid = g.descendantUuid
left outer join rbacobject as dpo on dpo.uuid = dp.objectUuid
) as x
left outer join rbacrole as r on r.uuid = userGrantsByRoleUuid
left outer join rbacrole as r on r.uuid = grantedByRoleUuid
left outer join rbacuser u on u.uuid = x.ascendantuuid
left outer join rbacobject go on go.uuid = r.objectuuid
@ -112,10 +112,10 @@ create or replace view rbacgrants_rv as
-- @formatter:off
select o.objectTable || '#' || findIdNameByObjectUuid(o.objectTable, o.uuid) || '.' || r.roletype as grantedByRoleIdName,
g.objectTable || '#' || g.objectIdName || '.' || g.roletype as grantedRoleIdName, g.userName, g.assumed,
g.userGrantsByRoleUuid, g.descendantUuid as grantedRoleUuid, g.ascendantUuid as userUuid,
g.grantedByRoleUuid, g.descendantUuid as grantedRoleUuid, g.ascendantUuid as userUuid,
g.objectTable, g.objectUuid, g.objectIdName, g.roleType as grantedRoleType
from (
select g.userGrantsByRoleUuid, g.ascendantuuid, g.descendantuuid, g.assumed,
select g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
u.name as userName, o.objecttable, r.objectuuid, r.roletype,
findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
from rbacgrants as g
@ -124,7 +124,7 @@ select o.objectTable || '#' || findIdNameByObjectUuid(o.objectTable, o.uuid) ||
left outer join rbacuser u on u.uuid = g.ascendantuuid
where isGranted(currentSubjectsUuids(), r.uuid)
) as g
join RbacRole as r on r.uuid = userGrantsByRoleUuid
join RbacRole as r on r.uuid = grantedByRoleUuid
join RbacObject as o on o.uuid = r.objectUuid
order by grantedRoleIdName;
-- @formatter:on
@ -177,7 +177,7 @@ create or replace function deleteRbacGrant()
returns trigger
language plpgsql as $$
begin
call revokeRoleFromUser(old.userGrantsByRoleUuid, old.grantedRoleUuid, old.userUuid);
call revokeRoleFromUser(old.grantedByRoleUuid, old.grantedRoleUuid, old.userUuid);
return old;
end; $$;

View File

@ -1,6 +1,6 @@
### rbac customer
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.425403022.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
@ -21,6 +21,7 @@ subgraph customer["`**customer**`"]
subgraph customer:permissions[ ]
style customer:permissions fill:#dd4901,stroke:white
perm:customer:INSERT{{customer:INSERT}}
perm:customer:DELETE{{customer:DELETE}}
perm:customer:UPDATE{{customer:UPDATE}}
perm:customer:SELECT{{customer:SELECT}}
@ -36,6 +37,7 @@ role:customer:owner ==> role:customer:admin
role:customer:admin ==> role:customer:tenant
%% granting permissions to roles
role:global:admin ==> perm:customer:INSERT
role:customer:owner ==> perm:customer:DELETE
role:customer:admin ==> perm:customer:UPDATE
role:customer:tenant ==> perm:customer:SELECT

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.441879428.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -80,6 +80,46 @@ execute procedure insertTriggerForTestCustomer_tf();
--changeset test-customer-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO test_customer permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO test_customer permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalAdmin());
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_customer');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
END;
$$;
/**
Adds test_customer INSERT permission to specified role of new global rows.
*/
create or replace function test_customer_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'test_customer'),
globalAdmin());
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_test_customer_global_insert_tg
after insert on global
for each row
execute procedure test_customer_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to test_customer,
where only global-admin has that permission.

View File

@ -1,6 +1,6 @@
### rbac package
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.484173294.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T12:01:44.554331877.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -195,32 +195,22 @@ execute procedure test_package_test_customer_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to test_package,
where the check is performed by an indirect role.
where the check is performed by a direct role.
An indirect role is a role FIXME.
A direct role is a role depending on a foreign key directly available in the NEW row.
*/
create or replace function test_package_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
if ( not hasInsertPermission(
( SELECT customer.uuid FROM
(SELECT * FROM test_customer c
WHERE c.uuid= NEW.customerUuid
) AS customer
), 'INSERT', 'test_package') ) then
raise exception
'[403] insert into test_package not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
raise exception '[403] insert into test_package not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger test_package_insert_permission_check_tg
before insert on test_package
for each row
when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') )
execute procedure test_package_insert_permission_missing_tf();
--//

View File

@ -1,6 +1,6 @@
### rbac domain
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.510830235.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.645391647.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset test-domain-rbac-OBJECT:1 endDelimiter:--//
@ -33,9 +34,12 @@ declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM test_package p
WHERE p.uuid= NEW.packageUuid
into newPackage;
INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
perform createRoleWithGrants(
testDomainOwner(NEW),
@ -71,9 +75,9 @@ create trigger insertTriggerForTestDomain_tg
after insert on test_domain
for each row
execute procedure insertTriggerForTestDomain_tf();
--//
-- ============================================================================
--changeset test-domain-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
@ -97,14 +101,18 @@ begin
SELECT * FROM test_package p
WHERE p.uuid= OLD.packageUuid
into oldPackage;
INTO oldPackage;
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
SELECT * FROM test_package p
WHERE p.uuid= NEW.packageUuid
into newPackage;
INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
if NEW.packageUuid <> OLD.packageUuid then
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage));
call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage));
@ -137,9 +145,9 @@ create trigger updateTriggerForTestDomain_tg
after update on test_domain
for each row
execute procedure updateTriggerForTestDomain_tf();
--//
-- ============================================================================
--changeset test-domain-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
@ -178,13 +186,17 @@ begin
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_test_domain_test_package_insert_tg
after insert on test_package
for each row
execute procedure test_domain_test_package_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to test_domain.
Checks if the user or assumed roles are allowed to insert a row to test_domain,
where the check is performed by a direct role.
A direct role is a role depending on a foreign key directly available in the NEW row.
*/
create or replace function test_domain_insert_permission_missing_tf()
returns trigger
@ -199,8 +211,8 @@ create trigger test_domain_insert_permission_check_tg
for each row
when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') )
execute procedure test_domain_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
@ -208,13 +220,15 @@ create trigger test_domain_insert_permission_check_tg
call generateRbacIdentityViewFromProjection('test_domain', $idName$
name
$idName$);
--//
-- ============================================================================
--changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_domain',
'name',
$orderBy$
name
$orderBy$,
$updates$
version = new.version,
packageUuid = new.packageUuid,
@ -222,4 +236,3 @@ call generateRbacRestrictedView('test_domain',
$updates$);
--//

View File

@ -1,6 +1,6 @@
### rbac contact
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-14T09:00:15.762621659.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-14T09:00:15.769718298.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -109,26 +109,16 @@ create or replace function hs_office_contact_global_insert_tf()
strict as $$
begin
call grantPermissionToRole(
globalGuest(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_contact'));
createPermission(NEW.uuid, 'INSERT', 'hs_office_contact'),
globalGuest());
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_contact_global_insert_tg
after insert on global
for each row
execute procedure hs_office_contact_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_contact.
*/
create or replace function hs_office_contact_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_contact not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
--//
-- ============================================================================
@ -148,7 +138,7 @@ call generateRbacRestrictedView('hs_office_contact',
label
$orderBy$,
$updates$
label = new.label,
label = new.label,
postalAddress = new.postalAddress,
emailAddresses = new.emailAddresses,
phoneNumbers = new.phoneNumbers

View File

@ -1,6 +1,6 @@
### rbac person
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T13:35:44.716916229.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T13:35:44.726508114.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -142,15 +142,6 @@ call generateRbacRestrictedView('hs_office_person',
tradeName = new.tradeName,
givenName = new.givenName,
familyName = new.familyName
$updates$
,
$columns$
uuid,
personType,
tradeName,
givenName,
familyName
$columns$
);
$updates$);
--//

View File

@ -1,6 +1,6 @@
### rbac relation
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-15T17:17:00.854621634.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-15T17:17:00.864301165.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -65,21 +65,21 @@ begin
perform createRoleWithGrants(
hsOfficeRelationAgent(NEW),
incomingSuperRoles => array[
hsOfficePersonAdmin(newHolderPerson),
hsOfficeRelationAdmin(NEW)]
hsOfficeRelationAdmin(NEW),
hsOfficePersonAdmin(newHolderPerson)]
);
perform createRoleWithGrants(
hsOfficeRelationTenant(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeRelationAgent(NEW),
hsOfficePersonAdmin(newHolderPerson),
hsOfficeContactAdmin(newContact),
hsOfficePersonAdmin(newHolderPerson)],
hsOfficeRelationAgent(NEW)],
outgoingSubRoles => array[
hsOfficeContactReferrer(newContact),
hsOfficePersonReferrer(newHolderPerson),
hsOfficePersonReferrer(newAnchorPerson)]
hsOfficePersonReferrer(newAnchorPerson),
hsOfficeContactReferrer(newContact)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
@ -220,34 +220,30 @@ begin
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_relation_hs_office_person_insert_tg
after insert on hs_office_person
for each row
execute procedure hs_office_relation_hs_office_person_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_relation.
Checks if the user or assumed roles are allowed to insert a row to hs_office_relation,
where the check is performed by a direct role.
A direct role is a role depending on a foreign key directly available in the NEW row.
*/
create or replace function hs_office_relation_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
if ( not hasInsertPermission(
( SELECT anchorPerson.uuid FROM
(select * from hs_office_person as p where p.uuid = NEW.anchorUuid) AS anchorPerson
), 'INSERT', 'hs_office_relation') ) then
raise exception
'[403] insert into hs_office_relation not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
raise exception '[403] insert into hs_office_relation not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_relation_insert_permission_check_tg
before insert on hs_office_relation
for each row
when ( not hasInsertPermission(NEW.anchorUuid, 'INSERT', 'hs_office_relation') )
execute procedure hs_office_relation_insert_permission_missing_tf();
--//
@ -271,7 +267,7 @@ call generateRbacRestrictedView('hs_office_relation',
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)
$orderBy$,
$updates$
contactUuid = new.contactUuid
contactUuid = new.contactUuid
$updates$);
--//

View File

@ -1,6 +1,6 @@
### rbac partner
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T09:31:47.361311186.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T09:31:47.368892199.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -131,36 +131,6 @@ begin
end if;
call leaveTriggerForObjectUuid(NEW.uuid);
-- raise exception 'RBAC updated from rel % to %', OLD.partnerReluuid, NEW.partnerReluuid;
end; $$;
create or replace procedure updateRbacRulesForHsOfficePartnerX(
OLD hs_office_partner,
NEW hs_office_partner
)
language plpgsql as $$
declare
partnerRel hs_office_relation;
grantCount int;
begin
assert OLD.uuid = NEW.uuid, 'uuid did change, but should not';
assert OLD.partnerReluuid <> NEW.partnerReluuid, 'partnerReluuid did not change, but should have';
delete from rbacgrants where grantedbytriggerof = OLD.uuid;
select count(*) from rbacgrants where grantedbytriggerof=NEW.uuid into grantCount;
assert grantCount=0, format('unexpected grantCount>0: %d', grantCount);
call buildRbacSystemForHsOfficePartner(NEW);
select * from hs_office_relation where uuid=NEW.partnerReluuid into partnerRel;
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(partnerRel));
select count(*) from rbacgrants where grantedbytriggerof=NEW.uuid into grantCount;
assert grantCount>0, format('unexpected grantCount=0: %d', grantCount);
raise warning 'WARNING grantCount=%', grantCount;
end; $$;
/*
@ -172,7 +142,7 @@ create or replace function updateTriggerForHsOfficePartner_tf()
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficePartnerX(OLD, NEW);
call updateRbacRulesForHsOfficePartner(OLD, NEW);
return NEW;
end; $$;
@ -228,7 +198,8 @@ create trigger z_hs_office_partner_global_insert_tg
execute procedure hs_office_partner_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner.
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_insert_permission_missing_tf()
returns trigger
@ -262,7 +233,7 @@ call generateRbacRestrictedView('hs_office_partner',
'P-' || partnerNumber
$orderBy$,
$updates$
partnerRelUuid = new.partnerRelUuid
partnerRelUuid = new.partnerRelUuid
$updates$);
--//

View File

@ -1,6 +1,6 @@
### rbac partnerDetails
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-16T12:04:37.309540020.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-16T12:04:37.319601283.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -102,7 +102,8 @@ create trigger z_hs_office_partner_details_global_insert_tg
execute procedure hs_office_partner_details_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner_details.
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner_details,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_details_insert_permission_missing_tf()
returns trigger
@ -140,7 +141,7 @@ call generateRbacRestrictedView('hs_office_partner_details',
uuid
$orderBy$,
$updates$
registrationOffice = new.registrationOffice,
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthPlace = new.birthPlace,
birthName = new.birthName,

View File

@ -1,6 +1,6 @@
### rbac bankAccount
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-14T08:55:11.118624882.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-14T08:55:11.127959896.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -109,26 +109,16 @@ create or replace function hs_office_bankaccount_global_insert_tf()
strict as $$
begin
call grantPermissionToRole(
globalGuest(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_bankaccount'));
createPermission(NEW.uuid, 'INSERT', 'hs_office_bankaccount'),
globalGuest());
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_bankaccount_global_insert_tg
after insert on global
for each row
execute procedure hs_office_bankaccount_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_bankaccount.
*/
create or replace function hs_office_bankaccount_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_bankaccount not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
--//
-- ============================================================================
@ -148,7 +138,7 @@ call generateRbacRestrictedView('hs_office_bankaccount',
iban
$orderBy$,
$updates$
holder = new.holder,
holder = new.holder,
iban = new.iban,
bic = new.bic
$updates$);

View File

@ -1,6 +1,6 @@
### rbac sepaMandate
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T16:07:14.011240343.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T16:07:14.019894954.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -64,8 +64,8 @@ begin
hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
outgoingSubRoles => array[
hsOfficeRelationAgent(newDebitorRel),
hsOfficeBankAccountReferrer(newBankAccount)]
hsOfficeBankAccountReferrer(newBankAccount),
hsOfficeRelationAgent(newDebitorRel)]
);
perform createRoleWithGrants(
@ -146,7 +146,10 @@ create trigger z_hs_office_sepamandate_hs_office_relation_insert_tg
execute procedure hs_office_sepamandate_hs_office_relation_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate.
Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate,
where the check is performed by an indirect role.
An indirect role is a role FIXME.
*/
create or replace function hs_office_sepamandate_insert_permission_missing_tf()
returns trigger
@ -195,7 +198,7 @@ call generateRbacRestrictedView('hs_office_sepamandate',
validity
$orderBy$,
$updates$
reference = new.reference,
reference = new.reference,
agreement = new.agreement,
validity = new.validity
$updates$);

View File

@ -1,6 +1,6 @@
### rbac debitor
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-16T13:52:18.484919583.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-20T13:55:16.722860098.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -177,7 +177,8 @@ create trigger z_hs_office_debitor_global_insert_tg
execute procedure hs_office_debitor_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_debitor.
Checks if the user or assumed roles are allowed to insert a row to hs_office_debitor,
where only global-admin has that permission.
*/
create or replace function hs_office_debitor_insert_permission_missing_tf()
returns trigger
@ -221,7 +222,7 @@ call generateRbacRestrictedView('hs_office_debitor',
defaultPrefix
$orderBy$,
$updates$
debitorRelUuid = new.debitorRelUuid,
debitorRelUuid = new.debitorRelUuid,
billable = new.billable,
refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId,

View File

@ -1,6 +1,6 @@
### rbac membership
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T17:09:08.826781619.
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T17:09:08.832004329.
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
@ -54,8 +54,8 @@ begin
hsOfficeMembershipAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficeMembershipOwner(NEW),
hsOfficeRelationAgent(newPartnerRel)]
hsOfficeRelationAgent(newPartnerRel),
hsOfficeMembershipOwner(NEW)]
);
perform createRoleWithGrants(
@ -133,7 +133,10 @@ create trigger z_hs_office_membership_hs_office_relation_insert_tg
execute procedure hs_office_membership_hs_office_relation_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_membership.
Checks if the user or assumed roles are allowed to insert a row to hs_office_membership,
where the check is performed by an indirect role.
An indirect role is a role FIXME.
*/
create or replace function hs_office_membership_insert_permission_missing_tf()
returns trigger
@ -183,7 +186,7 @@ call generateRbacRestrictedView('hs_office_membership',
validity
$orderBy$,
$updates$
validity = new.validity,
validity = new.validity,
membershipFeeBillable = new.membershipFeeBillable,
reasonForTermination = new.reasonForTermination
$updates$);

View File

@ -4,8 +4,9 @@ spring:
platform: postgres
datasource:
url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url-tc: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url-local: jdbc:postgresql://localhost:5432/postgres
url: ${spring.datasource.url-tc}
username: postgres
password: password