From 7cb52cdac5ae28f12d301c4ccd8e16cef7560281 Mon Sep 17 00:00:00 2001 From: Kyle Morel Date: Sun, 21 Jul 2024 16:32:41 -0700 Subject: [PATCH] WIP: Add RBAC schema/tables + navigator seeding --- .../db/migrations/20240718000000_007-rbac.ts | 559 ++++++++++++++++++ app/src/db/prisma/schema.prisma | 166 +++++- .../prisma/views/yars/role_permission_vw.sql | 37 ++ 3 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 app/src/db/migrations/20240718000000_007-rbac.ts create mode 100644 app/src/db/prisma/views/yars/role_permission_vw.sql diff --git a/app/src/db/migrations/20240718000000_007-rbac.ts b/app/src/db/migrations/20240718000000_007-rbac.ts new file mode 100644 index 00000000..9cf17ff4 --- /dev/null +++ b/app/src/db/migrations/20240718000000_007-rbac.ts @@ -0,0 +1,559 @@ +/* eslint-disable max-len */ +import stamps from '../stamps'; + +import type { Knex } from 'knex'; + +const resources = [ + { + name: 'document' + }, + { + name: 'enquiry' + }, + { + name: 'navigation' + }, + { + name: 'note' + }, + { + name: 'permit' + }, + { + name: 'roadmap' + }, + { + name: 'sso' + }, + { + name: 'submission' + }, + { + name: 'testing' + }, + { + name: 'user' + } +]; + +const actions = [ + { + name: 'create' + }, + { + name: 'read' + }, + { + name: 'update' + }, + { + name: 'delete' + }, + { + name: 'roleoverride' + } +]; + +export async function up(knex: Knex): Promise { + return ( + Promise.resolve() + // Create schema + .then(() => knex.schema.raw('CREATE SCHEMA IF NOT EXISTS yars')) + + // Create tables + .then(() => + knex.schema.withSchema('yars').createTable('role', (table) => { + table.specificType('role_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table.uuid('initiative_id'); + table.text('user_type').notNullable(); + stamps(knex, table); + table.unique(['initiative_id', 'user_type']); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('scope', (table) => { + table.specificType('scope_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table.text('name').notNullable(); + table.text('description').notNullable(); + stamps(knex, table); + table.unique('name'); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('policy', (table) => { + table.specificType('policy_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table + .integer('scope_id') + .references('scope_id') + .inTable('yars.scope') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.text('name').notNullable(); + table.text('description').notNullable(); + stamps(knex, table); + table.unique('name'); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('action', (table) => { + table.specificType('action_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table.text('name').notNullable(); + stamps(knex, table); + table.unique('name'); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('resource', (table) => { + table.specificType('resource_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table.text('name').notNullable(); + stamps(knex, table); + table.unique('name'); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('permission', (table) => { + table.specificType('permission_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table + .integer('resource_id') + .notNullable() + .references('resource_id') + .inTable('yars.resource') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table + .integer('action_id') + .notNullable() + .references('action_id') + .inTable('yars.action') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + stamps(knex, table); + table.unique(['resource_id', 'action_id']); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('role_policy', (table) => { + table.specificType('role_policy_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table + .integer('role_id') + .notNullable() + .references('role_id') + .inTable('yars.role') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table + .integer('policy_id') + .notNullable() + .references('policy_id') + .inTable('yars.policy') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + stamps(knex, table); + table.unique(['role_id', 'policy_id']); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('policy_permission', (table) => { + table.specificType('policy_permission_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table + .integer('policy_id') + .notNullable() + .references('policy_id') + .inTable('yars.policy') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table + .integer('permission_id') + .notNullable() + .references('permission_id') + .inTable('yars.permission') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + stamps(knex, table); + table.unique(['policy_id', 'permission_id']); + }) + ) + + .then(() => + knex.schema.withSchema('yars').createTable('identity_role', (table) => { + table.specificType('identity_role_id', 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'); + table.text('identity_id').notNullable(); + table + .integer('role_id') + .notNullable() + .references('role_id') + .inTable('yars.role') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + stamps(knex, table); + table.unique(['identity_id', 'role_id']); + }) + ) + + // Create before update triggers + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_role_trigger + BEFORE UPDATE ON yars."role" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_scope_trigger + BEFORE UPDATE ON yars."scope" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_policy_trigger + BEFORE UPDATE ON yars."policy" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_action_trigger + BEFORE UPDATE ON yars."action" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_resource_trigger + BEFORE UPDATE ON yars."resource" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_permission_trigger + BEFORE UPDATE ON yars."permission" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_role_policy_trigger + BEFORE UPDATE ON yars."role_policy" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_policy_permission_trigger + BEFORE UPDATE ON yars."policy_permission" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER before_update_identity_role_trigger + BEFORE UPDATE ON yars."identity_role" + FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`) + ) + + // Create audit triggers + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_role_trigger + AFTER UPDATE OR DELETE ON yars."role" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_scope_trigger + AFTER UPDATE OR DELETE ON yars."scope" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_policy_trigger + AFTER UPDATE OR DELETE ON yars."policy" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_action_trigger + AFTER UPDATE OR DELETE ON yars."action" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_resource_trigger + AFTER UPDATE OR DELETE ON yars."resource" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_role_policy_trigger + AFTER UPDATE OR DELETE ON yars."role_policy" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_policy_permission_trigger + AFTER UPDATE OR DELETE ON yars."policy_permission" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + .then(() => + knex.schema.raw(`CREATE TRIGGER audit_identity_role_trigger + AFTER UPDATE OR DELETE ON yars."identity_role" + FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`) + ) + + // Populate Baseline Data + .then(() => { + const housing_id = knex('initiative') + .where({ + code: 'HOUSING' + }) + .select('initiative_id'); + + const items = [ + { + user_type: 'developer' + }, + { + user_type: 'proponent' + }, + { + initiative_id: housing_id, + user_type: 'navigator' + }, + { + initiative_id: housing_id, + user_type: 'supervisor' + }, + { + initiative_id: housing_id, + user_type: 'admin' + } + ]; + return knex('yars.role').insert(items); + }) + + .then(() => { + const items = [ + { + name: 'all', + description: 'Allows access to all rows of a resource' + } + ]; + return knex('yars.scope').insert(items); + }) + + .then(() => { + return knex('yars.resource').insert(resources); + }) + + .then(() => { + return knex('yars.action').insert(actions); + }) + + .then(() => { + const items = []; + for (const resource of resources) { + for (const action of actions) { + if (resource.name === 'testing' || action.name === 'roleoverride') continue; + + items.push({ + resource_id: knex('yars.resource').where({ name: resource.name }).select('resource_id'), + action_id: knex('yars.action').where({ name: action.name }).select('action_id') + }); + } + } + + items.push({ + resource_id: knex('yars.resource').where({ name: 'testing' }).select('resource_id'), + action_id: knex('yars.action').where({ name: 'roleoverride' }).select('action_id') + }); + + return knex('yars.permission').insert(items); + }) + + .then(() => { + const items = [ + { + name: 'document_manage', + description: 'Full access to all document operations' + }, + { + name: 'enquiry_manage', + description: 'Full access to all enquiry operations' + }, + { + name: 'note_manage', + description: 'Full access to all note operations' + }, + { + name: 'permit_manage', + description: 'Full access to all permit operations' + }, + { + name: 'roadmap_manage', + description: 'Full access to all roadmap operations' + }, + { + name: 'sso_manage', + description: 'Full access to all SSO operation operations' + }, + { + name: 'submission_manage', + description: 'Full access to all submission operations' + }, + { + name: 'user_manage', + description: 'Full access to all user operations' + }, + { + name: 'testing_all', + description: 'Full access to all testing functionality' + } + ]; + + return knex('yars.policy').insert(items); + }) + + .then(async () => { + const permissions = await knex + .select('p.permission_id', 'r.name as resource_name', 'a.name as action_name') + .from({ p: 'yars.permission' }) + .innerJoin({ r: 'yars.resource' }, 'p.resource_id', '=', 'r.resource_id') + .innerJoin({ a: 'yars.action' }, 'p.action_id', '=', 'a.action_id'); + + const items: Array<{ policy_id: number; permission_id: number }> = []; + + const addPolicyPermissions = async (policyName: string, resourceName: string) => { + const document_manage_policy = await knex('yars.policy').where({ name: policyName }).select('policy_id'); + const document_permissions = permissions.filter((x) => x.resource_name === resourceName); + for (const dp of document_permissions) { + items.push({ + policy_id: document_manage_policy[0].policy_id, + permission_id: dp.permission_id + }); + } + }; + + await addPolicyPermissions('document_manage', 'document'); + await addPolicyPermissions('enquiry_manage', 'enquiry'); + await addPolicyPermissions('note_manage', 'note'); + await addPolicyPermissions('permit_manage', 'permit'); + await addPolicyPermissions('roadmap_manage', 'roadmap'); + await addPolicyPermissions('sso_manage', 'sso'); + await addPolicyPermissions('submission_manage', 'submission'); + await addPolicyPermissions('user_manage', 'user'); + + return knex('yars.policy_permission').insert(items); + }) + + .then(() => { + const housing_id = knex('initiative') + .where({ + code: 'HOUSING' + }) + .select('initiative_id'); + + const navigator_role_id = knex('yars.role') + .where({ initiative_id: housing_id, user_type: 'navigator' }) + .select('role_id'); + + const items = [ + /* + * Add all navigator policy mappings + */ + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'document_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'enquiry_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'note_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'permit_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'roadmap_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'sso_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'submission_manage' }).select('policy_id') + }, + { + role_id: navigator_role_id, + policy_id: knex('yars.policy').where({ name: 'user_manage' }).select('policy_id') + } + ]; + return knex('yars.role_policy').insert(items); + }) + + .then(async () => { + await knex.raw(`create view yars.role_permission_vw as select initiative.label as initiative_name, role.user_type, policy.name as policy_name, scope.name as scope_name, resource.name as resource_name, action.name as action_name + from yars.role + inner join public.initiative on yars.role.initiative_id = public.initiative.initiative_id + inner join yars.role_policy on yars.role_policy.role_id = yars.role.role_id + inner join yars.policy on yars.policy.policy_id = yars.role_policy.policy_id + left join yars.scope on yars.policy.scope_id = yars.scope.scope_id + inner join yars.policy_permission on yars.policy_permission.policy_id = yars.policy.policy_id + inner join yars.permission on yars.permission.permission_id = yars.policy_permission.permission_id + inner join yars.resource on resource.resource_id = yars.permission.resource_id + inner join yars.action on yars.action.action_id = yars.permission.action_id;`); + }) + ); +} + +export async function down(knex: Knex): Promise { + return ( + Promise.resolve() + // Drop yars views + .then(() => knex.schema.withSchema('yars').dropViewIfExists('role_permission_vw')) + + // Drop yars audit triggers + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_identity_role_trigger ON yars."identity_role"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_policy_permission_trigger ON yars."policy_permission"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_role_policy_trigger ON yars."role_policy"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_permission_trigger ON yars."permission"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_resource_trigger ON yars."resource"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_action_trigger ON yars."action"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_policy_trigger ON yars."policy"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_scope_trigger ON yars."scope"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_role_trigger ON yars."role"')) + + // Drop yars table triggers + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_identity_role_trigger ON yars."identity_role"')) + .then(() => + knex.schema.raw('DROP TRIGGER IF EXISTS before_update_policy_permission_trigger ON yars."policy_permission"') + ) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_role_policy_trigger ON yars."role_policy"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_permission_trigger ON yars."permission"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_resource_trigger ON yars."resource"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_action_trigger ON yars."action"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_policy_trigger ON yars."policy"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_scope_trigger ON yars."scope"')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_role_trigger ON yars."role"')) + + // Drop yars tables + .then(() => knex.schema.withSchema('yars').dropTableIfExists('identity_role')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('policy_permission')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('role_policy')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('permission')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('resource')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('action')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('policy')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('scope')) + .then(() => knex.schema.withSchema('yars').dropTableIfExists('role')) + + // Drop yars schema + .then(() => knex.schema.dropSchemaIfExists('yars')) + ); +} diff --git a/app/src/db/prisma/schema.prisma b/app/src/db/prisma/schema.prisma index 93fa188c..cd139106 100644 --- a/app/src/db/prisma/schema.prisma +++ b/app/src/db/prisma/schema.prisma @@ -1,10 +1,12 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + previewFeatures = ["multiSchema", "views"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") + schemas = ["public", "yars"] } model knex_migrations { @@ -12,11 +14,15 @@ model knex_migrations { name String? @db.VarChar(255) batch Int? migration_time DateTime? @db.Timestamptz(6) + + @@schema("public") } model knex_migrations_lock { index Int @id @default(autoincrement()) is_locked Int? + + @@schema("public") } model activity { @@ -33,6 +39,8 @@ model activity { note note[] permit permit[] submission submission[] + + @@schema("public") } model document { @@ -48,6 +56,7 @@ model document { activity activity @relation(fields: [activity_id], references: [activity_id], onDelete: Cascade, map: "document_activity_id_foreign") @@unique([document_id, activity_id], map: "document_document_id_activity_id_unique") + @@schema("public") } model identity_provider { @@ -58,6 +67,8 @@ model identity_provider { updated_by String? updated_at DateTime? @db.Timestamptz(6) user user[] + + @@schema("public") } model initiative { @@ -69,6 +80,8 @@ model initiative { updated_by String? updated_at DateTime? @db.Timestamptz(6) activity activity[] + + @@schema("public") } model note { @@ -85,6 +98,8 @@ model note { bring_forward_state String? is_deleted Boolean @default(false) activity activity @relation(fields: [activity_id], references: [activity_id], onDelete: Cascade, map: "note_activity_id_foreign") + + @@schema("public") } model permit { @@ -107,6 +122,7 @@ model permit { permit_type permit_type @relation(fields: [permit_type_id], references: [permit_type_id], onDelete: Cascade, map: "permit_permit_type_id_foreign") @@unique([permit_id, permit_type_id, activity_id], map: "permit_permit_id_permit_type_id_activity_id_unique") + @@schema("public") } model permit_type { @@ -128,6 +144,8 @@ model permit_type { updated_by String? updated_at DateTime? @db.Timestamptz(6) permit permit[] + + @@schema("public") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. @@ -193,6 +211,8 @@ model submission { contact_last_name String? activity activity @relation(fields: [activity_id], references: [activity_id], onDelete: Cascade, map: "submission_activity_id_foreign") user user? @relation(fields: [assigned_user_id], references: [user_id], onDelete: Cascade, map: "submission_assigned_user_id_foreign") + + @@schema("public") } model user { @@ -216,6 +236,7 @@ model user { @@index([email], map: "user_email_index") @@index([identity_id], map: "user_identity_id_index") @@index([username], map: "user_username_index") + @@schema("public") } model enquiry { @@ -244,4 +265,147 @@ model enquiry { updated_at DateTime? @db.Timestamptz(6) activity activity @relation(fields: [activity_id], references: [activity_id], onDelete: Cascade, map: "enquiry_activity_id_foreign") user user? @relation(fields: [assigned_user_id], references: [user_id], onDelete: Cascade, map: "enquiry_assigned_user_id_foreign") + + @@schema("public") +} + +model action { + action_id Int @id @default(autoincrement()) + name String @unique(map: "action_name_unique") + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + permission permission[] + + @@schema("yars") +} + +model identity_role { + identity_role_id Int @id @default(autoincrement()) + identity_id String + role_id Int + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + role role @relation(fields: [role_id], references: [role_id], onDelete: Cascade, map: "identity_role_role_id_foreign") + + @@unique([identity_id, role_id], map: "identity_role_identity_id_role_id_unique") + @@schema("yars") +} + +model permission { + permission_id Int @id @default(autoincrement()) + resource_id Int + action_id Int + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + action action @relation(fields: [action_id], references: [action_id], onDelete: Cascade, map: "permission_action_id_foreign") + resource resource @relation(fields: [resource_id], references: [resource_id], onDelete: Cascade, map: "permission_resource_id_foreign") + policy_permission policy_permission[] + + @@unique([resource_id, action_id], map: "permission_resource_id_action_id_unique") + @@schema("yars") +} + +model policy { + policy_id Int @id @default(autoincrement()) + scope_id Int? + name String @unique(map: "policy_name_unique") + description String + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + scope scope? @relation(fields: [scope_id], references: [scope_id], onDelete: Cascade, map: "policy_scope_id_foreign") + policy_permission policy_permission[] + role_policy role_policy[] + + @@schema("yars") +} + +model policy_permission { + policy_permission_id Int @id @default(autoincrement()) + policy_id Int + permission_id Int + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + permission permission @relation(fields: [permission_id], references: [permission_id], onDelete: Cascade, map: "policy_permission_permission_id_foreign") + policy policy @relation(fields: [policy_id], references: [policy_id], onDelete: Cascade, map: "policy_permission_policy_id_foreign") + + @@unique([policy_id, permission_id], map: "policy_permission_policy_id_permission_id_unique") + @@schema("yars") +} + +model resource { + resource_id Int @id @default(autoincrement()) + name String @unique(map: "resource_name_unique") + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + permission permission[] + + @@schema("yars") +} + +model role { + role_id Int @id @default(autoincrement()) + initiative_id String? @db.Uuid + user_type String + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + identity_role identity_role[] + role_policy role_policy[] + + @@unique([initiative_id, user_type], map: "role_initiative_id_user_type_unique") + @@schema("yars") +} + +model role_policy { + role_policy_id Int @id @default(autoincrement()) + role_id Int + policy_id Int + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + policy policy @relation(fields: [policy_id], references: [policy_id], onDelete: Cascade, map: "role_policy_policy_id_foreign") + role role @relation(fields: [role_id], references: [role_id], onDelete: Cascade, map: "role_policy_role_id_foreign") + + @@unique([role_id, policy_id], map: "role_policy_role_id_policy_id_unique") + @@schema("yars") +} + +model scope { + scope_id Int @id @default(autoincrement()) + name String @unique(map: "scope_name_unique") + description String + created_by String? @default("00000000-0000-0000-0000-000000000000") + created_at DateTime? @default(now()) @db.Timestamptz(6) + updated_by String? + updated_at DateTime? @db.Timestamptz(6) + policy policy[] + + @@schema("yars") +} + +/// The underlying view does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client. +view role_permission_vw { + initiative_name String? + user_type String? + policy_name String? + scope_name String? + resource_name String? + action_name String? + + @@ignore + @@schema("yars") } diff --git a/app/src/db/prisma/views/yars/role_permission_vw.sql b/app/src/db/prisma/views/yars/role_permission_vw.sql new file mode 100644 index 00000000..40cbe328 --- /dev/null +++ b/app/src/db/prisma/views/yars/role_permission_vw.sql @@ -0,0 +1,37 @@ +SELECT + initiative.label AS initiative_name, + role.user_type, + policy.name AS policy_name, + scope.name AS scope_name, + resource.name AS resource_name, + ACTION.name AS action_name +FROM + ( + ( + ( + ( + ( + ( + ( + ( + yars.role + JOIN initiative ON ((role.initiative_id = initiative.initiative_id)) + ) + JOIN yars.role_policy ON ((role_policy.role_id = role.role_id)) + ) + JOIN yars.policy ON ((policy.policy_id = role_policy.policy_id)) + ) + LEFT JOIN yars.scope ON ((policy.scope_id = scope.scope_id)) + ) + JOIN yars.policy_permission ON ((policy_permission.policy_id = policy.policy_id)) + ) + JOIN yars.permission ON ( + ( + permission.permission_id = policy_permission.permission_id + ) + ) + ) + JOIN yars.resource ON ((resource.resource_id = permission.resource_id)) + ) + JOIN yars.action ON ((ACTION.action_id = permission.action_id)) + ); \ No newline at end of file