introduce separate database schema-test and amend RBAC generators for schema-generation (#104)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #104
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-09-17 14:21:43 +02:00
parent 1eed0e9b21
commit 285e6fbeb5
57 changed files with 599 additions and 525 deletions

View File

@ -82,7 +82,7 @@ If you have at least Docker and the Java JDK installed in appropriate versions a
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
curl \
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: test_customer#yyy:ADMIN' \
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \
http://localhost:8080/api/test/packages
# add a new customer

View File

@ -3,14 +3,14 @@
-- --------------------------------------------------------
select rbac.isGranted(rbac.findRoleId('administrators'), rbac.findRoleId('test.package#aaa00:OWNER'));
select rbac.isGranted(rbac.findRoleId('test.package#aaa00:OWNER'), rbac.findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('test.package#aaa00:OWNER'), findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('administrators'), findRoleId('test.package#aaa00:OWNER'));
select rbac.isGranted(rbac.findRoleId('administrators'), rbac.findRoleId('rbactest.package#aaa00:OWNER'));
select rbac.isGranted(rbac.findRoleId('rbactest.package#aaa00:OWNER'), rbac.findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('rbactest.package#aaa00:OWNER'), findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('administrators'), findRoleId('rbactest.package#aaa00:OWNER'));
select count(*)
FROM rbac.queryAllPermissionsOfSubjectIdForObjectUuids(rbac.findRbacSubject('superuser-fran@hostsharing.net'),
ARRAY(select uuid from test.customer where reference < 1100000));
ARRAY(select uuid from rbactest.customer where reference < 1100000));
select count(*)
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-fran@hostsharing.net'));
select *

View File

@ -20,7 +20,7 @@ CREATE POLICY customer_policy ON customer
TO restricted
USING (
-- id=1000
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid())
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('rbactest.customer', id, 'SELECT'), rbac.currentSubjectUuid())
);
SET SESSION AUTHORIZATION restricted;
@ -31,28 +31,28 @@ SELECT * from customer;
SET SESSION SESSION AUTHORIZATION DEFAULT;
DROP VIEW cust_view;
CREATE VIEW cust_view AS
SELECT * FROM test.customer;
SELECT * FROM rbactest.customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT * FROM test.customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid());
SELECT * FROM rbactest.customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('rbactest.customer', id, 'SELECT'), rbac.currentSubjectUuid());
SELECT * from cust_view LIMIT 10;
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-alex@hostsharing.net'));
-- access control via view-rule with join to recursive permissions - really fast (38ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
ALTER TABLE test.customer ENABLE ROW LEVEL SECURITY;
ALTER TABLE rbactest.customer ENABLE ROW LEVEL SECURITY;
DROP VIEW IF EXISTS cust_view;
CREATE OR REPLACE VIEW cust_view AS
SELECT *
FROM test.customer;
FROM rbactest.customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT c.uuid, c.reference, c.prefix FROM test.customer AS c
SELECT c.uuid, c.reference, c.prefix FROM rbactest.customer AS c
JOIN rbac.queryAllPermissionsOfSubjectId(rbac.currentSubjectUuid()) AS p
ON p.objectTable='test.customer' AND p.objectUuid=c.uuid;
ON p.objectTable='rbactest.customer' AND p.objectUuid=c.uuid;
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
@ -81,9 +81,9 @@ select rr.uuid, rr.type from rbac.RbacGrants g
join rbac.RbacReference RR on g.ascendantUuid = RR.uuid
where g.descendantUuid in (
select uuid from rbac.queryAllPermissionsOfSubjectId(findRbacSubject('alex@example.com'))
where objectTable='test.customer');
where objectTable='rbactest.customer');
call rbac.grantRoleToUser(rbac.findRoleId('test.customer#aaa:ADMIN'), rbac.findRbacSubject('aaaaouq@example.com'));
call rbac.grantRoleToUser(rbac.findRoleId('rbactest.customer#aaa:ADMIN'), rbac.findRbacSubject('aaaaouq@example.com'));
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('aaaaouq@example.com'));

View File

@ -100,7 +100,7 @@ public class InsertTriggerGenerator {
/**
Grants ${rawSubTable} INSERT permission to specified role of new ${rawSuperTable} rows.
*/
create or replace function ${rawSuperTableSchemaName}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf()
create or replace function ${rawSubTableSchemaPrefix}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf()
returns trigger
language plpgsql
strict as $$
@ -114,10 +114,10 @@ public class InsertTriggerGenerator {
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_new_${rawSubTable}_grants_after_insert_tg
create trigger z_new_${rawSubTableName}_grants_after_insert_tg
after insert on ${rawSuperTableWithSchema}
for each row
execute procedure ${rawSuperTableSchemaName}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf();
execute procedure ${rawSubTableSchemaPrefix}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf();
""",
with("ifConditionThen", g.getSuperRoleDef().getEntityAlias().isCaseDependent()
// TODO.impl: .type needs to be dynamically generated
@ -130,8 +130,9 @@ public class InsertTriggerGenerator {
with("rawSuperTableWithSchema", g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema()),
with("rawSuperTableShortName", g.getSuperRoleDef().getEntityAlias().getRawTableShortName()),
with("rawSuperTable", g.getSuperRoleDef().getEntityAlias().getRawTableName()),
with("rawSuperTableSchemaName", g.getSuperRoleDef().getEntityAlias().getRawTableSchemaPrefix()),
with("rawSubTable", g.getPermDef().getEntityAlias().getRawTableNameWithSchema()),
with("rawSubTableSchemaPrefix", g.getPermDef().getEntityAlias().getRawTableSchemaPrefix()),
with("rawSubTableName", g.getPermDef().getEntityAlias().getRawTableName()),
with("rawSubTableShortName", g.getPermDef().getEntityAlias().getRawTableShortName()));
});
@ -154,15 +155,16 @@ public class InsertTriggerGenerator {
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into ${rawSubTable} values(%) not allowed regardless of current subject, no insert permissions granted at all', NEW;
raise exception '[403] insert into ${rawSubTableWithSchema} values(%) not allowed regardless of current subject, no insert permissions granted at all', NEW;
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
execute procedure ${rawSubTable}_insert_permission_missing_tf();
execute procedure ${rawSubTableWithSchema}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
with("rawSubTableWithSchema", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()),
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
plPgSql.writeLn("--//");
}
@ -183,7 +185,7 @@ public class InsertTriggerGenerator {
private void generateInsertPermissionsCheckHeader(final StringWriter plPgSql) {
plPgSql.writeLn("""
-- ============================================================================
--changeset InsertTriggerGenerator:${rawSubTable}-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
--changeset InsertTriggerGenerator:${liquibaseTagPrefix}-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
@ -196,6 +198,7 @@ public class InsertTriggerGenerator {
superObjectUuid uuid;
begin
""",
with("liquibaseTagPrefix", liquibaseTagPrefix),
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
plPgSql.chopEmptyLines();
}
@ -258,17 +261,18 @@ public class InsertTriggerGenerator {
private void generateInsertPermissionsChecksFooter(final StringWriter plPgSql) {
plPgSql.writeLn();
plPgSql.writeLn("""
raise exception '[403] insert into ${rawSubTable} values(%) not allowed for current subjects % (%)',
raise exception '[403] insert into ${rawSubTableWithSchema} values(%) not allowed for current subjects % (%)',
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
before insert on ${rawSubTableWithSchema}
for each row
execute procedure ${rawSubTable}_insert_permission_check_tf();
execute procedure ${rawSubTableWithSchema}_insert_permission_check_tf();
--//
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
with("rawSubTableWithSchema", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()),
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}
private String toStringList(final Set<RbacView.CaseDef> cases) {

View File

@ -90,11 +90,11 @@ public class RbacView {
* @param <E>
* a JPA entity class extending RbacObject
*/
public static <E extends BaseEntity> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
public static <E extends BaseEntity<?>> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
return new RbacView(alias, entityClass);
}
RbacView(final String alias, final Class<? extends BaseEntity> entityClass) {
RbacView(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
rootEntityAlias = new EntityAlias(alias, entityClass);
entityAliases.put(alias, rootEntityAlias);
new RbacSubjectReference(CREATOR);
@ -121,7 +121,7 @@ public class RbacView {
* <p>An identity view is a view which maps an objectUuid to an idName.
* The idName should be a human-readable representation of the row, but as short as possible.
* The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'.
* It's used to create the object-specific-role-names like test_customer#abc:ADMIN - here 'abc' is the idName.
* It's used to create the object-specific-role-names like rbactest.customer#abc:ADMIN - here 'abc' is the idName.
* The idName not necessarily unique in a table, but it should be avoided.
* </p>
*
@ -287,9 +287,9 @@ public class RbacView {
* @param <EC>
* a JPA entity class extending RbacObject
*/
public <EC extends BaseEntity> RbacView importRootEntityAliasProxy(
public <EC extends BaseEntity<?>> RbacView importRootEntityAliasProxy(
final String aliasName,
final Class<? extends BaseEntity> entityClass,
final Class<? extends BaseEntity<?>> entityClass,
final ColumnValue forCase,
final SQL fetchSql,
final Column dependsOnColum) {
@ -313,7 +313,7 @@ public class RbacView {
* a JPA entity class extending RbacObject
*/
public RbacView importSubEntityAlias(
final String aliasName, final Class<? extends BaseEntity> entityClass,
final String aliasName, final Class<? extends BaseEntity<?>> entityClass,
final SQL fetchSql, final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
return this;
@ -350,14 +350,14 @@ public class RbacView {
* a JPA entity class extending RbacObject
*/
public RbacView importEntityAlias(
final String aliasName, final Class<? extends BaseEntity> entityClass, final ColumnValue usingCase,
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
return this;
}
private EntityAlias importEntityAliasImpl(
final String aliasName, final Class<? extends BaseEntity> entityClass, final ColumnValue usingCase,
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
final var entityAlias = ofNullable(entityAliases.get(aliasName))
@ -911,13 +911,13 @@ public class RbacView {
return distinctGrantDef;
}
record EntityAlias(String aliasName, Class<? extends BaseEntity> entityClass, ColumnValue usingCase, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
record EntityAlias(String aliasName, Class<? extends BaseEntity<?>> entityClass, ColumnValue usingCase, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
public EntityAlias(final String aliasName) {
this(aliasName, null, null, null, null, false, null);
}
public EntityAlias(final String aliasName, final Class<? extends BaseEntity> entityClass) {
public EntityAlias(final String aliasName, final Class<? extends BaseEntity<?>> entityClass) {
this(aliasName, entityClass, null, null, null, false, null);
}
@ -964,7 +964,7 @@ public class RbacView {
if ( aliasName.equals("rbac.global")) {
return "rbac.global"; // TODO: maybe we should introduce a GlobalEntity class?
}
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
return qualifiedRealTableName(entityClass);
}
String getRawTableSchemaPrefix() {
@ -1010,8 +1010,12 @@ public class RbacView {
}
}
public static String withoutRvSuffix(final String tableName) {
return tableName.substring(0, tableName.length() - "_rv".length());
public static String qualifiedRealTableName(final Class<? extends BaseEntity<?>> entityClass) {
final var tableAnnotation = entityClass.getAnnotation(Table.class);
final var schema = tableAnnotation.schema();
final var tableName = tableAnnotation.name();
final var realTableName = tableName.substring(0, tableName.length() - "_rv".length());
return (schema.isEmpty() ? "" : (schema + ".")) + realTableName;
}
public enum Role {

View File

@ -17,7 +17,7 @@ public class RbacViewPostgresGenerator {
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
rbacDef = forRbacDef;
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-");
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-").replace(".", "-");
plPgSql.writeLn("""
--liquibase formatted sql
-- This code generated was by ${generator}, do not amend manually.

View File

@ -516,7 +516,7 @@ class RolesGrantsAndPermissionsGenerator {
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
*/
create or replace function insertTriggerFor${simpleEntityName}_tf()
returns trigger
language plpgsql
@ -525,7 +525,7 @@ class RolesGrantsAndPermissionsGenerator {
call buildRbacSystemFor${simpleEntityName}(NEW);
return NEW;
end; $$;
create trigger insertTriggerFor${simpleEntityName}_tg
after insert on ${rawTableName}
for each row

View File

@ -19,9 +19,11 @@ public class StringWriter {
writeLn();
}
void writeLn(final String text, final VarDef... varDefs) {
string.append( indented( new VarReplacer(varDefs).apply(text) ));
String writeLn(final String text, final VarDef... varDefs) {
final var insertText = indented(new VarReplacer(varDefs).apply(text));
string.append(insertText);
writeLn();
return insertText;
}
void writeLn() {

View File

@ -79,10 +79,10 @@ public class RbacGrantsDiagramService {
return;
}
if ( !g.getDescendantIdName().startsWith("role:rbac.global")) {
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":test_")) {
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":rbactest.")) {
return;
}
if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(":test_")) {
if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(":rbactest.")) {
return;
}
}

View File

@ -20,7 +20,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
@Entity
@Table(name = "test_customer_rv")
@Table(schema = "rbactest", name = "customer_rv")
@Getter
@Setter
@NoArgsConstructor
@ -62,6 +62,6 @@ public class TestCustomerEntity implements BaseEntity<TestCustomerEntity> {
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("2-test/201-test-customer/2013-test-customer-rbac");
rbac().generateWithBaseFileName("2-rbactest/201-rbactest-customer/2013-rbactest-customer-rbac");
}
}

View File

@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetc
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
@Entity
@Table(name = "test_domain_rv")
@Table(schema = "rbactest", name = "domain_rv")
@Getter
@Setter
@NoArgsConstructor
@ -68,6 +68,6 @@ public class TestDomainEntity implements BaseEntity<TestDomainEntity> {
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("2-test/203-test-domain/2033-test-domain-rbac");
rbac().generateWithBaseFileName("2-rbactest/203-rbactest-domain/2033-rbactest-domain-rbac");
}
}

View File

@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
@Entity
@Table(name = "test_package_rv")
@Table(schema = "rbactest", name = "package_rv")
@Getter
@Setter
@NoArgsConstructor
@ -69,6 +69,6 @@ public class TestPackageEntity implements BaseEntity<TestPackageEntity> {
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("2-test/202-test-package/2023-test-package-rbac");
rbac().generateWithBaseFileName("2-rbactest/202-rbactest-package/2023-rbactest-package-rbac");
}
}

View File

@ -6,15 +6,31 @@
--changeset michael.hoennig:table-columns-function endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function base.tableColumnNames( tableName text )
create or replace function base.tableColumnNames( ofTableName text )
returns text
stable
language 'plpgsql' as $$
declare columns text[];
declare
tableName text;
tableSchema text;
columns text[];
begin
tableSchema := CASE
WHEN position('.' in ofTableName) > 0 THEN split_part(ofTableName, '.', 1)
ELSE 'public'
END;
tableName := CASE
WHEN position('.' in ofTableName) > 0 THEN split_part(ofTableName, '.', 2)
ELSE ofTableName
END;
columns := (select array(select column_name::text
from information_schema.columns
where table_name = tableName));
from information_schema.columns
where table_name = tableName
and table_schema = tableSchema));
assert cardinality(columns) > 0, 'cannot determine columns of table ' || ofTableName ||
'("' || tableSchema || '"."' || tableName || '")';
return array_to_string(columns, ', ');
end; $$
--//

View File

@ -127,6 +127,7 @@ begin
end; $$;
--//
-- ============================================================================
--changeset michael.hoennig:context-base.ASSUMED-ROLES endDelimiter:--//
-- ----------------------------------------------------------------------------

View File

@ -0,0 +1,18 @@
--liquibase formatted sql
-- ============================================================================
--changeset michael.hoennig:base-COMBINE-TABLE-SCHEMA-AND-NAME endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function base.combine_table_schema_and_name(tableSchema name, tableName name)
returns text
language plpgsql as $$
begin
if tableSchema is null or tableSchema = 'public' or tableSchema = '' then
return tableName::text;
else
return tableSchema::text || '.' || tableName::text;
end if;
end; $$;
--//

View File

@ -77,9 +77,11 @@ create or replace function base.tx_journal_trigger()
declare
curTask text;
curTxId xid8;
tableSchemaAndName text;
begin
curTask := base.currentTask();
curTxId := pg_current_xact_id();
tableSchemaAndName := base.combine_table_schema_and_name(tg_table_schema, tg_table_name);
insert
into base.tx_context (txId, txTimestamp, currentSubject, assumedRoles, currentTask, currentRequest)
@ -90,20 +92,20 @@ begin
case tg_op
when 'INSERT' then insert
into base.tx_journal
values (curTxId,
tg_table_name, new.uuid, tg_op::base.tx_operation,
values (curTxId, tableSchemaAndName,
new.uuid, tg_op::base.tx_operation,
to_jsonb(new));
when 'UPDATE' then insert
into base.tx_journal
values (curTxId,
tg_table_name, old.uuid, tg_op::base.tx_operation,
values (curTxId, tableSchemaAndName,
old.uuid, tg_op::base.tx_operation,
base.jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
when 'DELETE' then insert
into base.tx_journal
values (curTxId,
tg_table_name, old.uuid, 'DELETE'::base.tx_operation,
values (curTxId,tableSchemaAndName,
old.uuid, 'DELETE'::base.tx_operation,
null::jsonb);
else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name;
else raise exception 'Trigger op % not supported for %.', tg_op, tableSchemaAndName;
end case;
return null;
end; $$;

View File

@ -81,8 +81,8 @@ begin
"alive" := false;
end if;
sql := format('INSERT INTO %3$I_ex VALUES (DEFAULT, pg_current_xact_id(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
raise notice 'sql: %', sql;
sql := format('INSERT INTO %3$I_ex VALUES (DEFAULT, pg_current_xact_id(), %1$L, %2$L, $1.*)',
TG_OP, alive, base.combine_table_schema_and_name(tg_table_schema, tg_table_name)::name);
execute sql using "row";
return "row";

View File

@ -3,9 +3,7 @@
-- ============================================================================
--changeset michael.hoennig:rbac-base-REFERENCE endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
*/
create type rbac.ReferenceType as enum ('rbac.subject', 'rbac.role', 'rbac.permission');
create table rbac.reference
@ -120,18 +118,20 @@ create or replace function rbac.insert_related_object()
strict as $$
declare
objectUuid uuid;
tableSchemaAndName text;
begin
tableSchemaAndName := base.combine_table_schema_and_name(TG_TABLE_SCHEMA, TG_TABLE_NAME);
if TG_OP = 'INSERT' then
if NEW.uuid is null then
insert
into rbac.object (objectTable)
values (TG_TABLE_NAME)
values (tableSchemaAndName)
returning uuid into objectUuid;
NEW.uuid = objectUuid;
else
insert
into rbac.object (uuid, objectTable)
values (NEW.uuid, TG_TABLE_NAME)
values (NEW.uuid, tableSchemaAndName)
returning uuid into objectUuid;
end if;
return NEW;

View File

@ -8,26 +8,40 @@
create or replace procedure rbac.generateRelatedRbacObject(targetTable varchar)
language plpgsql as $$
declare
targetTableName text;
targetSchemaPrefix text;
createInsertTriggerSQL text;
createDeleteTriggerSQL text;
begin
if POSITION('.' IN targetTable) > 0 then
targetSchemaPrefix := SPLIT_PART(targetTable, '.', 1) || '.';
targetTableName := SPLIT_PART(targetTable, '.', 2);
else
targetSchemaPrefix := '';
targetTableName := targetTable;
end if;
if targetSchemaPrefix = '' and targetTableName = 'customer' then
raise exception 'missing targetShemaPrefix: %', targetTable;
end if;
createInsertTriggerSQL = format($sql$
create trigger createRbacObjectFor_%s_Trigger
before insert on %s
create trigger createRbacObjectFor_%s_insert_tg_1058_25
before insert on %s%s
for each row
execute procedure rbac.insert_related_object();
$sql$, targetTable, targetTable);
$sql$, targetTableName, targetSchemaPrefix, targetTableName);
execute createInsertTriggerSQL;
createDeleteTriggerSQL = format($sql$
create trigger delete_related_rbac_rules_for_%s_tg
after delete
on %s
create trigger createRbacObjectFor_%s_delete_tg_1058_35
after delete on %s%s
for each row
execute procedure rbac.delete_related_rbac_rules_tf();
$sql$, targetTable, targetTable);
$sql$, targetTableName, targetSchemaPrefix, targetTableName);
execute createDeleteTriggerSQL;
end; $$;
end;
$$;
--//
@ -176,7 +190,7 @@ begin
*/
sql := format($sql$
create or replace view %1$s_rv as
with accessible_%1$s_uuids as (
with accessible_uuids as (
with recursive
recursive_grants as
(select distinct rbac.grants.descendantuuid,
@ -209,7 +223,7 @@ begin
)
select target.*
from %1$s as target
where target.uuid in (select * from accessible_%1$s_uuids)
where target.uuid in (select * from accessible_uuids)
order by %2$s;
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
@ -219,9 +233,9 @@ begin
/**
Instead of insert trigger function for the restricted view.
*/
newColumns := 'new.' || replace(columnNames, ',', ', new.');
newColumns := 'new.' || replace(columnNames, ', ', ', new.');
sql := format($sql$
create or replace function %1$sInsert()
create function %1$s_instead_of_insert_tf()
returns trigger
language plpgsql as $f$
declare
@ -240,11 +254,11 @@ begin
Creates an instead of insert trigger for the restricted view.
*/
sql := format($sql$
create trigger %1$sInsert_tg
create trigger instead_of_insert_tg
instead of insert
on %1$s_rv
for each row
execute function %1$sInsert();
execute function %1$s_instead_of_insert_tf();
$sql$, targetTable);
execute sql;
@ -252,7 +266,7 @@ begin
Instead of delete trigger function for the restricted view.
*/
sql := format($sql$
create or replace function %1$sDelete()
create function %1$s_instead_of_delete_tf()
returns trigger
language plpgsql as $f$
begin
@ -269,11 +283,11 @@ begin
Creates an instead of delete trigger for the restricted view.
*/
sql := format($sql$
create trigger %1$sDelete_tg
create trigger instead_of_delete_tg
instead of delete
on %1$s_rv
for each row
execute function %1$sDelete();
execute function %1$s_instead_of_delete_tf();
$sql$, targetTable);
execute sql;
@ -283,7 +297,7 @@ begin
*/
if columnUpdates is not null then
sql := format($sql$
create or replace function %1$sUpdate()
create function %1$s_instead_of_update_tf()
returns trigger
language plpgsql as $f$
begin
@ -302,11 +316,11 @@ begin
Creates an instead of delete trigger for the restricted view.
*/
sql = format($sql$
create trigger %1$sUpdate_tg
create trigger instead_of_update_tg
instead of update
on %1$s_rv
for each row
execute function %1$sUpdate();
execute function %1$s_instead_of_update_tf();
$sql$, targetTable);
execute sql;
end if;

View File

@ -0,0 +1,8 @@
--liquibase formatted sql
-- ============================================================================
--changeset michael.hoennig:rbactest-SCHEMA endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SCHEMA rbactest; -- just 'test' does not work, databasechangelog gets emptied or deleted
--//

View File

@ -4,7 +4,7 @@
--changeset michael.hoennig:test-customer-MAIN-TABLE endDelimiter:--//
-- ----------------------------------------------------------------------------
create table if not exists test_customer
create table if not exists rbactest.customer
(
uuid uuid unique references rbac.object (uuid),
version int not null default 0,

View File

@ -3,21 +3,21 @@
-- ============================================================================
--changeset RbacObjectGenerator:test-customer-rbac-OBJECT endDelimiter:--//
--changeset RbacObjectGenerator:rbactest-customer-rbac-OBJECT endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRelatedRbacObject('test_customer');
call rbac.generateRelatedRbacObject('rbactest.customer');
--//
-- ============================================================================
--changeset RbacRoleDescriptorsGenerator:test-customer-rbac-ROLE-DESCRIPTORS endDelimiter:--//
--changeset RbacRoleDescriptorsGenerator:rbactest-customer-rbac-ROLE-DESCRIPTORS endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRbacRoleDescriptors('testCustomer', 'test_customer');
call rbac.generateRbacRoleDescriptors('testCustomer', 'rbactest.customer');
--//
-- ============================================================================
--changeset RolesGrantsAndPermissionsGenerator:test-customer-rbac-insert-trigger endDelimiter:--//
--changeset RolesGrantsAndPermissionsGenerator:rbactest-customer-rbac-insert-trigger endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
@ -25,7 +25,7 @@ call rbac.generateRbacRoleDescriptors('testCustomer', 'test_customer');
*/
create or replace procedure buildRbacSystemForTestCustomer(
NEW test_customer
NEW rbactest.customer
)
language plpgsql as $$
@ -57,7 +57,7 @@ begin
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new test_customer row.
AFTER INSERT TRIGGER to create the role+grant structure for a new rbactest.customer row.
*/
create or replace function insertTriggerForTestCustomer_tf()
@ -70,68 +70,68 @@ begin
end; $$;
create trigger insertTriggerForTestCustomer_tg
after insert on test_customer
after insert on rbactest.customer
for each row
execute procedure insertTriggerForTestCustomer_tf();
--//
-- ============================================================================
--changeset InsertTriggerGenerator:test-customer-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
--changeset InsertTriggerGenerator:rbactest-customer-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
-- ----------------------------------------------------------------------------
-- granting INSERT permission to rbac.global ----------------------------
/*
Grants INSERT INTO test_customer permissions to specified role of pre-existing rbac.global rows.
Grants INSERT INTO rbactest.customer permissions to specified role of pre-existing rbac.global rows.
*/
do language plpgsql $$
declare
row rbac.global;
begin
call base.defineContext('create INSERT INTO test_customer permissions for pre-exising rbac.global rows');
call base.defineContext('create INSERT INTO rbactest.customer permissions for pre-exising rbac.global rows');
FOR row IN SELECT * FROM rbac.global
-- unconditional for all rows in that table
LOOP
call rbac.grantPermissionToRole(
rbac.createPermission(row.uuid, 'INSERT', 'test_customer'),
rbac.createPermission(row.uuid, 'INSERT', 'rbactest.customer'),
rbac.globalADMIN());
END LOOP;
end;
$$;
/**
Grants test_customer INSERT permission to specified role of new global rows.
Grants rbactest.customer INSERT permission to specified role of new global rows.
*/
create or replace function rbac.new_test_customer_grants_insert_to_global_tf()
create or replace function rbactest.new_customer_grants_insert_to_global_tf()
returns trigger
language plpgsql
strict as $$
begin
-- unconditional for all rows in that table
call rbac.grantPermissionToRole(
rbac.createPermission(NEW.uuid, 'INSERT', 'test_customer'),
rbac.createPermission(NEW.uuid, 'INSERT', 'rbactest.customer'),
rbac.globalADMIN());
-- 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_new_test_customer_grants_after_insert_tg
create trigger z_new_customer_grants_after_insert_tg
after insert on rbac.global
for each row
execute procedure rbac.new_test_customer_grants_insert_to_global_tf();
execute procedure rbactest.new_customer_grants_insert_to_global_tf();
-- ============================================================================
--changeset InsertTriggerGenerator:test_customer-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
--changeset InsertTriggerGenerator:rbactest-customer-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user respectively the assumed roles are allowed to insert a row to test_customer.
Checks if the user respectively the assumed roles are allowed to insert a row to rbactest.customer.
*/
create or replace function test_customer_insert_permission_check_tf()
create or replace function rbactest.customer_insert_permission_check_tf()
returns trigger
language plpgsql as $$
declare
@ -142,22 +142,22 @@ begin
return NEW;
end if;
raise exception '[403] insert into test_customer values(%) not allowed for current subjects % (%)',
raise exception '[403] insert into rbactest.customer values(%) not allowed for current subjects % (%)',
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
end; $$;
create trigger test_customer_insert_permission_check_tg
before insert on test_customer
create trigger customer_insert_permission_check_tg
before insert on rbactest.customer
for each row
execute procedure test_customer_insert_permission_check_tf();
execute procedure rbactest.customer_insert_permission_check_tf();
--//
-- ============================================================================
--changeset RbacIdentityViewGenerator:test-customer-rbac-IDENTITY-VIEW endDelimiter:--//
--changeset RbacIdentityViewGenerator:rbactest-customer-rbac-IDENTITY-VIEW endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRbacIdentityViewFromProjection('test_customer',
call rbac.generateRbacIdentityViewFromProjection('rbactest.customer',
$idName$
prefix
$idName$);
@ -165,9 +165,9 @@ call rbac.generateRbacIdentityViewFromProjection('test_customer',
-- ============================================================================
--changeset RbacRestrictedViewGenerator:test-customer-rbac-RESTRICTED-VIEW endDelimiter:--//
--changeset RbacRestrictedViewGenerator:rbactest-customer-rbac-RESTRICTED-VIEW endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRbacRestrictedView('test_customer',
call rbac.generateRbacRestrictedView('rbactest.customer',
$orderBy$
reference
$orderBy$,

View File

@ -28,18 +28,18 @@ declare
custRowId uuid;
custAdminName varchar;
custAdminUuid uuid;
newCust test_customer;
newCust rbactest.customer;
begin
custRowId = uuid_generate_v4();
custAdminName = 'customer-admin@' || custPrefix || '.example.com';
custAdminUuid = rbac.create_subject(custAdminName);
insert
into test_customer (reference, prefix, adminUserName)
into rbactest.customer (reference, prefix, adminUserName)
values (custReference, custPrefix, custAdminName);
select * into newCust
from test_customer where reference=custReference;
from rbactest.customer where reference=custReference;
call rbac.grantRoleToSubject(
rbac.getRoleId(testCustomerOwner(newCust)),
rbac.getRoleId(testCustomerAdmin(newCust)),

View File

@ -4,11 +4,11 @@
--changeset michael.hoennig:test-package-MAIN-TABLE endDelimiter:--//
-- ----------------------------------------------------------------------------
create table if not exists test_package
create table if not exists rbactest.package
(
uuid uuid unique references rbac.object (uuid),
version int not null default 0,
customerUuid uuid references test_customer (uuid),
customerUuid uuid references rbactest.customer (uuid),
name varchar(5),
description varchar(96)
);

View File

@ -3,21 +3,21 @@
-- ============================================================================
--changeset RbacObjectGenerator:test-package-rbac-OBJECT endDelimiter:--//
--changeset RbacObjectGenerator:rbactest-package-rbac-OBJECT endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRelatedRbacObject('test_package');
call rbac.generateRelatedRbacObject('rbactest.package');
--//
-- ============================================================================
--changeset RbacRoleDescriptorsGenerator:test-package-rbac-ROLE-DESCRIPTORS endDelimiter:--//
--changeset RbacRoleDescriptorsGenerator:rbactest-package-rbac-ROLE-DESCRIPTORS endDelimiter:--//
-- ----------------------------------------------------------------------------
call rbac.generateRbacRoleDescriptors('testPackage', 'test_package');
call rbac.generateRbacRoleDescriptors('testPackage', 'rbactest.package');
--//
-- ============================================================================
--changeset RolesGrantsAndPermissionsGenerator:test-package-rbac-insert-trigger endDelimiter:--//
--changeset RolesGrantsAndPermissionsGenerator:rbactest-package-rbac-insert-trigger endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
@ -25,17 +25,17 @@ call rbac.generateRbacRoleDescriptors('testPackage', 'test_package');
*/
create or replace procedure buildRbacSystemForTestPackage(
NEW test_package
NEW rbactest.package
)
language plpgsql as $$
declare
newCustomer test_customer;
newCustomer rbactest.customer;
begin
call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer;
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
@ -61,7 +61,7 @@ begin
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new test_package row.
AFTER INSERT TRIGGER to create the role+grant structure for a new rbactest.package row.
*/
create or replace function insertTriggerForTestPackage_tf()
@ -74,14 +74,14 @@ begin
end; $$;
create trigger insertTriggerForTestPackage_tg
after insert on test_package
after insert on rbactest.package
for each row
execute procedure insertTriggerForTestPackage_tf();
--//
-- ============================================================================
--changeset RolesGrantsAndPermissionsGenerator:test-package-rbac-update-trigger endDelimiter:--//
--changeset RolesGrantsAndPermissionsGenerator:rbactest-package-rbac-update-trigger endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
@ -89,22 +89,22 @@ execute procedure insertTriggerForTestPackage_tf();
*/
create or replace procedure updateRbacRulesForTestPackage(
OLD test_package,
NEW test_package
OLD rbactest.package,
NEW rbactest.package
)
language plpgsql as $$
declare
oldCustomer test_customer;
newCustomer test_customer;
oldCustomer rbactest.customer;
newCustomer rbactest.customer;
begin
call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM test_customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
SELECT * FROM rbactest.customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer;
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
@ -122,7 +122,7 @@ begin
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new test_package row.
AFTER INSERT TRIGGER to re-wire the grant structure for a new rbactest.package row.
*/
create or replace function updateTriggerForTestPackage_tf()