mikras_utils 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +10 -0
  5. data/README.md +31 -0
  6. data/Rakefile +8 -0
  7. data/acl-build +5 -0
  8. data/build +11 -0
  9. data/exe/mikras_utils +5 -0
  10. data/exe/mkacl +59 -0
  11. data/lib/mikras_utils/mkacl/analyzer.rb +46 -0
  12. data/lib/mikras_utils/mkacl/generator.rb +55 -0
  13. data/lib/mikras_utils/mkacl/generators/acl_functions.rb +208 -0
  14. data/lib/mikras_utils/mkacl/generators/id_functions.rb +262 -0
  15. data/lib/mikras_utils/mkacl/generators/insert_triggers.rb +267 -0
  16. data/lib/mikras_utils/mkacl/generators/role_functions.rb +72 -0
  17. data/lib/mikras_utils/mkacl/generators/rules.rb +80 -0
  18. data/lib/mikras_utils/mkacl/parser.rb +73 -0
  19. data/lib/mikras_utils/mkacl/spec.rb +154 -0
  20. data/lib/mikras_utils/mkacl.rb +18 -0
  21. data/lib/mikras_utils/version.rb +5 -0
  22. data/lib/mikras_utils.rb +6 -0
  23. data/sig/mikras_utils.rbs +4 -0
  24. data/tests/acl.fox +312 -0
  25. data/tests/acl.spec +135 -0
  26. data/tests/acl.sql +132 -0
  27. data/tests/acl_portal-functions.sql +94 -0
  28. data/tests/acl_portal-tables.sql +23 -0
  29. data/tests/acl_portal-views.sql +73 -0
  30. data/tests/agg.sql +25 -0
  31. data/tests/app.sql +48 -0
  32. data/tests/app_portal-tables.sql +138 -0
  33. data/tests/app_portal-triggers.sql +23 -0
  34. data/tests/app_portal-views.sql +203 -0
  35. data/tests/auth.sql +12 -0
  36. data/tests/auth.users.sql +5 -0
  37. data/tests/build +7 -0
  38. data/tests/final-functions.sql +28 -0
  39. data/tests/fox.sql +158 -0
  40. data/tests/initial-functions.sql +6 -0
  41. data/tests/meta.sql +197 -0
  42. data/tests/reflections.yml +5 -0
  43. data/tests/schemas.sql +25 -0
  44. data/tests/setup.sql +8 -0
  45. data/tests/sys_portal.sql +172 -0
  46. metadata +145 -0
@@ -0,0 +1,203 @@
1
+ \set ON_ERROR_STOP on
2
+
3
+ set search_path to app_portal;
4
+
5
+ -- Union of case_roles and event_roles
6
+ drop view if exists acl_portal.domain_roles cascade;
7
+ create view acl_portal.domain_roles as
8
+ select
9
+ id,
10
+ case_id as "domain_id",
11
+ role
12
+ from
13
+ app_portal.case_roles
14
+
15
+ union all
16
+
17
+ select
18
+ id,
19
+ event_id as "domain_id",
20
+ role
21
+ from
22
+ app_portal.event_roles
23
+ ;
24
+
25
+ -- Union of case_users and event_users
26
+ drop view if exists acl_portal.domain_users cascade;
27
+ create view acl_portal.domain_users as
28
+ select
29
+ case_role_id as "domain_role_id",
30
+ user_id
31
+ from
32
+ app_portal.case_users
33
+
34
+ union all
35
+
36
+ select
37
+ event_role_id as "domain_role_id",
38
+ user_id
39
+ from
40
+ app_portal.event_users
41
+ ;
42
+
43
+ -- Combines cases, case_roles, and case_users and also computes the virtual CLA
44
+ -- and CTA roles from the event ELA and ETA roles and assigns the CLA role to
45
+ -- RES users. Only ACL roles are included
46
+ drop view if exists case_role_users cascade;
47
+ create view case_role_users as
48
+ with
49
+ -- Base roles
50
+ roles as (
51
+ select
52
+ cr.case_id,
53
+ cu.user_id,
54
+ cr.id as "role_id",
55
+ cr.role
56
+ from app_portal.role_kinds rk
57
+ join app_portal.case_roles cr on cr.role = rk.kind
58
+ join app_portal.case_users cu on cu.case_role_id = cr.id
59
+ where rk.acl
60
+ ),
61
+ -- RES user is also CLA
62
+ res_roles as (
63
+ select
64
+ r.case_id,
65
+ r.user_id,
66
+ cr.id as "role_id",
67
+ cr.role
68
+ from roles r
69
+ join case_roles cr on cr.case_id = r.case_id and cr.role = 'CLA'
70
+ where r.role = 'RES'
71
+ ),
72
+ -- Event roles ELA and ETA are translated to CLA and CTA case roles
73
+ eta_ela_roles as (
74
+ select
75
+ e.case_id,
76
+ eu.user_id,
77
+ cr.id,
78
+ cr.role
79
+ from
80
+ events e
81
+ join event_roles er on er.event_id = e.id
82
+ join event_users eu on eu.event_role_id = er.id
83
+ join case_roles cr on
84
+ cr.case_id = e.case_id
85
+ and (
86
+ (cr.role = 'CTA' and er.role = 'ETA')
87
+ or (cr.role = 'CLA' and er.role = 'ELA')
88
+ )
89
+ where not e.closed
90
+ )
91
+ select * from roles
92
+ union
93
+ select * from res_roles
94
+ union
95
+ select * from eta_ela_roles
96
+ order by case_id, user_id -- FIXME: Yt
97
+ ;
98
+
99
+ -- Combines case_role_users with event_roles and event_users. It is the set of
100
+ -- roles (case or event) that is associated with an event. Users that are no
101
+ -- longer associated with the case are excluded
102
+ drop view if exists event_role_users cascade;
103
+ create view event_role_users as
104
+ -- Roles inherited from case
105
+ select
106
+ e.id as "event_id",
107
+ cru.user_id,
108
+ cru.role_id,
109
+ cru.role
110
+ from
111
+ events e
112
+ join case_role_users cru on cru.case_id = e.case_id
113
+ where
114
+ not e.closed
115
+
116
+ union all
117
+
118
+ -- Roles from event users. KON and AKK are duplicates
119
+ select
120
+ e.id as "event_id",
121
+ eu.user_id,
122
+ er.id as "role_id",
123
+ er.role
124
+ from
125
+ events e
126
+ join event_roles er on er.event_id = e.id
127
+ join event_users eu on eu.event_role_id = er.id
128
+ where
129
+ not e.closed
130
+
131
+ order by event_id -- FIXME Yt
132
+ ;
133
+
134
+ drop view if exists domain_role_users cascade;
135
+ create view domain_role_users as
136
+ select
137
+ case_id as "domain_id",
138
+ user_id,
139
+ role_id,
140
+ role
141
+ from
142
+ case_role_users
143
+
144
+ union all
145
+
146
+ select
147
+ event_id as "domain_id",
148
+ user_id,
149
+ role_id,
150
+ role
151
+ from
152
+ event_role_users
153
+ ;
154
+
155
+ drop view if exists agg.domain_role_users cascade;
156
+ create view agg.domain_role_users as
157
+ select
158
+ domain_id,
159
+ user_id,
160
+ array_agg(role_id) as "role_ids",
161
+ array_agg(role) as "roles"
162
+ from
163
+ app_portal.domain_role_users
164
+ group by
165
+ domain_id,
166
+ user_id
167
+ ;
168
+
169
+ drop view if exists name.domain_role_users cascade;
170
+ create view name.domain_role_users as
171
+ select
172
+ dru.domain_id,
173
+ dru.user_id,
174
+ dru.role_id,
175
+ coalesce(c.ident, e.label) as "domain",
176
+ u.rolename as "username",
177
+ dru.role
178
+ from
179
+ domain_role_users dru
180
+ join auth.roles u on u.id = dru.user_id
181
+ left join cases c on c.id = dru.domain_id
182
+ left join events e on e.id = dru.domain_id
183
+ ;
184
+
185
+ drop view if exists agg_name.domain_role_users cascade;
186
+ create view agg_name.domain_role_users as
187
+ select
188
+ domain_id,
189
+ user_id,
190
+ domain,
191
+ username,
192
+ array_agg(role_id) as "role_ids",
193
+ array_agg(role) as "roles"
194
+ from
195
+ name.domain_role_users
196
+ group by
197
+ domain_id,
198
+ user_id,
199
+ domain,
200
+ username
201
+ order by domain_id, username
202
+ ;
203
+
data/tests/auth.sql ADDED
@@ -0,0 +1,12 @@
1
+ \set ON_ERROR_STOP on
2
+
3
+ create table auth.roles (
4
+ id integer generated by default as identity primary key,
5
+ rolename varchar not null
6
+ );
7
+
8
+ create table auth.users (
9
+ id integer generated by default as identity primary key,
10
+ rolename varchar not null
11
+ );
12
+
@@ -0,0 +1,5 @@
1
+ -- FIXME
2
+ /*
3
+ delete from auth.users;
4
+ insert into auth.users select * from auth.roles;
5
+ */
data/tests/build ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/bash
2
+
3
+ . bash.include
4
+
5
+ psql -f app.sql
6
+ fox --delete=touched -r reflections.yml clr acl.fox | psql clr
7
+ psql -f fox.sql
@@ -0,0 +1,28 @@
1
+
2
+ -- Returns an array of role IDs for the current user (from both auth and
3
+ -- app_portal). This array is matched against the relevant ACL field in the RLS
4
+ -- policies so performance is important (TODO)
5
+ drop function if exists public.current_role_ids() cascade;
6
+ create function public.current_role_ids() returns integer[] as $$
7
+ select acl_portal.select_user_acl(public.current_user_id());
8
+ $$ language sql
9
+ stable
10
+ leakproof
11
+ security definer
12
+ ;
13
+
14
+ -- Returns an array of app_portal role names (kind) for the current user in the
15
+ -- given domain
16
+ drop function if exists public.current_domain_roles(integer) cascade;
17
+ create function public.current_domain_roles(_domain_id integer) returns text[] as $$
18
+ select coalesce(array_agg(dr.role), array[]::text[])
19
+ from acl_portal.domain_users du
20
+ join acl_portal.domain_roles dr on dr.id = du.domain_role_id
21
+ where du.user_id = public.current_user_id()
22
+ and dr.domain_id = _domain_id;
23
+ $$ language sql
24
+ stable
25
+ leakproof
26
+ security definer
27
+ ;
28
+
data/tests/fox.sql ADDED
@@ -0,0 +1,158 @@
1
+ \set ON_ERROR_STOP on
2
+
3
+ /*
4
+ delete from acl_portal.attach_acls;
5
+
6
+ insert into acl_portal.attach_acls (parent_table, parent_id, child_table, child_field, acls) values
7
+ ('cases', 1, 'events', 'case_id', array[1]), -- case-hdj can create events on case 1
8
+ ('events', 3, 'visits', 'event_id', array[16]), -- event-hdj can create visits on event 3 of case 1
9
+ ('visits', 1, 'noncompliances', 'visit_id', array[16, 17]),
10
+ ('visits', 2, 'noncompliances', 'visit_id', array[16, 2]),
11
+ ('events', 3, 'bookings', 'event_id', array[16])
12
+ ;
13
+
14
+ insert into acl_portal.attach_acls (parent_table, parent_id, child_table, child_field, acls)
15
+ select
16
+ 'case_roles' as "parent_table",
17
+ id as "parent_id",
18
+ 'case_users' as "child_table",
19
+ 'case_role_id' as "child_id",
20
+ array[2] -- hdj
21
+ from
22
+ app_portal.case_roles
23
+ where case_id = 1
24
+ and role = 'CLA'
25
+ ;
26
+ */
27
+
28
+ delete from auth.users;
29
+ insert into auth.users select * from auth.roles;
30
+
31
+ select acl_portal.update_acls();
32
+ select acl_portal.update_user_acls();
33
+
34
+ /*
35
+ drop schema if exists acl cascade;
36
+ create schema acl;
37
+ set search_path to acl;
38
+
39
+ create view tables as
40
+ select distinct
41
+ schema_name,
42
+ table_name
43
+ from
44
+ meta.columns
45
+ where
46
+ column_name like 'acl\_%'
47
+ ;
48
+
49
+ \echo ACL_TABLES
50
+ select * from tables where schema_name = 'app_portal';
51
+
52
+ -- Subset of meta.columns where both referencing and referenced tables are ACL tables
53
+ drop view if exists links cascade;
54
+ create view links as
55
+ select c.*
56
+ from tables tf
57
+ join meta.links c
58
+ on c.schema_name = tf.schema_name
59
+ and c.table_name = tf.table_name
60
+ join tables tt
61
+ on tt.schema_name = c.ref_schema_name
62
+ and tt.table_name = c.ref_table_name
63
+ ;
64
+
65
+ \echo ACL_LINKS
66
+ select * from links where schema_name = 'app_portal';
67
+
68
+ -- Subset of meta.chains where first and last table are ACL tables
69
+ drop view if exists chains cascade;
70
+ create view chains as
71
+ select mc.*
72
+ from meta.chains mc
73
+ join tables src on
74
+ mc.src_schema_name = src.schema_name
75
+ and mc.src_table_name = src.table_name
76
+ join tables dst on
77
+ mc.dst_schema_name = dst.schema_name
78
+ and mc.dst_table_name = dst.table_name
79
+ ;
80
+
81
+ \echo ACL_CHAINS
82
+ --select * from chains where src_schema_name = 'app_portal';
83
+
84
+ drop view if exists closures cascade;
85
+ create view closures as
86
+ select
87
+ at.schema_name,
88
+ at.table_name,
89
+ coalesce(
90
+ array_agg(ac.src_schema_name || '.' || ac.src_table_name) filter (where ac.src_schema_name is not null),
91
+ array[]::varchar[]
92
+ ) as closure_uids
93
+ from
94
+ tables at
95
+ left join chains ac
96
+ on ac.dst_schema_name = at.schema_name
97
+ and ac.dst_table_name = at.table_name
98
+ group by
99
+ at.schema_name,
100
+ at.table_name
101
+ ;
102
+
103
+ \echo ACL_CLOSURES
104
+ --select * from closures;
105
+
106
+ drop view if exists paths cascade;
107
+ create view paths as
108
+ with
109
+ recursive search_path as (
110
+ with edges as (
111
+ select distinct
112
+ schema_name || '.' || table_name as "from_table",
113
+ ref_schema_name || '.' || ref_table_name as "to_table",
114
+ column_name as "from_column",
115
+ ref_column_name as "to_column"
116
+ from
117
+ acl.links
118
+ )
119
+
120
+ -- Anchor member: start from the initial node
121
+ select
122
+ from_table as "start_table",
123
+ from_table,
124
+ to_table,
125
+ array[from_table] as path,
126
+ array[array[from_table || '.' || from_column, to_table || '.' || to_column]] as link,
127
+ false as cycle
128
+ from edges
129
+
130
+ union all
131
+
132
+ -- Recursive member: find the next links
133
+ select
134
+ sp.start_table,
135
+ e.from_table,
136
+ e.to_table,
137
+ sp.path || e.from_table as "path",
138
+ sp.link || array[array[e.from_table || '.' || e.from_column, e.to_table || '.' || e.to_column]] as "link",
139
+ e.from_table = any(sp.path) as "cycle"
140
+ from search_path sp
141
+ join edges e on e.from_table = sp.to_table
142
+ where not sp.cycle
143
+ )
144
+ select
145
+ start_table,
146
+ to_table as "stop_table",
147
+ path || to_table as "path",
148
+ link as "links"
149
+ from search_path
150
+ order by
151
+ start_table,
152
+ "stop_table"
153
+ ;
154
+
155
+ \echo ACL_PATHS
156
+ --select * from paths;
157
+
158
+ */
@@ -0,0 +1,6 @@
1
+
2
+ drop function if exists public.current_user_id() cascade;
3
+ create function public.current_user_id() returns integer as $$
4
+ select 2
5
+ $$ language sql;
6
+
data/tests/meta.sql ADDED
@@ -0,0 +1,197 @@
1
+
2
+ \set ON_ERROR_STOP on
3
+
4
+ drop schema if exists meta cascade;
5
+ create schema meta;
6
+ set search_path to meta;
7
+
8
+ create view schemas as
9
+ select
10
+ nspname as "schema_name"
11
+ from
12
+ pg_namespace
13
+ where nspname <> 'information_schema'
14
+ and nspname not like 'pg\_%'
15
+ and nspname not like 'fdw\_%'
16
+ and nspname not in ('pgcrypto') -- array because more libraries may come
17
+ ;
18
+
19
+ create view tables as
20
+ select
21
+ relnamespace::regnamespace::varchar as "schema_name",
22
+ relname as "table_name"
23
+ from
24
+ pg_class
25
+ where
26
+ relkind = 'r'
27
+ and relnamespace::regnamespace::varchar <> 'information_schema'
28
+ and relnamespace::regnamespace::varchar not like 'pg\_%'
29
+ -- FIXME relation "fdw_webtoolpublished.publisheddata" does not exist
30
+ and relnamespace::regnamespace::varchar not like 'fdw\_%'
31
+ ;
32
+
33
+ \echo META_TABLES
34
+ select *
35
+ from tables
36
+ where
37
+ schema_name = 'app_portal'
38
+ ;
39
+
40
+ create view columns as
41
+ with
42
+ referencing_fields as (
43
+ select
44
+ tf.relnamespace::regnamespace::varchar as "schema_name",
45
+ tf.relname as "table_name", -- avoids qualified table name
46
+ af.attname as "column_name",
47
+ tt.relnamespace::regnamespace::varchar as "ref_schema_name",
48
+ tt.relname as "ref_table_name", -- avoids qualified table name
49
+ at.attname as "ref_column_name"
50
+ from
51
+ pg_constraint as c
52
+ join pg_attribute as af on af.attnum = any(c.conkey) and af.attrelid = c.conrelid
53
+ join pg_attribute as at on at.attnum = any(c.confkey) and at.attrelid = c.confrelid
54
+ join pg_class tf on tf.oid = c.conrelid
55
+ join pg_class tt on tt.oid = c.confrelid
56
+ where
57
+ c.contype = 'f'
58
+ order by
59
+ "schema_name",
60
+ "table_name",
61
+ "column_name"
62
+ )
63
+ select
64
+ t.schema_name,
65
+ t.table_name,
66
+ af.attname as "column_name",
67
+ at.ref_schema_name,
68
+ at.ref_table_name,
69
+ at.ref_column_name,
70
+ af.attnum as "ordinal"
71
+ from
72
+ tables t
73
+ join pg_attribute af
74
+ on af.attrelid = (schema_name || '.' || table_name)::regclass
75
+ and af.attnum > 0
76
+ left join referencing_fields at
77
+ on at.schema_name = t.schema_name
78
+ and at.table_name = t.table_name
79
+ and at.column_name = af.attname
80
+ order by
81
+ "schema_name",
82
+ "table_name",
83
+ "ordinal"
84
+ ;
85
+
86
+ \echo META_COLUMNS
87
+ /*
88
+ select
89
+ schema_name, table_name, column_name,
90
+ ref_schema_name, ref_table_name, ref_column_name
91
+ from columns
92
+ where
93
+ schema_name = 'app_portal'
94
+ ;
95
+ */
96
+
97
+ create view links as
98
+ select *
99
+ from columns
100
+ where ref_table_name is not null
101
+ ;
102
+
103
+ \echo META_LINKS
104
+ --select * from links;
105
+
106
+ create view chains as
107
+ with
108
+ recursive search_path as (
109
+ with
110
+ edges as (
111
+ select distinct
112
+ schema_name || '.' || table_name as "from_table",
113
+ ref_schema_name || '.' || ref_table_name as "to_table",
114
+ column_name as "from_column",
115
+ ref_column_name as "to_column"
116
+ from
117
+ links
118
+ )
119
+
120
+ -- Anchor member: src from the initial node
121
+ select
122
+ from_table as "src_table",
123
+ from_table,
124
+ to_table,
125
+ array[from_table] as path,
126
+ array[array[from_table || '.' || from_column, to_table || '.' || to_column]] as link,
127
+ false as cycle
128
+ from edges
129
+
130
+ union all
131
+
132
+ -- Recursive member: find the next links
133
+ select
134
+ sp.src_table,
135
+ e.from_table,
136
+ e.to_table,
137
+ sp.path || e.from_table as "path",
138
+ sp.link || array[array[e.from_table || '.' || e.from_column, e.to_table || '.' || e.to_column]] as "link",
139
+ e.from_table = any(sp.path) as "cycle"
140
+ from search_path sp
141
+ join edges e on e.from_table = sp.to_table
142
+ where not sp.cycle
143
+ )
144
+ select
145
+ regexp_replace(src_table, '\..*', '') as src_schema_name,
146
+ regexp_replace(src_table, '^.*\.', '') as src_table_name,
147
+ regexp_replace(to_table, '\..*', '') as dst_schema_name,
148
+ regexp_replace(to_table, '^.*\.', '') as dst_table_name,
149
+ path || to_table as "path",
150
+ link as "links"
151
+ from search_path
152
+ order by
153
+ "src_schema_name",
154
+ "src_table_name",
155
+ "dst_schema_name",
156
+ "dst_table_name"
157
+ ;
158
+
159
+ \echo META_CHAINS
160
+ --select * from chains;
161
+
162
+ create view closures as
163
+ select
164
+ mt.schema_name,
165
+ mt.table_name,
166
+ coalesce(
167
+ array_agg(mc.src_schema_name || '.' || mc.src_table_name) filter (where mc.src_schema_name is not null),
168
+ array[]::varchar[]
169
+ ) as closure_uids
170
+ from
171
+ tables mt
172
+ left join chains mc
173
+ on mc.dst_schema_name = mt.schema_name
174
+ and mc.dst_table_name = mt.table_name
175
+ group by
176
+ mt.schema_name,
177
+ mt.table_name
178
+ ;
179
+
180
+ \echo META_CLOSURES
181
+ --select * from closures;
182
+
183
+ create view functions as
184
+ select
185
+ s.schema_name,
186
+ proname as "function_name",
187
+ string_to_array(pg_catalog.pg_get_function_identity_arguments(oid), ',') as arguments,
188
+ pg_catalog.format_type(prorettype, null) as return_type,
189
+ proconfig as "config"
190
+ from
191
+ meta.schemas s
192
+ join pg_proc p on p.pronamespace::regnamespace::varchar = s.schema_name
193
+ order by
194
+ s.schema_name,
195
+ "function_name"
196
+ ;
197
+
@@ -0,0 +1,5 @@
1
+ ---
2
+ - match: app_portal.event_users.case_role_id
3
+ that: event_user
4
+ - match: app_portal.event_users.user_id
5
+ that: event_role
data/tests/schemas.sql ADDED
@@ -0,0 +1,25 @@
1
+ \set ON_ERROR_STOP on
2
+
3
+ drop schema if exists app_portal cascade;
4
+ drop schema if exists acl_portal cascade;
5
+ drop schema if exists sys_portal cascade;
6
+
7
+ drop schema if exists meta cascade;
8
+ drop schema if exists acl cascade;
9
+
10
+ drop schema if exists agg cascade;
11
+ drop schema if exists name cascade;
12
+ drop schema if exists agg_name cascade;
13
+
14
+ drop schema if exists auth cascade;
15
+
16
+ create schema auth;
17
+ create schema app_portal;
18
+ create schema acl_portal;
19
+ create schema sys_portal;
20
+ create schema meta;
21
+ create schema acl;
22
+ create schema agg;
23
+ create schema name;
24
+ create schema agg_name;
25
+
data/tests/setup.sql ADDED
@@ -0,0 +1,8 @@
1
+
2
+ grant all on schema app_portal to alt;
3
+ grant all on all tables in schema app_portal to alt;
4
+ alter table cases enable row level security;
5
+ alter table events enable row level security;
6
+ alter table visits enable row level security;
7
+ alter table noncompliances enable row level security;
8
+