This commit is contained in:
Michael Hoennig 2024-03-12 08:31:50 +01:00
parent e422db9081
commit 9788205724
26 changed files with 867 additions and 1008 deletions

View File

@ -59,8 +59,11 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class) return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.projection("iban || ':' || holder")) .withIdentityView(SQL.projection("concat(iban, ':', holder)"))
.withUpdatableColumns("holder", "iban", "bic") .withUpdatableColumns("holder", "iban", "bic")
.toRole("global", GUEST).grantPermission("bankAccount", INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR); with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
@ -75,6 +78,6 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac-generated"); rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac");
} }
} }

View File

@ -78,6 +78,8 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
return rbacViewFor("person", HsOfficePersonEntity.class) return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)")) .withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName") .withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.toRole("global", GUEST).grantPermission("person", INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.permission(DELETE); with.permission(DELETE);
with.owningUser(CREATOR); with.owningUser(CREATOR);
@ -93,6 +95,6 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("213-hs-office-person-rbac-generated"); rbac().generateWithBaseFileName("213-hs-office-person-rbac");
} }
} }

View File

@ -95,7 +95,12 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class) return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
.withIdentityView(projection("concat(tradeName, familyName, givenName)")) .withIdentityView(query("""
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
from hs_office_sepamandate sm
join hs_office_bankaccount ba on ba.uuid = sm.bankAccountUuid
"""))
.withRestrictedViewOrderBy(expression("validity"))
.withUpdatableColumns("reference", "agreement", "validity") .withUpdatableColumns("reference", "agreement", "validity")
.importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid")) .importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid"))
@ -111,17 +116,17 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
}) })
.createSubRole(AGENT, (with) -> { .createSubRole(AGENT, (with) -> {
with.outgoingSubRole("bankAccount", REFERRER); with.outgoingSubRole("bankAccount", REFERRER);
with.outgoingSubRole("debitorRel", AGENT); with.outgoingSubRole("debitor", AGENT);
}) })
.createSubRole(REFERRER, (with) -> { .createSubRole(REFERRER, (with) -> {
with.incomingSuperRole("bankAccount", ADMIN); with.incomingSuperRole("bankAccount", ADMIN);
with.incomingSuperRole("debitorRel", AGENT); with.incomingSuperRole("debitor", AGENT);
with.outgoingSubRole("debitorRel", TENANT); with.outgoingSubRole("debitorRel", TENANT);
with.permission(SELECT); with.permission(SELECT);
}); });
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac-generated"); rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac");
} }
} }

View File

@ -53,7 +53,7 @@ public class InsertTriggerGenerator {
FOR row IN SELECT * FROM ${rawSuperTableName} FOR row IN SELECT * FROM ${rawSuperTableName}
LOOP LOOP
roleUuid := findRoleId(${rawSuperRoleDescriptor}(row)); roleUuid := findRoleId(${rawSuperRoleDescriptor});
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}');
call grantPermissionToRole(roleUuid, permissionUuid); call grantPermissionToRole(roleUuid, permissionUuid);
END LOOP; END LOOP;
@ -62,7 +62,7 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef)) with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row"))
); );
}); });
} }
@ -79,7 +79,7 @@ public class InsertTriggerGenerator {
strict as $$ strict as $$
begin begin
call grantPermissionToRole( call grantPermissionToRole(
${rawSuperRoleDescriptor}(NEW), ${rawSuperRoleDescriptor},
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}')); createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'));
return NEW; return NEW;
end; $$; end; $$;
@ -91,7 +91,7 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef)) with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, PostgresTriggerReference.NEW.name()))
); );
}); });
} }
@ -111,7 +111,9 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
getOptionalInsertGrant().ifPresentOrElse(g -> { getOptionalInsertGrant().ifPresentOrElse(g -> {
plPgSql.writeLn(""" if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) {
plPgSql.writeLn(
"""
create trigger ${rawSubTable}_insert_permission_check_tg create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable} before insert on ${rawSubTable}
for each row for each row
@ -120,6 +122,28 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName()));
} else {
switch (g.getSuperRoleDef().getRole()) {
case ADMIN -> {
plPgSql.writeLn(
"""
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
when ( not isGlobalAdmin() )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}
case GUEST -> {
// no permission check trigger generated, as anybody can insert rows into this table
}
default -> {
throw new IllegalArgumentException(
"invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole());
}
}
}
}, },
() -> { () -> {
plPgSql.writeLn(""" plPgSql.writeLn("""
@ -162,4 +186,12 @@ public class InsertTriggerGenerator {
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
} }
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
final var functionName = toVar(roleDef);
if (roleDef.getEntityAlias().isGlobal()) {
return functionName + "()";
}
return functionName + "(" + ref + ")";
}
} }

View File

@ -31,7 +31,7 @@ public class RbacIdentityViewGenerator {
$idName$); $idName$);
"""; """;
case SQL_QUERY -> """ case SQL_QUERY -> """
call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$ call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$
${identityViewSqlPart} ${identityViewSqlPart}
$idName$); $idName$);
"""; """;

View File

@ -24,7 +24,9 @@ public class RbacRestrictedViewGenerator {
--changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('${rawTableName}', call generateRbacRestrictedView('${rawTableName}',
'${orderBy}', $orderBy$
${orderBy}
$orderBy$,
$updates$ $updates$
${updates} ${updates}
$updates$); $updates$);

View File

@ -626,22 +626,23 @@ public class RbacView {
return tableName.substring(0, tableName.length() - "_rv".length()); return tableName.substring(0, tableName.length() - "_rv".length());
} }
public record Role(String roleName) { public enum Role {
public static final Role OWNER = new Role("owner"); OWNER,
public static final Role ADMIN = new Role("admin"); ADMIN,
public static final Role AGENT = new Role("agent"); AGENT,
public static final Role TENANT = new Role("tenant"); TENANT,
public static final Role REFERRER = new Role("referrer"); REFERRER,
GUEST;
@Override @Override
public String toString() { public String toString() {
return ":" + roleName; return ":" + roleName();
} }
@Override String roleName() {
public boolean equals(final Object obj) { return name().toLowerCase();
return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName));
} }
} }

View File

@ -103,7 +103,7 @@ public class RbacGrantController implements RbacGrantsApi {
// public ResponseEntity<String> allGrantsOfUserAsMermaid( // public ResponseEntity<String> allGrantsOfUserAsMermaid(
// @RequestHeader(name = "current-user") String currentUser, // @RequestHeader(name = "current-user") String currentUser,
// @RequestHeader(name = "assumed-roles", required = false) String assumedRoles) { // @RequestHeader(name = "assumed-roles", required = false) String assumedRoles) {
// final var graph = RbacGrantsMermaidService.allGrantsToUser(currentUser); // final var graph = RbacGrantsDiagramService.allGrantsToUser(currentUser);
// return ResponseEntity.ok(graph); // return ResponseEntity.ok(graph);
// } // }

View File

@ -1,204 +0,0 @@
package net.hostsharing.hsadminng.rbac.rbacgrant;
import net.hostsharing.hsadminng.context.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.validation.constraints.NotNull;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService.Include.*;
// TODO: cleanup - this code was 'hacked' to quickly fix a specific problem, needs refactoring
@Service
public class RbacGrantsMermaidService {
public static void writeToFile(final String title, final String graph, final String fileName) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
writer.write("""
### all grants to %s
```mermaid
%s
```
""".formatted(title, graph));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public enum Include {
DETAILS,
USERS,
PERMISSIONS,
NOT_ASSUMED,
TEST_ENTITIES,
NON_TEST_ENTITIES
}
@Autowired
private Context context;
@Autowired
private RawRbacGrantRepository rawGrantRepo;
@PersistenceContext
private EntityManager em;
public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new HashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
traverseGrantsTo(graph, subjectUuid, includes);
}
return toMermaidFlowchart(graph, includes);
}
private void traverseGrantsTo(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> includes) {
final var grants = rawGrantRepo.findByAscendingUuid(refUuid);
grants.forEach(g -> {
if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) {
return;
}
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(" test_")) {
return;
}
if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(" test_")) {
return;
}
graph.add(g);
if (includes.contains(NOT_ASSUMED) || g.isAssumed()) {
traverseGrantsTo(graph, g.getDescendantUuid(), includes);
}
});
}
public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet<Include> includes) {
final var refUuid = (UUID) em.createNativeQuery("SELECT uuid FROM rbacpermission WHERE objectuuid=:targetObject AND op=:op")
.setParameter("targetObject", targetObject)
.setParameter("op", op)
.getSingleResult();
final var graph = new HashSet<RawRbacGrantEntity>();
traverseGrantsFrom(graph, refUuid, includes);
return toMermaidFlowchart(graph, includes);
}
private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> option) {
final var grants = rawGrantRepo.findByDescendantUuid(refUuid);
grants.forEach(g -> {
if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user ")) {
return;
}
graph.add(g);
if (option.contains(NOT_ASSUMED) || g.isAssumed()) {
traverseGrantsFrom(graph, g.getAscendingUuid(), option);
}
});
}
private String toMermaidFlowchart(final HashSet<RawRbacGrantEntity> graph, final EnumSet<Include> includes) {
final var entities =
includes.contains(DETAILS)
? graph.stream()
.flatMap(g -> Stream.of(
new Node(g.getAscendantIdName(), g.getAscendingUuid()),
new Node(g.getDescendantIdName(), g.getDescendantUuid()))
)
.collect(groupingBy(RbacGrantsMermaidService::renderEntityIdName))
.entrySet().stream()
.map(entity -> "subgraph " + quoted(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
+ entity.getValue().stream()
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
.sorted()
.distinct()
.collect(joining("\n\n ")))
.collect(joining("\n\nend\n\n"))
+ "\n\nend\n\n"
: "";
final var grants = graph.stream()
.map(g -> quoted(g.getAscendantIdName()) +
(g.isAssumed() ? " --> " : " -.-> ") +
quoted(g.getDescendantIdName()))
.sorted()
.collect(joining("\n"));
final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n";
return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "")
+ "flowchart TB\n\n"
+ entities
+ grants;
}
private String renderSubgraph(final String entityId) {
// this does not work according to Mermaid bug https://github.com/mermaid-js/mermaid/issues/3806
// if (entityId.contains("#")) {
// final var parts = entityId.split("#");
// final var table = parts[0];
// final var entity = parts[1];
// if (table.equals("entity")) {
// return "[" + entity "]";
// }
// return "[" + table + "\n" + entity + "]";
// }
return "[" + entityId + "]";
}
private static String renderEntityIdName(final Node node) {
final var refType = refType(node.idName());
if (refType.equals("user")) {
return "users";
}
if (refType.equals("perm")) {
return node.idName().split(" ", 4)[3];
}
if (refType.equals("role")) {
final var withoutRolePrefix = node.idName().substring("role:".length());
return withoutRolePrefix.substring(0, withoutRolePrefix.lastIndexOf('.'));
}
throw new IllegalArgumentException("unknown refType '" + refType + "' in '" + node.idName() + "'");
}
private String renderNode(final String idName, final UUID uuid) {
return quoted(idName) + renderNodeContent(idName, uuid);
}
private String renderNodeContent(final String idName, final UUID uuid) {
final var refType = refType(idName);
if (refType.equals("user")) {
final var displayName = idName.substring(refType.length()+1);
return "(" + displayName + "\nref:" + uuid + ")";
}
if (refType.equals("role")) {
final var roleType = idName.substring(idName.lastIndexOf('.') + 1);
return "[" + roleType + "\nref:" + uuid + "]";
}
if (refType.equals("perm")) {
final var roleType = idName.split(" ")[1];
return "{{" + roleType + "\nref:" + uuid + "}}";
}
return "";
}
private static String refType(final String idName) {
return idName.split(" ", 2)[0];
}
@NotNull
private static String quoted(final String idName) {
return idName.replace(" ", ":").replaceAll("@.*", "");
}
}
record Node(String idName, UUID uuid) {
}

View File

@ -41,8 +41,7 @@ public class TestCustomerEntity implements HasUuid {
.withIdentityView(SQL.projection("prefix")) .withIdentityView(SQL.projection("prefix"))
.withRestrictedViewOrderBy(SQL.expression("reference")) .withRestrictedViewOrderBy(SQL.expression("reference"))
.withUpdatableColumns("reference", "prefix", "adminUserName") .withUpdatableColumns("reference", "prefix", "adminUserName")
// TODO: do we want explicit specification of parent-independent insert permissions? .toRole("global", ADMIN).grantPermission("customer", INSERT)
// .toRole("global", ADMIN).grantPermission("customer", INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR).unassumed(); with.owningUser(CREATOR).unassumed();

View File

@ -373,10 +373,12 @@ 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 varchar(60), opTableName varchar(60)
unique (objectUuid, op)
); );
ALTER TABLE RbacPermission
ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
call create_journal('RbacPermission'); call create_journal('RbacPermission');
create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
@ -469,23 +471,6 @@ $$;
--// --//
-- ============================================================================
--changeset rbac-base-duplicate-role-grant-exception:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure raiseDuplicateRoleGrantException(subRoleId uuid, superRoleId uuid)
language plpgsql as $$
declare
subRoleIdName text;
superRoleIdName text;
begin
select roleIdName from rbacRole_ev where uuid=subRoleId into subRoleIdName;
select roleIdName from rbacRole_ev where uuid=superRoleId into superRoleIdName;
raise exception '[400] Duplicate role grant detected: role % (%) already granted to % (%)', subRoleId, subRoleIdName, superRoleId, superRoleIdName;
end;
$$;
--//
-- ============================================================================ -- ============================================================================
--changeset rbac-base-duplicate-role-grant-exception:1 endDelimiter:--// --changeset rbac-base-duplicate-role-grant-exception:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -121,6 +121,29 @@ begin transaction;
call defineContext('creating global admin role', null, null, null); call defineContext('creating global admin role', null, null, null);
select createRole(globalAdmin()); select createRole(globalAdmin());
commit; commit;
--//
-- ============================================================================
--changeset rbac-global-GUEST-ROLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
A global guest role.
*/
create or replace function globalGuest(assumed boolean = true)
returns RbacRoleDescriptor
returns null on null input
stable -- leakproof
language sql as $$
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'guest'::RbacRoleType, assumed;
$$;
begin transaction;
call defineContext('creating global guest role', null, null, null);
select createRole(globalGuest());
commit;
--//
-- ============================================================================ -- ============================================================================
--changeset rbac-global-ADMIN-USERS:1 context:dev,tc endDelimiter:--// --changeset rbac-global-ADMIN-USERS:1 context:dev,tc endDelimiter:--//

View File

@ -1,4 +1,5 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T15:13:04.479330676.
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--//
@ -15,29 +16,30 @@ call generateRbacRoleDescriptors('hsOfficePerson', 'hs_office_person');
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-person-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new person for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficePerson()
returns trigger create or replace procedure buildRbacSystemForHsOfficePerson(
language plpgsql NEW hs_office_person
strict as $$ )
language plpgsql as $$
declare
begin begin
if TG_OP <> 'INSERT' then call enterTriggerForObjectUuid(NEW.uuid);
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficePersonOwner(NEW), hsOfficePersonOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()], userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin() incomingSuperRoles => array[globalAdmin()]
); );
-- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to update the data?
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficePersonAdmin(NEW), hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
@ -50,34 +52,98 @@ begin
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)] incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
); );
return NEW; call leaveTriggerForObjectUuid(NEW.uuid);
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_person row.
*/ */
create trigger createRbacRolesForHsOfficePerson_Trigger create or replace function insertTriggerForHsOfficePerson_tf()
after insert returns trigger
on hs_office_person language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficePerson(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePerson_tg
after insert on hs_office_person
for each row for each row
execute procedure createRbacRolesForHsOfficePerson(); execute procedure insertTriggerForHsOfficePerson_tf();
--// --//
-- ============================================================================
--changeset hs-office-person-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_person permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_person permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalGuest());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_person');
call grantPermissionToRole(roleUuid, permissionUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_person INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_person_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
globalGuest(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_person'));
return NEW;
end; $$;
create trigger hs_office_person_global_insert_tg
after insert on global
for each row
execute procedure hs_office_person_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_person.
*/
create or replace function hs_office_person_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_person not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_person', $idName$ call generateRbacIdentityViewFromProjection('hs_office_person', $idName$
concat(target.tradeName, target.familyName, target.givenName) concat(tradeName, familyName, givenName)
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, target.familyName, target.givenName)', call generateRbacRestrictedView('hs_office_person',
'concat(tradeName, familyName, givenName)',
$updates$ $updates$
personType = new.personType, personType = new.personType,
tradeName = new.tradeName, tradeName = new.tradeName,
@ -87,48 +153,3 @@ call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, ta
--// --//
-- ============================================================================
--changeset hs-office-person-rbac-NEW-PERSON:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-person and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-person permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-person']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficePersonNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-person not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_person_insert_trigger
before insert
on hs_office_person
for each row
-- TODO.spec: who is allowed to create new persons
when ( not hasAssumedRole() )
execute procedure addHsOfficePersonNotAllowedForCurrentSubjects();
--//

View File

@ -27,71 +27,77 @@ create or replace function hsOfficeRelationshipRbacRolesTrigger()
language plpgsql language plpgsql
strict as $$ strict as $$
declare declare
hsOfficeRelationshipTenant RbacRoleDescriptor; newAnchorPerson hs_office_person;
newRelAnchor hs_office_person; newHolderPerson hs_office_person;
newRelHolder hs_office_person;
oldContact hs_office_contact; oldContact hs_office_contact;
newContact hs_office_contact; newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW); select * from hs_office_person as p where p.uuid = NEW.relAnchorUuid into newAnchorPerson;
select * from hs_office_person as p where p.uuid = NEW.relHolderUuid into newHolderPerson;
select * from hs_office_person as p where p.uuid = NEW.relAnchorUuid into newRelAnchor;
select * from hs_office_person as p where p.uuid = NEW.relHolderUuid into newRelHolder;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact; select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
if TG_OP = 'INSERT' then if TG_OP = 'INSERT' then
-- cannot be generated using `tools/generate` because there are multiple grants to the same entity type
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationshipOwner(NEW), hsOfficeRelationshipOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
incomingSuperRoles => array[ incomingSuperRoles => array[
globalAdmin(), globalAdmin()
hsOfficePersonAdmin(newRelAnchor)] ]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationshipAdmin(NEW), hsOfficeRelationshipAdmin(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeRelationshipOwner(NEW)] incomingSuperRoles => array[
hsOfficeRelationshipOwner(NEW),
hsOfficePersonAdmin(newAnchorPerson)
]
); );
-- the tenant role for those related users who can view the data
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationshipTenant, hsOfficeRelationshipAgent(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficeRelationshipAdmin(NEW), hsOfficeRelationshipAdmin(NEW),
hsOfficePersonAdmin(newRelAnchor), hsOfficePersonAdmin(newHolderPerson),
hsOfficePersonAdmin(newRelHolder), hsOfficeContactAdmin(newContact)
hsOfficeContactAdmin(newContact)], ]
outgoingSubRoles => array[
hsOfficePersonTenant(newRelAnchor),
hsOfficePersonTenant(newRelHolder),
hsOfficeContactTenant(newContact)]
); );
-- anchor and holder admin roles need each others tenant role perform createRoleWithGrants(
-- to be able to see the joined relationship hsOfficeRelationshipTenant(NEW),
-- TODO: this can probably be avoided through agent+guest roles permissions => array['SELECT'],
call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder)); incomingSuperRoles => array[
call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor)); hsOfficeRelationshipAgent(NEW)
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact)); ],
outgoingSubRoles => array[
hsOfficePersonReferrer(newAnchorPerson),
hsOfficePersonReferrer(newHolderPerson),
hsOfficeContactReferrer(newContact)
]
);
if ( NEW.relType = 'REPRESENTATIVE' ) then
call grantRoleToRole(hsOfficePersonAdmin(newHolderPerson), hsOfficePersonAdmin(newAnchorPerson));
end if;
elsif TG_OP = 'UPDATE' then elsif TG_OP = 'UPDATE' then
if OLD.contactUuid <> NEW.contactUuid then if OLD.contactUuid <> NEW.contactUuid then
-- nothing but the contact can be updated, -- only the contact can be updated,
-- in other cases, a new relationship needs to be created and the old updated -- in other cases, a new relationship needs to be created and the old updated
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact; select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
call revokeRoleFromRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(oldContact) ); call revokeRoleFromRole( hsOfficeContactReferrer(oldContact), hsOfficeRelationshipTenant(NEW) );
call grantRoleToRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(newContact) ); call grantRoleToRole( hsOfficeContactReferrer(newContact), hsOfficeRelationshipTenant(NEW) );
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationshipTenant ); call revokeRoleFromRole( hsOfficeRelationshipAgent(NEW), hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationshipTenant ); call grantRoleToRole( hsOfficeRelationshipAgent(NEW), hsOfficeContactAdmin(newContact) );
end if; end if;
else else
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';

View File

@ -1,72 +1,158 @@
### hs_office_partner RBAC ### rbac partner
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T15:29:41.494727519.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph external[ ] subgraph partnerRel.contact["`**partnerRel.contact**`"]
style external fill:#fff
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph partnerPerson
style partnerPerson fill:#eee
role:partnerPerson.admin[partnerPerson.admin]
end
subgraph otherRelatedPerson
style otherRelatedPerson fill:#eee
role:otherRelatedPerson.admin[otherRelatedPerson.admin]
end
subgraph hsOfficeRelationship[hsOfficeRelationship:PARTNER]
direction TB direction TB
style hsOfficeRelationship fill:#eee style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:global.admin subgraph partnerRel.contact:roles[ ]
--> role:hsOfficeRelationship.owner[relationship.owner] style partnerRel.contact:roles fill:#99bcdb,stroke:white
--> role:hsOfficeRelationship.admin[relationship.admin]
--> role:hsOfficeRelationship.agent[relationship.agent]
--> role:hsOfficeRelationship.tenant[relationship.tenant]
role:partnerPerson.admin --> role:hsOfficeRelationship.agent role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:otherRelatedPerson.admin --> role:hsOfficeRelationship.tenant role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end end
end end
subgraph internal[ ] subgraph partner["`**partner**`"]
subgraph hsOfficePartner
style hsOfficePartner fill:#fff
perm:hsOfficePartner.*{{partner.*}}
role:hsOfficeRelationship.owner ==> perm:hsOfficePartner.*
perm:hsOfficePartner.edit{{partner.edit}}
role:hsOfficeRelationship.admin ==> perm:hsOfficePartner.edit
perm:hsOfficePartner.view{{partner.view}}
role:hsOfficeRelationship.tenant ==> perm:hsOfficePartner.view
end
subgraph hsOfficePartnerDetails
direction TB direction TB
style hsOfficePartnerDetails fill:#eee style partner fill:#dd4901,stroke:#274d6e,stroke-width:8px
perm:hsOfficePartnerDetails.*{{partnerDetails.*}} subgraph partner:permissions[ ]
role:hsOfficeRelationship.owner ==> perm:hsOfficePartnerDetails.* style partner:permissions fill:#dd4901,stroke:white
perm:hsOfficePartnerDetails.edit{{partnerDetails.edit}} perm:partner:new-partner{{partner:new-partner}}
role:hsOfficeRelationship.agent ==> perm:hsOfficePartnerDetails.edit perm:partner:DELETE{{partner:DELETE}}
role:hsOfficeRelationship.agent ==> perm:hsOfficePartnerDetails.view perm:partner:UPDATE{{partner:UPDATE}}
perm:partner:SELECT{{partner:SELECT}}
perm:hsOfficePartnerDetails.view{{partnerDetails.view}}
end end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
end
subgraph partnerDetails["`**partnerDetails**`"]
direction TB
style partnerDetails fill:#feb28c,stroke:#274d6e,stroke-width:8px
subgraph partnerDetails:permissions[ ]
style partnerDetails:permissions fill:#feb28c,stroke:white
perm:partnerDetails:DELETE{{partnerDetails:DELETE}}
perm:partnerDetails:UPDATE{{partnerDetails:UPDATE}}
perm:partnerDetails:SELECT{{partnerDetails:SELECT}}
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
%% granting permissions to roles
role:global:admin ==> perm:partner:new-partner
role:partnerRel:admin ==> perm:partner:DELETE
role:partnerRel:agent ==> perm:partner:UPDATE
role:partnerRel:tenant ==> perm:partner:SELECT
role:partnerRel:admin ==> perm:partnerDetails:DELETE
role:partnerRel:agent ==> perm:partnerDetails:UPDATE
role:partnerRel:agent ==> perm:partnerDetails:SELECT
``` ```

View File

@ -32,58 +32,7 @@ begin
if TG_OP = 'INSERT' then if TG_OP = 'INSERT' then
-- === ATTENTION: code generated from related Mermaid flowchart: === -- Permissions and Grants for Partner
perform createRoleWithGrants(
hsOfficePartnerOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()]
);
perform createRoleWithGrants(
hsOfficePartnerAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficePartnerOwner(NEW)],
outgoingSubRoles => array[
hsOfficeRelationshipTenant(newPartnerRole),
hsOfficePersonTenant(newPerson),
hsOfficeContactTenant(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerAgent(NEW),
incomingSuperRoles => array[
hsOfficePartnerAdmin(NEW),
hsOfficeRelationshipAdmin(newPartnerRole),
hsOfficePersonAdmin(newPerson),
hsOfficeContactAdmin(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerTenant(NEW),
incomingSuperRoles => array[
hsOfficePartnerAgent(NEW)],
outgoingSubRoles => array[
hsOfficeRelationshipTenant(newPartnerRole),
hsOfficePersonGuest(newPerson),
hsOfficeContactGuest(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePartnerTenant(NEW)]
);
-- === END of code generated from Mermaid flowchart. ===
-- Each partner-details entity belong exactly to one partner entity
-- and it makes little sense just to delegate partner-details roles.
-- Therefore, we did not model partner-details roles,
-- but instead just assign extra permissions to existing partner-roles.
--Attention: Cannot be in partner-details because of insert order (partner is not in database yet)
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel)), getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
@ -96,20 +45,20 @@ begin
); );
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipTenant(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipTenant(newPartnerRel)),
createPermissions(partnerUuid, array ['view']) createPermissions(partnerUuid, array ['SELECT'])
); );
-- Permissions and Grants for PartnerDetails -- Permissions and Grants for PartnerDetails
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
createPermissions(partnerDetailsUuid, array ['*']) createPermissions(partnerDetailsUuid, array ['DELETE'])
); );
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipAdmin(newPartnerRel)),
createPermissions(partnerDetailsUuid, array ['edit']) createPermissions(partnerDetailsUuid, array ['UPDATE'])
); );
call grantPermissionsToRole( call grantPermissionsToRole(
@ -129,7 +78,7 @@ begin
-- Revokes from Partner -- Revokes from Partner
call revokePermissionFromRole( call revokePermissionFromRole(
findPermissionId(partnerUuid, 'view'), findPermissionId(partnerUuid, 'SELECT'),
hsOfficeRelationshipTenant(oldPartnerRel) hsOfficeRelationshipTenant(oldPartnerRel)
); );
@ -146,55 +95,55 @@ begin
-- Grants for Partner -- Grants for Partner
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
array[findPermissionId(partnerUuid, '*')] array[findPermissionId(partnerUuid, 'DELETE')]
); );
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipAdmin(newPartnerRel)),
array[findPermissionId(partnerUuid, 'edit')] array[findPermissionId(partnerUuid, 'UPDATE')]
); );
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipTenant(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipTenant(newPartnerRel)),
array[findPermissionId(partnerUuid, 'view')] array[findPermissionId(partnerUuid, 'SELECT')]
); );
-- Revokes from PartnerDetails -- Revokes from PartnerDetails
-- call revokePermissionFromRole( -- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, 'view'), -- findPermissionId(partnerDetailsUuid, 'SELECT'),
-- hsOfficeRelationshipAgent(oldPartnerRel) -- hsOfficeRelationshipAgent(oldPartnerRel)
-- ); -- );
-- --
-- call revokePermissionFromRole( -- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, 'edit'), -- findPermissionId(partnerDetailsUuid, 'UPDATE'),
-- hsOfficeRelationshipAdmin(oldPartnerRel) -- hsOfficeRelationshipAdmin(oldPartnerRel)
-- ); -- );
-- --
-- call revokePermissionFromRole( -- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, '*'), -- findPermissionId(partnerDetailsUuid, 'DELETE'),
-- hsOfficeRelationshipOwner(oldPartnerRel) -- hsOfficeRelationshipOwner(oldPartnerRel)
-- ); -- );
-- Grants for PartnerDetails -- Grants for PartnerDetails
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
array[findPermissionId(partnerDetailsUuid, '*')] array[findPermissionId(partnerDetailsUuid, 'DELETE')]
); );
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipAdmin(newPartnerRel)),
array[findPermissionId(partnerDetailsUuid, 'edit')] array[findPermissionId(partnerDetailsUuid, 'UPDATE')]
); );
call grantPermissionsToRole( call grantPermissionsToRole(
-- Yes, here hsOfficePartnerAGENT is used, not hsOfficePartnerTENANT. -- Yes, here hsOfficePartnerAGENT is used, not hsOfficePartnerTENANT.
-- Do NOT grant view permission on partner-details to hsOfficeRelationshipTENANT! -- Do NOT grant view permission on partner-details to hsOfficeRelationshipTENANT!
-- Otherwise package-admins etc. would be able to read the data. -- Otherwise package-admins etc. would be able to read the data.
getRoleId(hsOfficeRelationshipAgent(newPartnerRel), 'fail'), getRoleId(hsOfficeRelationshipAgent(newPartnerRel)),
array[findPermissionId(partnerDetailsUuid, 'view')] array[findPermissionId(partnerDetailsUuid, 'SELECT')]
); );
end if; end if;
@ -203,7 +152,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(partnerUuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -1,50 +1,45 @@
### hs_office_bankaccount RBAC Roles ### rbac bankAccount
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T19:09:38.350576842.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
%%% RbacEntity.builder().forEntity(HsOfficeBankAccountEntity.class) subgraph bankAccount["`**bankAccount**`"]
%%% .alias("bankAccount")
%% the global subgraph would get imported implicitly by later usage
subgraph global
style global fill: #eee
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
direction TB direction TB
style hsOfficeBankAccount fill: #e9f7ef style bankAccount fill:#dd4901,stroke:#274d6e,stroke-width:8px
user:hsOfficeBankAccount.creator([bankAccount.creator]) subgraph bankAccount:roles[ ]
style bankAccount:roles fill:#dd4901,stroke:white
%%% .createRole(OWNER) role:bankAccount:owner[[bankAccount:owner]]
role:hsOfficeBankAccount.owner[[bankAccount.owner]] role:bankAccount:admin[[bankAccount:admin]]
%%% .withPermission(ALL) role:bankAccount:referrer[[bankAccount:referrer]]
%% permissions
role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{hsOfficeBankAccount.*}}
%% incoming
%%% .withCreatorAsOwningUser()
user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner
%%% .withIncomingSuperRole(GlobalEntity.class, ADMIN)
role:global.admin --> role:hsOfficeBankAccount.owner
%%% .createSubRole(ADMIN)
role:hsOfficeBankAccount.admin[[bankAccount.admin]]
%% permissions
%%% .withPermission(EDIT)
role:hsOfficeBankAccount.admin --> perm:hsOfficeBankAccount.edit{{hsOfficeBankAccount.edit}}
%% incoming
role:hsOfficeBankAccount.owner ---> role:hsOfficeBankAccount.admin
%%% .createSubRole(REFERRER)
role:hsOfficeBankAccount.referrer[[bankAccount.referrer]]
%% permissions
%%% .withPermission(VIEW)
role:hsOfficeBankAccount.referrer --> perm:hsOfficeBankAccount.view{{hsOfficeBankAccount.view}}
%% incoming
role:hsOfficeBankAccount.admin ---> role:hsOfficeBankAccount.referrer
end end
```
subgraph bankAccount:permissions[ ]
style bankAccount:permissions fill:#dd4901,stroke:white
perm:bankAccount:INSERT{{bankAccount:INSERT}}
perm:bankAccount:DELETE{{bankAccount:DELETE}}
perm:bankAccount:UPDATE{{bankAccount:UPDATE}}
perm:bankAccount:SELECT{{bankAccount:SELECT}}
end
end
%% granting roles to users
user:creator ==> role:bankAccount:owner
%% granting roles to roles
role:global:admin ==> role:bankAccount:owner
role:bankAccount:owner ==> role:bankAccount:admin
role:bankAccount:admin ==> role:bankAccount:referrer
%% granting permissions to roles
role:global:guest ==> perm:bankAccount:INSERT
role:bankAccount:owner ==> perm:bankAccount:DELETE
role:bankAccount:admin ==> perm:bankAccount:UPDATE
role:bankAccount:referrer ==> perm:bankAccount:SELECT
```

View File

@ -1,4 +1,5 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T19:09:38.359318650.
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--//
@ -15,125 +16,141 @@ call generateRbacRoleDescriptors('hsOfficeBankAccount', 'hs_office_bankaccount')
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new bankaccount for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficeBankAccount() create or replace procedure buildRbacSystemForHsOfficeBankAccount(
returns trigger NEW hs_office_bankaccount
language plpgsql )
strict as $$ language plpgsql as $$
declare
begin begin
if TG_OP <> 'INSERT' then call enterTriggerForObjectUuid(NEW.uuid);
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeBankAccountOwner(NEW), hsOfficeBankAccountOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()], userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin() incomingSuperRoles => array[globalAdmin()]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeBankAccountAdmin(NEW), hsOfficeBankAccountAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)] incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeBankAccountTenant(NEW), hsOfficeBankAccountReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)] incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)]
); );
perform createRoleWithGrants( call leaveTriggerForObjectUuid(NEW.uuid);
hsOfficeBankAccountGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeBankAccountTenant(NEW)]
);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_bankaccount row.
*/ */
create trigger createRbacRolesForHsOfficeBankAccount_Trigger create or replace function insertTriggerForHsOfficeBankAccount_tf()
after insert returns trigger
on hs_office_bankaccount language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeBankAccount(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeBankAccount_tg
after insert on hs_office_bankaccount
for each row for each row
execute procedure createRbacRolesForHsOfficeBankAccount(); execute procedure insertTriggerForHsOfficeBankAccount_tf();
--// --//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_bankaccount permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_bankaccount permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalGuest());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_bankaccount');
call grantPermissionToRole(roleUuid, permissionUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_bankaccount INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_bankaccount_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
globalGuest(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_bankaccount'));
return NEW;
end; $$;
create trigger 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; $$;
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$ call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$
target.iban || ':' || target.holder concat(iban, ':', holder)
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_bankaccount', 'target.holder', call generateRbacRestrictedView('hs_office_bankaccount',
$orderBy$
concat(iban, ':', holder)
$orderBy$,
$updates$ $updates$
holder = new.holder, holder = new.holder,
iban = new.iban, iban = new.iban,
bic = new.bic bic = new.bic
$updates$); $updates$);
--/
-- ============================================================================
--changeset hs-office-bankaccount-rbac-NEW-BANKACCOUNT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-bankaccount and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-bankaccount permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-bankaccount']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeBankAccountNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-bankaccount not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_bankaccount_insert_trigger
before insert
on hs_office_bankaccount
for each row
-- TODO.spec: who is allowed to create new bankaccounts
when ( not hasAssumedRole() )
execute procedure addHsOfficeBankAccountNotAllowedForCurrentSubjects();
--// --//

View File

@ -1,62 +1,178 @@
### hs_office_sepaMandate RBAC ### rbac sepaMandate
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T18:29:47.084556363.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph bankAccount["`**bankAccount**`"]
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
direction TB direction TB
style hsOfficeBankAccount fill:#eee style bankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeBankAccount.owner[bankAccount.owner] subgraph bankAccount:roles[ ]
--> role:hsOfficeBankAccount.admin[bankAccount.admin] style bankAccount:roles fill:#99bcdb,stroke:white
--> role:hsOfficeBankAccount.referrer[bankAccount.referrer]
role:bankAccount:owner[[bankAccount:owner]]
role:bankAccount:admin[[bankAccount:admin]]
role:bankAccount:referrer[[bankAccount:referrer]]
end
end end
subgraph hsOfficeRelationship:DEBITOR subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB direction TB
style hsOfficeRelationship:DEBITOR fill:#eee style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeRelationship:DEBITOR.owner[debitorRel.owner] subgraph debitorRel.contact:roles[ ]
--> role:hsOfficeRelationship:DEBITOR.admin[debitorRel.admin] style debitorRel.contact:roles fill:#99bcdb,stroke:white
--> role:hsOfficeRelationship:DEBITOR.agent[debitorRel.agent]
--> role:hsOfficeRelationship:DEBITOR.tenant[debitorRel.tenant] role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end end
subgraph hsOfficeSepaMandate subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeSepaMandate.owner[sepaMandate.owner] subgraph debitorRel.anchorPerson:roles[ ]
%% permissions style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:hsOfficeSepaMandate.owner --> perm:hsOfficeSepaMandate.*{{sepaMandate.*}}
%% incoming
role:global.admin ---> role:hsOfficeSepaMandate.owner
role:hsOfficeSepaMandate.admin[sepaMandate.admin]
%% permissions
role:hsOfficeSepaMandate.admin --> perm:hsOfficeSepaMandate.edit{{sepaMandate.edit}}
%% incoming
role:hsOfficeSepaMandate.owner ---> role:hsOfficeSepaMandate.admin
role:hsOfficeSepaMandate.agent[sepaMandate.agent]
%% incoming
role:hsOfficeSepaMandate.admin ---> role:hsOfficeSepaMandate.agent
role:hsOfficeRelationship:DEBITOR.admin --> role:hsOfficeSepaMandate.agent
role:hsOfficeBankAccount.admin --> role:hsOfficeSepaMandate.agent
%% outgoing
role:hsOfficeSepaMandate.admin --> role:hsOfficeBankAccount.referrer
role:hsOfficeSepaMandate.agent --> role:hsOfficeRelationship:DEBITOR.tenant
role:hsOfficeSepaMandate.agent --> role:hsOfficeBankAccount.referrer
role:hsOfficeSepaMandate.tenant[sepaMandate.tenant]
%% incoming
role:hsOfficeSepaMandate.agent --> role:hsOfficeSepaMandate.tenant
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph sepaMandate["`**sepaMandate**`"]
direction TB
style sepaMandate fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph sepaMandate:roles[ ]
style sepaMandate:roles fill:#dd4901,stroke:white
role:sepaMandate:owner[[sepaMandate:owner]]
role:sepaMandate:admin[[sepaMandate:admin]]
role:sepaMandate:agent[[sepaMandate:agent]]
role:sepaMandate:referrer[[sepaMandate:referrer]]
end
subgraph sepaMandate:permissions[ ]
style sepaMandate:permissions fill:#dd4901,stroke:white
perm:sepaMandate:DELETE{{sepaMandate:DELETE}}
perm:sepaMandate:UPDATE{{sepaMandate:UPDATE}}
perm:sepaMandate:SELECT{{sepaMandate:SELECT}}
end
end
subgraph debitorRel["`**debitorRel**`"]
direction TB
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph debitorRel:roles[ ]
style debitorRel:roles fill:#99bcdb,stroke:white
role:debitorRel:owner[[debitorRel:owner]]
role:debitorRel:admin[[debitorRel:admin]]
role:debitorRel:agent[[debitorRel:agent]]
role:debitorRel:tenant[[debitorRel:tenant]]
end
end
%% granting roles to users
user:creator ==> role:sepaMandate:owner
%% granting roles to roles
role:global:admin -.-> role:debitorRel.anchorPerson:owner
role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer
role:global:admin -.-> role:debitorRel.holderPerson:owner
role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin
role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer
role:global:admin -.-> role:debitorRel.contact:owner
role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin
role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:debitorRel:owner
role:debitorRel:owner -.-> role:debitorRel:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin
role:debitorRel:admin -.-> role:debitorRel:agent
role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent
role:debitorRel:agent -.-> role:debitorRel:tenant
role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant
role:debitorRel.contact:admin -.-> role:debitorRel:tenant
role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:bankAccount:owner
role:bankAccount:owner -.-> role:bankAccount:admin
role:bankAccount:admin -.-> role:bankAccount:referrer
role:global:admin ==> role:sepaMandate:owner
role:sepaMandate:owner ==> role:sepaMandate:admin
role:sepaMandate:admin ==> role:sepaMandate:agent
role:sepaMandate:agent ==> role:bankAccount:referrer
role:sepaMandate:agent ==> role:debitorRel:agent
role:sepaMandate:agent ==> role:sepaMandate:referrer
role:bankAccount:admin ==> role:sepaMandate:referrer
role:debitorRel:agent ==> role:sepaMandate:referrer
role:sepaMandate:referrer ==> role:debitorRel:tenant
%% granting permissions to roles
role:sepaMandate:owner ==> perm:sepaMandate:DELETE
role:sepaMandate:admin ==> perm:sepaMandate:UPDATE
role:sepaMandate:referrer ==> perm:sepaMandate:SELECT
``` ```

View File

@ -1,4 +1,5 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T18:29:47.095199204.
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--//
@ -15,105 +16,123 @@ call generateRbacRoleDescriptors('hsOfficeSepaMandate', 'hs_office_sepamandate')
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for sepaMandate entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficeSepaMandateRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficeSepaMandate(
returns trigger NEW hs_office_sepamandate
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
newHsOfficeDebitor hs_office_debitor; newBankAccount hs_office_bankaccount;
newhsOfficeRelationship:DEBITOR hs_office_relationship; newDebitorRel hs_office_relationship;
newHsOfficeBankAccount hs_office_bankAccount;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.bankAccountUuid into newBankAccount;
select * from hs_office_debitor as p where p.uuid = NEW.debitorUuid into newHsOfficeDebitor; SELECT * FROM hs_office_relationship WHERE uuid = NEW.debitorUuid into newDebitorRel;
select * from hs_office_relationship as r where r.uuid = newHsOfficeDebitor.debitorRelUuid into newhsOfficeRelationship:DEBITOR;
select * from hs_office_bankAccount as c where c.uuid = NEW.bankAccountUuid into newHsOfficeBankAccount;
if TG_OP = 'INSERT' then
-- === ATTENTION: code generated from related Mermaid flowchart: ===
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateOwner(NEW), hsOfficeSepaMandateOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[globalAdmin()] incomingSuperRoles => array[globalAdmin()]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateAdmin(NEW), hsOfficeSepaMandateAdmin(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
incomingSuperRoles => array[ incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)]
hsOfficeSepaMandateOwner(NEW)],
outgoingSubRoles => array[
hsOfficeBankAccountTenant(newHsOfficeBankAccount)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateAgent(NEW), hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[ incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
hsOfficeSepaMandateAdmin(NEW),
hsOfficeRelationshipAdmin(newhsOfficeRelationship:DEBITOR),
hsOfficeBankAccountAdmin(newHsOfficeBankAccount)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsOfficeRelationshipTenant(newhsOfficeRelationship:DEBITOR)] hsOfficeBankAccountReferrer(newBankAccount),
hsOfficeRelationshipAgent(newDebitorRel)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateTenant(NEW), hsOfficeSepaMandateReferrer(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[
hsOfficeRelationshipReferrer(newhsOfficeRelationship:DEBITOR),
hsOfficeBankAccountGuest(newHsOfficeBankAccount)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateGuest(NEW),
permissions => array['SELECT'], permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeSepaMandateTenant(NEW)] incomingSuperRoles => array[
hsOfficeRelationshipAgent(newDebitorRel),
hsOfficeBankAccountAdmin(newBankAccount),
hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[hsOfficeRelationshipTenant(newDebitorRel)]
); );
-- === END of code generated from Mermaid flowchart. ===
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_sepamandate row.
*/ */
create trigger createRbacRolesForHsOfficeSepaMandate_Trigger
after insert create or replace function insertTriggerForHsOfficeSepaMandate_tf()
on hs_office_sepamandate returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeSepaMandate(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeSepaMandate_tg
after insert on hs_office_sepamandate
for each row for each row
execute procedure hsOfficeSepaMandateRbacRolesTrigger(); execute procedure insertTriggerForHsOfficeSepaMandate_tf();
--// --//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate.
*/
create or replace function hs_office_sepamandate_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_sepamandate not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_sepamandate_insert_permission_check_tg
before insert on hs_office_sepamandate
for each row
-- As there is no explicit INSERT grant specified for this table,
-- only global admins are allowed to insert any rows.
when ( not isGlobalAdmin() )
execute procedure hs_office_sepamandate_insert_permission_missing_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_sepamandate', 'target.reference');
call generateRbacIdentityViewFromQuery('hs_office_sepamandate', $idName$
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
from hs_office_sepamandate sm
join hs_office_bankaccount ba on ba.uuid = sm.bankAccountUuid
$idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_sepamandate', call generateRbacRestrictedView('hs_office_sepamandate',
orderby => 'target.reference', 'validity',
columnUpdates => $updates$ $updates$
reference = new.reference, reference = new.reference,
agreement = new.agreement, agreement = new.agreement,
validity = new.validity validity = new.validity
@ -121,48 +140,3 @@ call generateRbacRestrictedView('hs_office_sepamandate',
--// --//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-NEW-SepaMandate:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-sepaMandate and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-sepaMandate permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-sepamandate']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeSepaMandateNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-sepaMandate not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_sepamandate_insert_trigger
before insert
on hs_office_sepamandate
for each row
-- TODO.spec: who is allowed to create new sepaMandates
when ( not hasAssumedRole() )
execute procedure addHsOfficeSepaMandateNotAllowedForCurrentSubjects();
--//

View File

@ -50,25 +50,21 @@ begin
-- Permissions and Grants for Debitor -- Permissions and Grants for Debitor
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipOwner(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipOwner(newDebitorRel)),
-- createPermissions(partnerUuid, array ['DELETE']) -- createPermissions(partnerUuid, array ['DELETE'])
-- ); -- );
-- --
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipAdmin(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipAdmin(newDebitorRel), 'fail'),
-- createPermissions(partnerUuid, array ['edit']) -- createPermissions(partnerUuid, array ['UPDATE'])
-- ); -- );
-- --
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipTenant(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipTenant(newDebitorRel), 'fail'),
-- createPermissions(partnerUuid, array ['view']) -- createPermissions(partnerUuid, array ['SELECT'])
-- ); -- );
perform createRoleWithGrants( -- Grants to and from related Partner Relationship
hsOfficeDebitorAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeDebitorOwner(NEW)]
);
-- call grantRoleToRole(hsOfficeRelationshipAdmin(newDebitorRel), hsOfficeRelationshipAdmin(newPartnerRel), true); -- call grantRoleToRole(hsOfficeRelationshipAdmin(newDebitorRel), hsOfficeRelationshipAdmin(newPartnerRel), true);
-- call grantRoleToRole(hsOfficeRelationshipAgent(newPartnerRel), hsOfficeRelationshipAdmin(newDebitorRel), true); -- call grantRoleToRole(hsOfficeRelationshipAgent(newPartnerRel), hsOfficeRelationshipAdmin(newDebitorRel), true);
@ -78,12 +74,10 @@ begin
-- Grants to and from refundBankAccount -- Grants to and from refundBankAccount
perform createRoleWithGrants( -- if newBankAccount is not null then
hsOfficeDebitorGuest(NEW), -- call grantRoleToRole(hsOfficeBankAccountReferrer(newBankAccount), hsOfficeRelationshipAgent(newDebitorRel), true);
permissions => array['SELECT'], -- call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newBankAccount), true);
incomingSuperRoles => array[ -- end if;
hsOfficeDebitorTenant(NEW)]
);
elsif TG_OP = 'UPDATE' then elsif TG_OP = 'UPDATE' then
@ -93,18 +87,18 @@ begin
from hs_office_relationship as r where r.relType = 'ACCOUNTING' and r.relHolderUuid = NEW.debitorRelUuid; from hs_office_relationship as r where r.relType = 'ACCOUNTING' and r.relHolderUuid = NEW.debitorRelUuid;
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipOwner(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipOwner(newDebitorRel)),
-- createPermissions(partnerUuid, array ['*']) -- createPermissions(partnerUuid, array ['DELETE'])
-- ); -- );
-- --
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipAdmin(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipAdmin(newDebitorRel)),
-- createPermissions(partnerUuid, array ['edit']) -- createPermissions(partnerUuid, array ['UPDATE'])
-- ); -- );
-- --
-- call grantPermissionsToRole( -- call grantPermissionsToRole(
-- getRoleId(hsOfficeRelationshipTenant(newDebitorRel), 'fail'), -- getRoleId(hsOfficeRelationshipTenant(newDebitorRel)),
-- createPermissions(partnerUuid, array ['view']) -- createPermissions(partnerUuid, array ['SELECT'])
-- ); -- );
end if; end if;

View File

@ -9,8 +9,8 @@ import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEnti
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService.Include; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array; import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
@ -40,7 +40,7 @@ import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class, RbacGrantsMermaidService.class }) @Import( { Context.class, JpaAttempt.class, RbacGrantsDiagramService.class })
class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithCleanup { class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired @Autowired
@ -71,7 +71,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@Autowired @Autowired
RbacGrantsMermaidService mermaidService; RbacGrantsDiagramService mermaidService;
@MockBean @MockBean
HttpServletRequest request; HttpServletRequest request;
@ -315,7 +315,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif");
RbacGrantsMermaidService.writeToFile("initial partner: Fourth eG + fourth contact", RbacGrantsDiagramService.writeToFile("initial partner: Fourth eG + fourth contact",
mermaidService.allGrantsFrom(givenDebitor.getUuid(), "view", EnumSet.of(Include.USERS, Include.DETAILS)), mermaidService.allGrantsFrom(givenDebitor.getUuid(), "view", EnumSet.of(Include.USERS, Include.DETAILS)),
"doc/all-grants-before-globalAdmin_canUpdateArbitraryDebitor.md"); "doc/all-grants-before-globalAdmin_canUpdateArbitraryDebitor.md");

View File

@ -472,17 +472,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.contact(givenContact) .contact(givenContact)
.build(); .build();
relationshipRepo.save(partnerRole); relationshipRepo.save(partnerRole);
return partnerRole;
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(partnerNumber)
.partnerRole(partnerRole)
.person(givenPartnerPerson)
.contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().build())
.build();
return partnerRepo.save(newPartner);
}).assertSuccessful().returnedValue();
} }
void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) { void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {

View File

@ -59,7 +59,6 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var count = personRepo.count(); final var count = personRepo.count();
// when // when
final var result = attempt(em, () -> toCleanup(personRepo.save( final var result = attempt(em, () -> toCleanup(personRepo.save(
hsOfficePerson("a new person")))); hsOfficePerson("a new person"))));

View File

@ -1,136 +0,0 @@
package net.hostsharing.hsadminng.rbac.rbacgrant;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService.Include;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.EnumSet;
import java.util.UUID;
import static java.lang.String.join;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class, RbacGrantsMermaidService.class})
class RbacGrantsMermaidServiceIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired
RbacGrantsMermaidService grantsMermaidService;
@MockBean
HttpServletRequest request;
@Test
void allGrantsToCurrentUser() {
context("superuser-alex@hostsharing.net", "test_domain#xxx00-aaaa.owner");
final var graph = grantsMermaidService.allGrantsToCurrentUser(EnumSet.of(Include.TEST_ENTITIES));
assertThat(graph).isEqualTo("""
flowchart TB
role:test_package#xxx00.tenant[
test_package
xxx00.t
tenant] --> role:test_customer#xxx.tenant[
test_customer
xxx.t
tenant]
role:test_domain#xxx00-aaaa.owner[
test_domain
xxx00-aaaa.o
owner] --> role:test_domain#xxx00-aaaa.admin[
test_domain
xxx00-aaaa.a
admin]
role:test_domain#xxx00-aaaa.admin[
test_domain
xxx00-aaaa.a
admin] --> role:test_package#xxx00.tenant[
test_package
xxx00.t
tenant]
""".trim());
}
@Test
void allGrantsToCurrentUserIncludingPermissions() {
context("superuser-alex@hostsharing.net", "test_domain#xxx00-aaaa.owner");
final var graph = grantsMermaidService.allGrantsToCurrentUser(EnumSet.of(Include.TEST_ENTITIES, Include.PERMISSIONS));
assertThat(graph).isEqualTo("""
flowchart TB
role:test_domain#xxx00-aaaa.owner[
test_domain
xxx00-aaaa.o
owner] --> perm:*:on:test_domain#xxx00-aaaa{{
test_domain
xxx00-aaaa
*}}
role:test_customer#xxx.tenant[
test_customer
xxx.t
tenant] --> perm:view:on:test_customer#xxx{{
test_customer
xxx
view}}
role:test_domain#xxx00-aaaa.admin[
test_domain
xxx00-aaaa.a
admin] --> perm:edit:on:test_domain#xxx00-aaaa{{
test_domain
xxx00-aaaa
edit}}
role:test_package#xxx00.tenant[
test_package
xxx00.t
tenant] --> role:test_customer#xxx.tenant[
test_customer
xxx.t
tenant]
role:test_domain#xxx00-aaaa.owner[
test_domain
xxx00-aaaa.o
owner] --> role:test_domain#xxx00-aaaa.admin[
test_domain
xxx00-aaaa.a
admin]
role:test_package#xxx00.tenant[
test_package
xxx00.t
tenant] --> perm:view:on:test_package#xxx00{{
test_package
xxx00
view}}
role:test_domain#xxx00-aaaa.admin[
test_domain
xxx00-aaaa.a
admin] --> role:test_package#xxx00.tenant[
test_package
xxx00.t
tenant]
""".trim());
}
@Test
// @Disabled
void print() throws IOException {
//context("superuser-alex@hostsharing.net", "hs_office_person#FirbySusan.admin");
context("superuser-alex@hostsharing.net");
//final var graph = grantsMermaidService.allGrantsToCurrentUser(EnumSet.of(Include.NON_TEST_ENTITIES, Include.PERMISSIONS));
final var targetObject = (UUID) em.createNativeQuery("SELECT uuid FROM hs_office_coopassetstransaction WHERE reference='ref 1000101-1'").getSingleResult();
final var graph = grantsMermaidService.allGrantsFrom(targetObject, "view", EnumSet.of(Include.USERS));
RbacGrantsMermaidService.writeToFile(join(";", context.getAssumedRoles()), graph, "doc/all-grants.md");
}
}

View File

@ -4,8 +4,8 @@ spring:
platform: postgres platform: postgres
datasource: 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: jdbc:postgresql://localhost:5432/postgres
username: postgres username: postgres
password: password password: password