handcoded multiple insert permission grants

This commit is contained in:
Michael Hoennig 2024-04-23 16:41:06 +02:00
parent 66332b6de2
commit 09f7368d1f
11 changed files with 183 additions and 40 deletions

View File

@ -156,8 +156,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
dependsOnColumn("parentAssetUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
// TODO.rbac: implement multiple INSERT-rules, e.g. for Asset.bookingItem + Asset.parentAsset
//.toRole("parentServer", AGENT).grantPermission(INSERT)
.toRole("parentServer", ADMIN).grantPermission(INSERT)
)
)
@ -175,6 +174,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac");
rbac().generateWithBaseFileName("7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-generated");
}
}

View File

@ -19,7 +19,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCases;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;

View File

@ -0,0 +1,16 @@
--liquibase formatted sql
-- ============================================================================
-- RAISE-FUNCTIONS
--changeset RAISE-FUNCTIONS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Like RAISE EXCEPTION ... just as an expression instead of a statement.
*/
create or replace function raiseException(msg text)
returns varchar
language plpgsql as $$
begin
raise exception using message = msg;
end; $$;
--//

View File

@ -34,6 +34,55 @@ create table if not exists hs_hosting_asset
--//
-- ============================================================================
--changeset hosting-asset-HIERARCHY-CHECK:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function hs_hosting_asset_type_hierarchy_check_tf()
returns trigger
language plpgsql as $$
declare
actualParentType HsHostingAssetType;
expectedParentType HsHostingAssetType;
begin
if NEW.parentAssetUuid is not null then
actualParentType := (select type
from hs_hosting_asset
where NEW.parentAssetUuid = uuid);
end if;
expectedParentType := (select case NEW.type
when 'CLOUD_SERVER' then null
when 'MANAGED_SERVER' then null
when 'MANAGED_WEBSPACE' then 'MANAGED_SERVER'
when 'UNIX_USER' then 'MANAGED_WEBSPACE'
when 'DOMAIN_SETUP' then 'UNIX_USER'
when 'EMAIL_ALIAS' then 'MANAGED_WEBSPACE'
when 'EMAIL_ADDRESS' then 'DOMAIN_SETUP'
when 'PGSQL_USER' then 'MANAGED_WEBSPACE'
when 'PGSQL_DATABASE' then 'MANAGED_WEBSPACE'
when 'MARIADB_USER' then 'MANAGED_WEBSPACE'
when 'MARIADB_DATABASE' then 'MANAGED_WEBSPACE'
else raiseException(format('[400] unknown asset type %s', NEW.type::text))
end);
if expectedParentType is not null and actualParentType is null then
raise exception '[400] % must have % as parent, but got <NULL>',
NEW.type, expectedParentType;
elsif expectedParentType is not null and actualParentType <> expectedParentType then
raise exception '[400] % must have % as parent, but got %s',
NEW.type, expectedParentType, actualParentType;
end if;
return NEW;
end; $$;
create trigger hs_hosting_asset_type_hierarchy_check_tg
before insert on hs_hosting_asset
for each row
execute procedure hs_hosting_asset_type_hierarchy_check_tf();
--//
-- ============================================================================
--changeset hs-hosting-asset-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
-- ----------------------------------------------------------------------------

View File

@ -49,6 +49,12 @@ end
subgraph parentServer["`**parentServer**`"]
direction TB
style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph parentServer:roles[ ]
style parentServer:roles fill:#99bcdb,stroke:white
role:parentServer:ADMIN[[parentServer:ADMIN]]
end
end
subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"]
@ -455,6 +461,7 @@ role:asset:TENANT ==> role:bookingItem:TENANT
%% granting permissions to roles
role:bookingItem:AGENT ==> perm:asset:INSERT
role:parentServer:ADMIN ==> perm:asset:INSERT
role:asset:OWNER ==> perm:asset:DELETE
role:asset:ADMIN ==> perm:asset:UPDATE
role:asset:TENANT ==> perm:asset:SELECT

View File

@ -49,6 +49,12 @@ end
subgraph parentServer["`**parentServer**`"]
direction TB
style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph parentServer:roles[ ]
style parentServer:roles fill:#99bcdb,stroke:white
role:parentServer:ADMIN[[parentServer:ADMIN]]
end
end
subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"]
@ -455,6 +461,7 @@ role:asset:TENANT ==> role:bookingItem:TENANT
%% granting permissions to roles
role:bookingItem:AGENT ==> perm:asset:INSERT
role:parentServer:ADMIN ==> perm:asset:INSERT
role:asset:OWNER ==> perm:asset:DELETE
role:asset:ADMIN ==> perm:asset:UPDATE
role:asset:TENANT ==> perm:asset:SELECT

View File

@ -49,6 +49,12 @@ end
subgraph parentServer["`**parentServer**`"]
direction TB
style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph parentServer:roles[ ]
style parentServer:roles fill:#99bcdb,stroke:white
role:parentServer:ADMIN[[parentServer:ADMIN]]
end
end
subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"]
@ -461,6 +467,7 @@ role:asset:TENANT ==> role:bookingItem:TENANT
%% granting permissions to roles
role:bookingItem:AGENT ==> perm:asset:INSERT
role:parentServer:ADMIN ==> perm:asset:INSERT
role:asset:OWNER ==> perm:asset:DELETE
role:asset:ADMIN ==> perm:asset:UPDATE
role:asset:TENANT ==> perm:asset:SELECT

View File

@ -90,46 +90,90 @@ execute procedure insertTriggerForHsHostingAsset_tf();
-- ============================================================================
--changeset hs-hosting-asset-rbac-INSERT:1 endDelimiter:--//
--changeset hs-hosting-asset-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
-- ---------------------------------------------------------------------------------------
-- to hs_booking_item
-- ---------------------------------------------------------------------------------------
/*
Creates INSERT INTO hs_hosting_asset permissions for the related hs_booking_item rows.
Grants INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_booking_item rows.
*/
do language plpgsql $$
declare
row hs_booking_item;
hsBookingItem hs_booking_item;
begin
call defineContext('create INSERT INTO hs_hosting_asset permissions for the related hs_booking_item rows');
call defineContext('create INSERT INTO hs_hosting_asset permissions for pre-exising hs_booking_item rows');
FOR row IN SELECT * FROM hs_booking_item
FOR hsBookingItem IN SELECT * FROM hs_booking_item
-- unconditional for all rows in that table
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_hosting_asset'),
hsBookingItemAGENT(row));
createPermission(hsBookingItem.uuid, 'INSERT', 'hs_hosting_asset'),
hsBookingItemAGENT(hsBookingItem));
END LOOP;
END;
end;
$$;
/**
Adds hs_hosting_asset INSERT permission to specified role of new hs_booking_item rows.
Grants hs_hosting_asset INSERT permission to specified role of new hs_booking_item rows.
*/
create or replace function hs_hosting_asset_hs_booking_item_insert_tf()
create or replace function new_hs_hosting_asset_grants_insert_to_hs_booking_item_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
-- unconditional for all rows in that table:
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'),
hsBookingItemAGENT(NEW));
-- end.
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_hosting_asset_hs_booking_item_insert_tg
create trigger z_new_hs_hosting_asset_grants_insert_to_hs_booking_item_tg
after insert on hs_booking_item
for each row
execute procedure hs_hosting_asset_hs_booking_item_insert_tf();
execute procedure new_hs_hosting_asset_grants_insert_to_hs_booking_item_tf();
-- ---------------------------------------------------------------------------------------
-- to hs_hosting_asset
-- ---------------------------------------------------------------------------------------
/*
Grants INSERT INTO hs_hosting_asset permissions to specified role of new hs_hosting_asset rows.
*/
-- such rows cannot yet exist => code block skipped
/**
Grants hs_hosting_asset INSERT permission to specified role of new hs_hosting_asset rows.
*/
create or replace function new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tf()
returns trigger
language plpgsql
strict as $$
begin
if NEW.type in ('MANAGED_SERVER') then
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'),
hsHostingAssetADMIN(NEW));
end if;
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tg
after insert on hs_hosting_asset
for each row
execute procedure new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tf();
--//
-- ============================================================================
--changeset hs-hosting-asset-rbac-CHECKING-INSERT-PERMISSION:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_hosting_asset,
@ -148,7 +192,10 @@ end; $$;
create trigger hs_hosting_asset_insert_permission_check_tg
before insert on hs_hosting_asset
for each row
when ( not hasInsertPermission(NEW.bookingItemUuid, 'INSERT', 'hs_hosting_asset') )
when ( not (
hasInsertPermission(NEW.bookingItemUuid, 'INSERT', 'hs_hosting_asset') or
NEW.type = 'MANAGED_WEBSPACE' and hasInsertPermission(NEW.parentAssetUuid, 'INSERT', 'hs_hosting_asset')
) )
execute procedure hs_hosting_asset_insert_permission_missing_tf();
--//

View File

@ -18,6 +18,7 @@ declare
currentTask varchar;
relatedDebitor hs_office_debitor;
relatedBookingItem hs_booking_item;
managedServerUuid uuid;
begin
currentTask := 'creating hosting-asset test-data ' || givenPartnerNumber::text || givenDebitorSuffix;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN');
@ -33,14 +34,15 @@ begin
from hs_booking_item item
where item.debitoruuid = relatedDebitor.uuid
and item.caption = 'some PrivateCloud';
select uuid_generate_v4() into managedServerUuid;
raise notice 'creating test hosting-asset: %', givenPartnerNumber::text || givenDebitorSuffix::text;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert
into hs_hosting_asset (uuid, bookingitemuuid, type, identifier, caption, config)
values (uuid_generate_v4(), relatedBookingItem.uuid, 'MANAGED_SERVER'::HsHostingAssetType, 'vm10' || givenDebitorSuffix, 'some ManagedServer', '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'CLOUD_SERVER'::HsHostingAssetType, 'vm20' || givenDebitorSuffix, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'MANAGED_WEBSPACE'::HsHostingAssetType, givenWebspacePrefix || '01', 'some Webspace', '{ "RAM": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb);
into hs_hosting_asset (uuid, bookingitemuuid, type, parentAssetUuid, identifier, caption, config)
values (managedServerUuid, relatedBookingItem.uuid, 'MANAGED_SERVER', null, 'vm10' || givenDebitorSuffix, 'some ManagedServer', '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'CLOUD_SERVER', null, 'vm20' || givenDebitorSuffix, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, givenWebspacePrefix || '01', 'some Webspace', '{ "RAM": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb);
end; $$;
--//

View File

@ -13,6 +13,8 @@ databaseChangeLog:
file: db/changelog/0-basis/006-numeric-hash-functions.sql
- include:
file: db/changelog/0-basis/007-table-columns.sql
- include:
file: db/changelog/0-basis/008-raise-functions.sql
- include:
file: db/changelog/0-basis/009-check-environment.sql
- include:

View File

@ -68,12 +68,13 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// given
context("superuser-alex@hostsharing.net");
final var count = assetRepo.count();
final var givenBookingItem = givenBookingItem("First", "some CloudServer");
final var givenManagedServer = givenManagedServer("First", "some ManagedServer");
// when
final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem)
.bookingItem(givenManagedServer.getBookingItem())
.parentAsset(givenManagedServer)
.caption("some new managed webspace")
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("xyz90")
@ -96,14 +97,14 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream()
.map(s -> s.replace("hs_office_", ""))
.toList();
final var givenBookingItem = givenBookingItem("First", "some CloudServer");
final var givenBookingItem = givenBookingItem("First", "some PrivateCloud");
// when
final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder()
.bookingItem(givenBookingItem)
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("xyz91")
.type(HsHostingAssetType.MANAGED_SERVER)
.identifier("vm9000")
.caption("some new managed webspace")
.build();
return toCleanup(assetRepo.save(newAsset));
@ -114,27 +115,27 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN",
"hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER",
"hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT"));
"hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:ADMIN",
"hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:OWNER",
"hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(fromFormatted(
initialGrantNames,
// global-admin
// owner
"{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:DELETE to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER by system and assume }",
"{ grant perm:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:DELETE to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:OWNER to role:hs_booking_item#D-1000111-somePrivateCloud:ADMIN by system and assume }",
// admin
"{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:UPDATE to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER to role:hs_booking_item#D-1000111-someCloudServer:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:INSERT>hs_hosting_asset to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:UPDATE to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:ADMIN to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:OWNER by system and assume }",
// tenant
"{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:SELECT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN by system and assume }",
"{ grant role:hs_booking_item#D-1000111-someCloudServer:TENANT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT by system and assume }",
"{ grant perm:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:SELECT to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:TENANT by system and assume }",
"{ grant role:hs_booking_item#D-1000111-somePrivateCloud:TENANT to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:TENANT by system and assume }",
"{ grant role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:TENANT to role:hs_hosting_asset#D-1000111-somePrivateCloud-vm9000:ADMIN by system and assume }",
null));
}
@ -161,7 +162,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then
allTheseServersAreReturned(
result,
"HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_WEBSPACE, bbb01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_WEBSPACE, D-1000212:some PrivateCloud:vm1012, bbb01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_SERVER, vm1012, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000212:some PrivateCloud, CLOUD_SERVER, vm2012, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })");
}
@ -178,7 +179,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// then:
exactlyTheseAssetsAreReturned(
result,
"HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_WEBSPACE, aaa01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_WEBSPACE, D-1000111:some PrivateCloud:vm1011, aaa01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_SERVER, vm1011, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000111:some PrivateCloud, CLOUD_SERVER, vm2011, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })");
}
@ -352,6 +353,13 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
.findAny().orElseThrow();
}
HsHostingAssetEntity givenManagedServer(final String debitorName, final String hostingAssetCaption) {
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow();
return assetRepo.findAllByDebitorUuid(givenDebitor.getUuid()).stream()
.filter(i -> i.getCaption().equals(hostingAssetCaption))
.findAny().orElseThrow();
}
void exactlyTheseAssetsAreReturned(
final List<HsHostingAssetEntity> actualResult,
final String... serverNames) {