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() {
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.projection("iban || ':' || holder"))
.withIdentityView(SQL.projection("concat(iban, ':', holder)"))
.withUpdatableColumns("holder", "iban", "bic")
.toRole("global", GUEST).grantPermission("bankAccount", INSERT)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
@ -75,6 +78,6 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
}
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)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.toRole("global", GUEST).grantPermission("person", INSERT)
.createRole(OWNER, (with) -> {
with.permission(DELETE);
with.owningUser(CREATOR);
@ -93,6 +95,6 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
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() {
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")
.importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid"))
@ -111,17 +116,17 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
})
.createSubRole(AGENT, (with) -> {
with.outgoingSubRole("bankAccount", REFERRER);
with.outgoingSubRole("debitorRel", AGENT);
with.outgoingSubRole("debitor", AGENT);
})
.createSubRole(REFERRER, (with) -> {
with.incomingSuperRole("bankAccount", ADMIN);
with.incomingSuperRole("debitorRel", AGENT);
with.incomingSuperRole("debitor", AGENT);
with.outgoingSubRole("debitorRel", TENANT);
with.permission(SELECT);
});
}
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}
LOOP
roleUuid := findRoleId(${rawSuperRoleDescriptor}(row));
roleUuid := findRoleId(${rawSuperRoleDescriptor});
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}');
call grantPermissionToRole(roleUuid, permissionUuid);
END LOOP;
@ -62,7 +62,7 @@ public class InsertTriggerGenerator {
""",
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef))
with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row"))
);
});
}
@ -79,7 +79,7 @@ public class InsertTriggerGenerator {
strict as $$
begin
call grantPermissionToRole(
${rawSuperRoleDescriptor}(NEW),
${rawSuperRoleDescriptor},
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'));
return NEW;
end; $$;
@ -91,7 +91,7 @@ public class InsertTriggerGenerator {
""",
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
with("rawSuperRoleDescriptor", toVar(superRoleDef))
with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, PostgresTriggerReference.NEW.name()))
);
});
}
@ -111,15 +111,39 @@ public class InsertTriggerGenerator {
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
getOptionalInsertGrant().ifPresentOrElse(g -> {
plPgSql.writeLn("""
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() ));
if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) {
plPgSql.writeLn(
"""
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
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("""
@ -162,4 +186,12 @@ public class InsertTriggerGenerator {
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$);
""";
case SQL_QUERY -> """
call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$
call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$
${identityViewSqlPart}
$idName$);
""";

View File

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

View File

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

View File

@ -103,7 +103,7 @@ public class RbacGrantController implements RbacGrantsApi {
// public ResponseEntity<String> allGrantsOfUserAsMermaid(
// @RequestHeader(name = "current-user") String currentUser,
// @RequestHeader(name = "assumed-roles", required = false) String assumedRoles) {
// final var graph = RbacGrantsMermaidService.allGrantsToUser(currentUser);
// final var graph = RbacGrantsDiagramService.allGrantsToUser(currentUser);
// 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"))
.withRestrictedViewOrderBy(SQL.expression("reference"))
.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) -> {
with.owningUser(CREATOR).unassumed();

View File

@ -373,10 +373,12 @@ create table RbacPermission
uuid uuid primary key references RbacReference (uuid) on delete cascade,
objectUuid uuid not null references RbacObject,
op RbacOp not null,
opTableName varchar(60),
unique (objectUuid, op)
opTableName varchar(60)
);
ALTER TABLE RbacPermission
ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
call create_journal('RbacPermission');
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:--//
-- ----------------------------------------------------------------------------

View File

@ -118,9 +118,32 @@ select 'global', (select uuid from RbacObject where objectTable = 'global'), 'ad
$$;
begin transaction;
call defineContext('creating global admin role', null, null, null);
select createRole(globalAdmin());
call defineContext('creating global admin role', null, null, null);
select createRole(globalAdmin());
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:--//

View File

@ -1,4 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T15:13:04.479330676.
-- ============================================================================
--changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--//
@ -15,69 +16,134 @@ 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()
create or replace procedure buildRbacSystemForHsOfficePerson(
NEW hs_office_person
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[globalAdmin()]
);
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficePersonOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_person row.
*/
create or replace function insertTriggerForHsOfficePerson_tf()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin()
);
-- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to update the data?
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficePersonOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
);
call buildRbacSystemForHsOfficePerson(NEW);
return NEW;
end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForHsOfficePerson_Trigger
after insert
on hs_office_person
create trigger insertTriggerForHsOfficePerson_tg
after insert on hs_office_person
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:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_person', $idName$
concat(target.tradeName, target.familyName, target.givenName)
concat(tradeName, familyName, givenName)
$idName$);
--//
-- ============================================================================
--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$
personType = new.personType,
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
strict as $$
declare
hsOfficeRelationshipTenant RbacRoleDescriptor;
newRelAnchor hs_office_person;
newRelHolder hs_office_person;
newAnchorPerson hs_office_person;
newHolderPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin
call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW);
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_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_contact as c where c.uuid = NEW.contactUuid into newContact;
if TG_OP = 'INSERT' then
-- cannot be generated using `tools/generate` because there are multiple grants to the same entity type
perform createRoleWithGrants(
hsOfficeRelationshipOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[
globalAdmin(),
hsOfficePersonAdmin(newRelAnchor)]
);
globalAdmin()
]
);
perform createRoleWithGrants(
hsOfficeRelationshipAdmin(NEW),
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(
hsOfficeRelationshipTenant,
permissions => array['SELECT'],
hsOfficeRelationshipAgent(NEW),
incomingSuperRoles => array[
hsOfficeRelationshipAdmin(NEW),
hsOfficePersonAdmin(newRelAnchor),
hsOfficePersonAdmin(newRelHolder),
hsOfficeContactAdmin(newContact)],
outgoingSubRoles => array[
hsOfficePersonTenant(newRelAnchor),
hsOfficePersonTenant(newRelHolder),
hsOfficeContactTenant(newContact)]
);
hsOfficePersonAdmin(newHolderPerson),
hsOfficeContactAdmin(newContact)
]
);
-- anchor and holder admin roles need each others tenant role
-- to be able to see the joined relationship
-- TODO: this can probably be avoided through agent+guest roles
call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder));
call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor));
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact));
perform createRoleWithGrants(
hsOfficeRelationshipTenant(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeRelationshipAgent(NEW)
],
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
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
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
call revokeRoleFromRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(newContact) );
call revokeRoleFromRole( hsOfficeContactReferrer(oldContact), hsOfficeRelationshipTenant(NEW) );
call grantRoleToRole( hsOfficeContactReferrer(newContact), hsOfficeRelationshipTenant(NEW) );
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationshipTenant );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationshipTenant );
call revokeRoleFromRole( hsOfficeRelationshipAgent(NEW), hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeRelationshipAgent(NEW), hsOfficeContactAdmin(newContact) );
end if;
else
raise exception 'invalid usage of TRIGGER';
@ -136,8 +142,8 @@ call generateRbacIdentityViewFromProjection('hs_office_relationship', $idName$
--changeset hs-office-relationship-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_relationship',
'(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)',
$updates$
'(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)',
$updates$
contactUuid = new.contactUuid
$updates$);
--//

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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph external[ ]
style external fill:#fff
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerPerson
style partnerPerson fill:#eee
role:partnerPerson.admin[partnerPerson.admin]
end
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
subgraph otherRelatedPerson
style otherRelatedPerson fill:#eee
role:otherRelatedPerson.admin[otherRelatedPerson.admin]
end
subgraph hsOfficeRelationship[hsOfficeRelationship:PARTNER]
direction TB
style hsOfficeRelationship fill:#eee
role:global.admin
--> role:hsOfficeRelationship.owner[relationship.owner]
--> role:hsOfficeRelationship.admin[relationship.admin]
--> role:hsOfficeRelationship.agent[relationship.agent]
--> role:hsOfficeRelationship.tenant[relationship.tenant]
role:partnerPerson.admin --> role:hsOfficeRelationship.agent
role:otherRelatedPerson.admin --> role:hsOfficeRelationship.tenant
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph internal[ ]
subgraph partner["`**partner**`"]
direction TB
style partner fill:#dd4901,stroke:#274d6e,stroke-width:8px
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
subgraph partner:permissions[ ]
style partner:permissions fill:#dd4901,stroke:white
perm:partner:new-partner{{partner:new-partner}}
perm:partner:DELETE{{partner:DELETE}}
perm:partner:UPDATE{{partner:UPDATE}}
perm:partner:SELECT{{partner:SELECT}}
end
subgraph hsOfficePartnerDetails
subgraph partnerRel["`**partnerRel**`"]
direction TB
style hsOfficePartnerDetails fill:#eee
perm:hsOfficePartnerDetails.*{{partnerDetails.*}}
role:hsOfficeRelationship.owner ==> perm:hsOfficePartnerDetails.*
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
perm:hsOfficePartnerDetails.edit{{partnerDetails.edit}}
role:hsOfficeRelationship.agent ==> perm:hsOfficePartnerDetails.edit
role:hsOfficeRelationship.agent ==> perm:hsOfficePartnerDetails.view
perm:hsOfficePartnerDetails.view{{partnerDetails.view}}
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
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
-- === ATTENTION: code generated from related Mermaid flowchart: ===
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)
-- Permissions and Grants for Partner
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
@ -96,21 +45,21 @@ begin
);
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipTenant(newPartnerRel), 'fail'),
createPermissions(partnerUuid, array ['view'])
getRoleId(hsOfficeRelationshipTenant(newPartnerRel)),
createPermissions(partnerUuid, array ['SELECT'])
);
-- Permissions and Grants for PartnerDetails
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel), 'fail'),
createPermissions(partnerDetailsUuid, array ['*'])
);
getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
createPermissions(partnerDetailsUuid, array ['DELETE'])
);
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel), 'fail'),
createPermissions(partnerDetailsUuid, array ['edit'])
);
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel)),
createPermissions(partnerDetailsUuid, array ['UPDATE'])
);
call grantPermissionsToRole(
-- Yes, here hsOfficePartnerAGENT is used, not hsOfficeRelationshipTENANT.
@ -118,7 +67,7 @@ begin
-- Otherwise package-admins etc. would be able to read the data.
getRoleId(hsOfficeRelationshipAgent(newPartnerRel)),
createPermissions(partnerDetailsUuid, array ['SELECT'])
);
);
elsif TG_OP = 'UPDATE' then
@ -129,72 +78,72 @@ begin
-- Revokes from Partner
call revokePermissionFromRole(
findPermissionId(partnerUuid, 'view'),
findPermissionId(partnerUuid, 'SELECT'),
hsOfficeRelationshipTenant(oldPartnerRel)
);
-- call revokePermissionFromRole(
-- findPermissionId(partnerUuid, 'edit'),
-- hsOfficeRelationshipAdmin(oldPartnerRel)
-- );
--
-- call revokePermissionFromRole(
-- findPermissionId(partnerUuid, '*'),
-- hsOfficeRelationshipOwner(oldPartnerRel)
-- );
-- call revokePermissionFromRole(
-- findPermissionId(partnerUuid, 'edit'),
-- hsOfficeRelationshipAdmin(oldPartnerRel)
-- );
--
-- call revokePermissionFromRole(
-- findPermissionId(partnerUuid, '*'),
-- hsOfficeRelationshipOwner(oldPartnerRel)
-- );
-- Grants for Partner
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipOwner(newPartnerRel), 'fail'),
array[findPermissionId(partnerUuid, '*')]
getRoleId(hsOfficeRelationshipOwner(newPartnerRel)),
array[findPermissionId(partnerUuid, 'DELETE')]
);
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel), 'fail'),
array[findPermissionId(partnerUuid, 'edit')]
getRoleId(hsOfficeRelationshipAdmin(newPartnerRel)),
array[findPermissionId(partnerUuid, 'UPDATE')]
);
call grantPermissionsToRole(
getRoleId(hsOfficeRelationshipTenant(newPartnerRel), 'fail'),
array[findPermissionId(partnerUuid, 'view')]
getRoleId(hsOfficeRelationshipTenant(newPartnerRel)),
array[findPermissionId(partnerUuid, 'SELECT')]
);
-- Revokes from PartnerDetails
-- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, 'view'),
-- hsOfficeRelationshipAgent(oldPartnerRel)
-- );
--
-- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, 'edit'),
-- hsOfficeRelationshipAdmin(oldPartnerRel)
-- );
--
-- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, '*'),
-- hsOfficeRelationshipOwner(oldPartnerRel)
-- );
-- call revokePermissionFromRole(
-- findPermissionId(partnerDetailsUuid, 'SELECT'),
-- hsOfficeRelationshipAgent(oldPartnerRel)