Michael Hoennig
2022-10-17 63db9395831cc4f8bbd368fea71bbd4bd4857908
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
--liquibase formatted sql
 
 
-- ============================================================================
--changeset rbac-generators-RELATED-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
 
create or replace procedure generateRelatedRbacObject(targetTable varchar)
    language plpgsql as $$
declare
    createInsertTriggerSQL text;
    createDeleteTriggerSQL text;
begin
    createInsertTriggerSQL = format($sql$
        create trigger createRbacObjectFor_%s_Trigger
            before insert
            on %s
            for each row
                execute procedure insertRelatedRbacObject();
        $sql$, targetTable, targetTable);
    execute createInsertTriggerSQL;
 
    createDeleteTriggerSQL = format($sql$
        create trigger deleteRbacRulesFor_%s_Trigger
            after delete
            on %s
            for each row
                execute procedure deleteRelatedRbacObject();
        $sql$, targetTable, targetTable);
    execute createDeleteTriggerSQL;
end; $$;
--//
 
 
-- ============================================================================
--changeset rbac-generators-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
 
create or replace procedure generateRbacRoleDescriptors(prefix text, targetTable text)
    language plpgsql as $$
declare
    sql text;
begin
    sql = format($sql$
        create or replace function %1$sOwner(entity %2$s)
            returns RbacRoleDescriptor
            language plpgsql
            strict as $f$
        begin
            return roleDescriptor('%2$s', entity.uuid, 'owner');
        end; $f$;
 
        create or replace function %1$sAdmin(entity %2$s)
            returns RbacRoleDescriptor
            language plpgsql
            strict as $f$
        begin
            return roleDescriptor('%2$s', entity.uuid, 'admin');
        end; $f$;
 
        create or replace function %1$sAgent(entity %2$s)
            returns RbacRoleDescriptor
            language plpgsql
            strict as $f$
        begin
            return roleDescriptor('%2$s', entity.uuid, 'agent');
        end; $f$;
 
        create or replace function %1$sTenant(entity %2$s)
            returns RbacRoleDescriptor
            language plpgsql
            strict as $f$
        begin
            return roleDescriptor('%2$s', entity.uuid, 'tenant');
        end; $f$;
 
        create or replace function %1$sGuest(entity %2$s)
            returns RbacRoleDescriptor
            language plpgsql
            strict as $f$
        begin
            return roleDescriptor('%2$s', entity.uuid, 'guest');
        end; $f$;
 
        $sql$, prefix, targetTable);
    execute sql;
end; $$;
--//
 
 
-- ============================================================================
--changeset rbac-generators-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
 
create or replace procedure generateRbacIdentityView(targetTable text, idNameExpression text)
    language plpgsql as $$
declare
    sql text;
begin
    -- create a view to the target main table which maps an idName to the objectUuid
    sql = format($sql$
            create or replace view %1$s_iv as
            select target.uuid, cleanIdentifier(%2$s) as idName
                from %1$s as target;
            grant all privileges on %1$s_iv to restricted;
        $sql$, targetTable, idNameExpression);
    execute sql;
 
    -- creates a function which maps an idName to the objectUuid
    sql = format($sql$
        create or replace function %1$sUuidByIdName(givenIdName varchar)
            returns uuid
            language sql
            strict as $f$
        select uuid from %1$s_iv iv where iv.idName = givenIdName;
        $f$;
        $sql$, targetTable);
    execute sql;
 
    -- creates a function which maps an objectUuid to the related idName
    sql = format($sql$
        create or replace function %1$sIdNameByUuid(givenUuid uuid)
            returns varchar
            language sql
            strict as $f$
        select idName from %1$s_iv iv where iv.uuid = givenUuid;
        $f$;
    $sql$, targetTable);
    execute sql;
end; $$;
--//
 
 
-- ============================================================================
--changeset rbac-generators-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
 
create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text)
    language plpgsql as $$
declare
    sql text;
begin
    /*
        Creates a restricted view based on the 'view' permission of the current subject.
    */
    sql := format($sql$
        set session session authorization default;
        create view %1$s_rv as
            with accessibleObjects as (
                select queryAccessibleObjectUuidsOfSubjectIds('view', '%1$s', currentSubjectsUuids())
            )
            select target.*
                from %1$s as target
                where target.uuid in (select * from accessibleObjects)
                order by %2$s;
            grant all privileges on %1$s_rv to restricted;
        $sql$, targetTable, orderBy);
    execute sql;
 
    /**
        Instead of insert trigger function for the restricted view.
     */
    sql := format($sql$
        create or replace function %1$sInsert()
            returns trigger
            language plpgsql as $f$
        declare
            newTargetRow %1$s;
        begin
            insert
                into %1$s
                values (new.*)
                returning * into newTargetRow;
            return newTargetRow;
        end; $f$;
        $sql$, targetTable);
    execute sql;
 
    /*
        Creates an instead of insert trigger for the restricted view.
     */
    sql := format($sql$
        create trigger %1$sInsert_tg
            instead of insert
            on %1$s_rv
            for each row
        execute function %1$sInsert();
    $sql$, targetTable);
    execute sql;
 
    /**
        Instead of delete trigger function for the restricted view.
     */
    sql := format($sql$
        create or replace function %1$sDelete()
            returns trigger
            language plpgsql as $f$
        begin
            if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', '%1$s', currentSubjectsUuids())) then
                delete from %1$s p where p.uuid = old.uuid;
                return old;
            end if;
            raise exception '[403] Subject %% is not allowed to delete %1$s uuid %%', currentSubjectsUuids(), old.uuid;
        end; $f$;
    $sql$, targetTable);
    execute sql;
 
    /*
        Creates an instead of delete trigger for the restricted view.
     */
    sql := format($sql$
        create trigger %1$sDelete_tg
            instead of delete
            on %1$s_rv
            for each row
        execute function %1$sDelete();
    $sql$, targetTable);
    execute sql;
 
    /**
        Instead of update trigger function for the restricted view
        based on the 'edit' permission of the current subject.
     */
    sql := format($sql$
        create or replace function %1$sUpdate()
            returns trigger
            language plpgsql as $f$
        begin
            if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('edit', '%1$s', currentSubjectsUuids())) then
                update %1$s
                    set %2$s
                    where uuid = old.uuid;
                return old;
            end if;
            raise exception '[403] Subject %% is not allowed to update %1$s uuid %%', currentSubjectsUuids(), old.uuid;
        end; $f$;
    $sql$, targetTable, columnUpdates);
    execute sql;
 
    /*
        Creates an instead of delete trigger for the restricted view.
     */
    sql = format($sql$
        create trigger %1$sUpdate_tg
            instead of update
            on %1$s_rv
            for each row
        execute function %1$sUpdate();
    $sql$, targetTable);
    execute sql;
end; $$;
--//