mikras_utils 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/mkacl +11 -5
- data/lib/mikras_utils/mkacl/generators/id_functions.rb +92 -103
- data/lib/mikras_utils/mkacl/generators/role_functions.rb +37 -40
- data/lib/mikras_utils/mkacl.rb +1 -0
- data/lib/mikras_utils/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf10f02dfcf24bfe2f518edfda534abbb78047b6876610f6a09adfac5217f73c
|
4
|
+
data.tar.gz: 7801d2245d2cd810b4defcf94b2bace9f8a6587e3fbebccc49240a689b47763e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 565ee40963dc77d9759bd78a55dacddab5296f23c022a07ccce42fd8c890db65eddcb6ce015c909943a34483009233f53d2c75dd0fe05faa1ccca2c1831b5375
|
7
|
+
data.tar.gz: 8d8afe7d2bb415edd5c3a275bca441a0d4bf73299b97e5f2d8578c9efa0b37b62840679ba73eeb60b525dbf0254e1a8a3a893ba11c4ba0add75f89706219bf50
|
data/exe/mkacl
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
SPEC = %(
|
4
|
-
@ Make RLS policies
|
4
|
+
@ Make RLS functions and policies
|
5
5
|
|
6
6
|
-- [DATABASE [USERNAME]] SPEC-FILE
|
7
7
|
|
8
8
|
Read SPEC and generate following functions, policies, and triggers
|
9
9
|
|
10
|
-
|
10
|
+
FUNCTIONS
|
11
|
+
todo
|
12
|
+
|
13
|
+
TRIGGERS
|
14
|
+
todo
|
15
|
+
|
16
|
+
SPEC FILE
|
11
17
|
The format is a YAML file where each root key represents a table. A table
|
12
18
|
consists of insert, select, update, and delete actions that are arrays of
|
13
19
|
ACL entries. An ACL entry list of associated roles and possibly a check SQL
|
@@ -25,9 +31,9 @@ SPEC = %(
|
|
25
31
|
The following roles are recognized: RLA, LA, TA, KON, AKK, CLA, CTA, ELA,
|
26
32
|
and ETA
|
27
33
|
|
28
|
-
Sagsys also defines the ADM role that is excluded because it is
|
29
|
-
|
30
|
-
Mikras
|
34
|
+
Sagsys also defines the ADM role that is excluded because it is a RBAC role
|
35
|
+
in Mikras, and PUP and NON roles that are excluded because they are not
|
36
|
+
implemented in Mikras
|
31
37
|
|
32
38
|
OPTIONS
|
33
39
|
-i,interactive
|
@@ -17,6 +17,42 @@ module MkAcl
|
|
17
17
|
@chains = {}
|
18
18
|
end
|
19
19
|
|
20
|
+
# Generate a set of per-table functions that returns the associated
|
21
|
+
# case_id/event_id/vist_id for the given record. The Functions are
|
22
|
+
# generated for each table in the spec file (todo read from meta)
|
23
|
+
#
|
24
|
+
# app_portal functions:
|
25
|
+
#
|
26
|
+
# case_id_of_RECORD(object_id integer)
|
27
|
+
# event_id_of_RECORD(object_id integer)
|
28
|
+
# visit_id_of_RECORD(object_id integer)
|
29
|
+
#
|
30
|
+
# 'RECORD' is substituted with the record name of a table. Record names
|
31
|
+
# are the singular name of a table. TODO: Make it plural again
|
32
|
+
#
|
33
|
+
# acl_portal functions with a record argument (used in triggers):
|
34
|
+
#
|
35
|
+
# case_id_of_RECORD(r record)
|
36
|
+
# event_id_of_RECORD(r record)
|
37
|
+
# visit_id_of_RECORD(r record)
|
38
|
+
#
|
39
|
+
# Returns the case, event, or visit id associated with the given record.
|
40
|
+
# The record should be an ACL table record type. These functions are used
|
41
|
+
# in before insert triggers because it takes a record (eg. NEW) instead
|
42
|
+
# of an ID - this saves a lookup
|
43
|
+
#
|
44
|
+
# acl_portal general id-of functions:
|
45
|
+
#
|
46
|
+
# case_id_of(table varchar, id integer)
|
47
|
+
# event_id_of(table varchar, id integer)
|
48
|
+
# visit_id_of(table varchar, id integer)
|
49
|
+
#
|
50
|
+
# These methods are practically as efficient as using the more
|
51
|
+
# specialized versions above
|
52
|
+
#
|
53
|
+
# Returns null if not found. Note that the record has to exists because
|
54
|
+
# we access it to read the foreign keys
|
55
|
+
#
|
20
56
|
# TODO: ACL checks so that a user can't get IDs of other users' records
|
21
57
|
def generate
|
22
58
|
# Find chains
|
@@ -44,46 +80,24 @@ module MkAcl
|
|
44
80
|
# # SQL sequence of spec file tables
|
45
81
|
# def table_seq() @table_seq ||= spec.tables.map(&:uid).join(', ') end
|
46
82
|
|
47
|
-
# Generate
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# The geneated functions are
|
52
|
-
#
|
53
|
-
# case_id_of_RECORD(id integer)
|
54
|
-
# event_id_of_RECORD(id integer)
|
55
|
-
# domain_id_of_RECORD(id integer)
|
56
|
-
#
|
57
|
-
# and the record variants
|
58
|
-
#
|
59
|
-
# case_id_of_RECORD(r record)
|
60
|
-
# event_id_of_RECORD(r record)
|
61
|
-
# domain_id_of_RECORD(r record)
|
62
|
-
#
|
63
|
-
# 'RECORD' is substituted with the record name of a table. Record names
|
64
|
-
# are the singular name of a table. TODO: Make it plural again
|
65
|
-
#
|
66
|
-
# The domain functions returns event_id if related to an event and
|
67
|
-
# case_id if not. It is meant to return the most specialized domin id for
|
68
|
-
# a record. TODO Is it used?
|
69
|
-
#
|
70
|
-
# Returns null if not found. Note that the record has to exists because
|
71
|
-
# we access it to read the foreign keys
|
83
|
+
# Generate
|
84
|
+
# case_id_of_RECORD(object_id integer)
|
85
|
+
# event_id_of_RECORD(object_id integer)
|
86
|
+
# visit_id_of_RECORD(object_id integer)
|
72
87
|
#
|
73
88
|
def generate_per_table_id_functions
|
74
|
-
# Generate functions by domain
|
75
|
-
for domain in DOMAINS
|
89
|
+
for domain in DOMAINS # Generate functions by domain
|
76
90
|
|
77
91
|
# Create the identity functions first. The identity functions are
|
78
92
|
# case_id_of_case() and event_id_of_event(). This makes some stuff
|
79
93
|
# easier later on
|
80
94
|
table = "#{domain}s"
|
81
95
|
id_field = "#{domain}_id"
|
82
|
-
signature = "#{
|
96
|
+
signature = "#{app_schema}.#{id_field}_of_#{domain}(_object_id integer)"
|
83
97
|
puts %(
|
84
98
|
drop function if exists #{signature} cascade;
|
85
99
|
create function #{signature} returns integer as $$
|
86
|
-
select
|
100
|
+
select _object_id;
|
87
101
|
$$ language sql
|
88
102
|
set search_path to ''; -- intentionally empty
|
89
103
|
).align
|
@@ -120,47 +134,43 @@ module MkAcl
|
|
120
134
|
end
|
121
135
|
end
|
122
136
|
|
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
|
-
|
137
|
+
# # Tables that references visits, events, and cases
|
138
|
+
# visit_tables = chains['visits'].map(&:first)
|
139
|
+
#
|
140
|
+
# # Tables that references events and cases
|
141
|
+
# event_tables = chains['events'].map(&:first) - visit_tables
|
142
|
+
#
|
143
|
+
# # Tables that references only cases
|
144
|
+
# case_tables = chains['cases'].map(&:first) - event_tables
|
145
|
+
#
|
146
|
+
# # Create domain functions
|
147
|
+
# for domain, tables in { case: case_tables, event: event_tables, visit: visit_tables }
|
148
|
+
# for table_name in tables
|
149
|
+
# record_name = Prick::Inflector.singularize(table_name)
|
150
|
+
# signature = "#{acl_schema}.domain_id_of_#{record_name}(_object_id integer)"
|
151
|
+
# domain_function = "#{acl_schema}.#{domain}_id_of_#{record_name}"
|
152
|
+
# puts %(
|
153
|
+
# drop function if exists #{signature} cascade;
|
154
|
+
# create function #{signature} returns integer as $$
|
155
|
+
# select #{domain_function}(_object_id);
|
156
|
+
# $$ language sql
|
157
|
+
# set search_path to ''; -- intentionally empty
|
158
|
+
# ).align
|
159
|
+
# puts
|
160
|
+
# end
|
161
|
+
# end
|
148
162
|
end
|
149
163
|
|
150
|
-
# Generate
|
151
|
-
#
|
164
|
+
# Generate
|
152
165
|
# case_id_of(table varchar, id integer)
|
153
166
|
# event_id_of(table varchar, id integer)
|
154
|
-
#
|
155
|
-
#
|
156
|
-
# These methods are practically as efficient as using the more
|
157
|
-
# specialized versions above
|
167
|
+
# visit_id_of(table varchar, id integer)
|
158
168
|
#
|
159
169
|
def generate_general_id_functions
|
160
170
|
for domain in DOMAINS
|
161
171
|
domain_table = Prick::Inflector.pluralize(domain)
|
162
172
|
field = "#{domain}_id"
|
163
|
-
signature = "#{acl_schema}.#{field}_of(_table varchar,
|
173
|
+
signature = "#{acl_schema}.#{field}_of(_table varchar, _object_id integer)"
|
164
174
|
|
165
175
|
puts %(
|
166
176
|
drop function if exists #{signature} cascade;
|
@@ -173,11 +183,11 @@ module MkAcl
|
|
173
183
|
for table_name in chains[domain_table].map(&:first)
|
174
184
|
record_name = Prick::Inflector.singularize(table_name)
|
175
185
|
function_name = "#{field}_of_#{record_name}"
|
176
|
-
puts "when '#{table_name}' then #{acl_schema}.#{function_name}(
|
186
|
+
puts "when '#{table_name}' then #{acl_schema}.#{function_name}(_object_id)"
|
177
187
|
end
|
178
|
-
puts "when 'cases' then
|
179
|
-
puts "when 'events' then
|
180
|
-
puts "when 'visits' then
|
188
|
+
puts "when 'cases' then _object_id"
|
189
|
+
puts "when 'events' then _object_id"
|
190
|
+
puts "when 'visits' then _object_id"
|
181
191
|
puts "else null"
|
182
192
|
}
|
183
193
|
puts "end case;"
|
@@ -185,34 +195,13 @@ module MkAcl
|
|
185
195
|
puts "$$ language sql;"
|
186
196
|
puts
|
187
197
|
end
|
188
|
-
|
189
|
-
signature = "#{acl_schema}.domain_id_of(_table varchar, _id integer)"
|
190
|
-
puts %(
|
191
|
-
-- FIXME: Ugly implementation
|
192
|
-
drop function if exists #{signature} cascade;
|
193
|
-
create function #{signature} returns integer as $$
|
194
|
-
select coalesce(
|
195
|
-
acl_portal.visit_id_of(_table, _id),
|
196
|
-
acl_portal.event_id_of(_table, _id),
|
197
|
-
acl_portal.case_id_of(_table, _id)
|
198
|
-
);
|
199
|
-
$$ language sql;
|
200
|
-
).align
|
201
|
-
puts
|
202
198
|
end
|
203
199
|
|
204
|
-
#
|
205
|
-
#
|
200
|
+
# Generate
|
206
201
|
# case_id_of(r record)
|
207
202
|
# event_id_of(r record)
|
208
203
|
# domain_id_of(r record)
|
209
204
|
#
|
210
|
-
# Returns the case, event, or visit id associated with the given record.
|
211
|
-
# The record should be an ACL table record type
|
212
|
-
#
|
213
|
-
# Very useful in before insert triggers because it takes a record (eg.
|
214
|
-
# NEW) instead of an ID
|
215
|
-
#
|
216
205
|
def generate_general_id_of_record_functions
|
217
206
|
links = (conn.structs %(
|
218
207
|
select
|
@@ -276,22 +265,22 @@ module MkAcl
|
|
276
265
|
puts
|
277
266
|
end
|
278
267
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
268
|
+
# signature = "#{acl_schema}.domain_id_of(_r record)"
|
269
|
+
# puts %(
|
270
|
+
# -- Note: Ugly implementation but maybe close to optimal
|
271
|
+
# drop function if exists #{signature} cascade;
|
272
|
+
# create function #{signature} returns integer as $$
|
273
|
+
# declare
|
274
|
+
# _object_id integer;
|
275
|
+
# begin
|
276
|
+
# select coalesce(acl_portal.visit_id_of(_r), acl_portal.event_id_of(_r), acl_portal.case_id_of(_r))
|
277
|
+
# into _object_id;
|
278
|
+
# return _object_id;
|
279
|
+
# end;
|
280
|
+
# $$ language plpgsql
|
281
|
+
# set search_path to ''; -- intentionally ''
|
282
|
+
# ).align
|
283
|
+
# puts
|
295
284
|
end
|
296
285
|
end
|
297
286
|
end
|
@@ -1,4 +1,21 @@
|
|
1
1
|
module MkAcl
|
2
|
+
# Generates the functions
|
3
|
+
#
|
4
|
+
# acl_portal.user_is_role(_user_id integer, _object_id integer, _roles text[])
|
5
|
+
# acl_portal.user_is_role(_user_id integer, _object_id integer, _roles text)
|
6
|
+
# Returns true if the user has one of the given roles on the domain
|
7
|
+
# object
|
8
|
+
#
|
9
|
+
# public.current_is_role(_object_id integer, _roles text[])
|
10
|
+
# public.current_is_role(_object_id integer, _roles text)
|
11
|
+
# Returns true if the current user has one of the given roles on the
|
12
|
+
# domain object
|
13
|
+
#
|
14
|
+
# public.current_is_ROLE(_object_id integer)
|
15
|
+
# ROLE is one of the role kinds. Returns true if the current user has the
|
16
|
+
# given role
|
17
|
+
#
|
18
|
+
|
2
19
|
class Generator
|
3
20
|
class RoleFunctions
|
4
21
|
using String::Text
|
@@ -20,54 +37,34 @@ module MkAcl
|
|
20
37
|
private
|
21
38
|
def generate_user_role_functions
|
22
39
|
# TODO: Test. Then combine with per-table methods
|
23
|
-
signature = "#{acl_schema}.user_is_role(_user_id integer,
|
40
|
+
signature = "#{acl_schema}.user_is_role(_user_id integer, _object_id integer, _roles text[])"
|
24
41
|
puts %(
|
25
42
|
-- Return true if the user possess one or more of the given roles on the
|
26
|
-
--
|
43
|
+
-- object (cases, events, visits) with the given ID
|
27
44
|
--
|
28
45
|
drop function if exists #{signature} cascade;
|
29
46
|
create function #{signature} returns boolean as $$
|
30
47
|
select exists (
|
31
48
|
select
|
32
|
-
from
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
union
|
39
|
-
|
40
|
-
select
|
41
|
-
from #{app_schema}.event_roles cr
|
42
|
-
join #{app_schema}.event_role_users cru on cru.event_role_id = cr.id
|
43
|
-
where cr.event_id = _domain_id
|
44
|
-
and cru.user_id = _user_id
|
45
|
-
and cr.kind = any(_roles)
|
46
|
-
|
47
|
-
union
|
48
|
-
|
49
|
-
select
|
50
|
-
from #{app_schema}.visit_roles cr
|
51
|
-
join #{app_schema}.visit_role_users cru on cru.visit_role_id = cr.id
|
52
|
-
where cr.visit_id = _domain_id
|
53
|
-
and cru.user_id = _user_id
|
54
|
-
and cr.kind = any(_roles)
|
55
|
-
|
56
|
-
);
|
49
|
+
from app_portal.domain_users
|
50
|
+
where domain_id = _domain_id
|
51
|
+
and user_id = _user_id
|
52
|
+
and role = any(_roles)
|
53
|
+
)
|
57
54
|
$$ language sql
|
58
55
|
security definer;
|
59
56
|
).align
|
60
57
|
puts
|
61
58
|
|
62
|
-
signature = "#{acl_schema}.user_is_role(_user_id integer,
|
59
|
+
signature = "#{acl_schema}.user_is_role(_user_id integer, _object_id integer, role text)"
|
63
60
|
puts %(
|
64
|
-
-- Return true if the user has the given role on the
|
61
|
+
-- Return true if the user has the given role on the object
|
65
62
|
-- (cases, events, visits). Note that this function overloads the multi-role
|
66
63
|
-- version
|
67
64
|
--
|
68
65
|
drop function if exists #{signature} cascade;
|
69
66
|
create function #{signature} returns boolean as $$
|
70
|
-
select #{acl_schema}.user_is_role(_user_id,
|
67
|
+
select #{acl_schema}.user_is_role(_user_id, _object_id, array[role]);
|
71
68
|
$$ language sql
|
72
69
|
security definer;
|
73
70
|
).align
|
@@ -75,13 +72,13 @@ module MkAcl
|
|
75
72
|
|
76
73
|
for domain, roles in DOMAINS.zip([CASE_ROLES, EVENT_ROLES, VISIT_ROLES])
|
77
74
|
for role in roles
|
78
|
-
signature = "#{acl_schema}.user_is_#{role.downcase}(_user_id integer,
|
75
|
+
signature = "#{acl_schema}.user_is_#{role.downcase}(_user_id integer, _object_id integer)"
|
79
76
|
puts %(
|
80
77
|
-- Return true if the user possess the '#{role}' role
|
81
78
|
--
|
82
79
|
drop function if exists #{signature} cascade;
|
83
80
|
create function #{signature} returns boolean as $$
|
84
|
-
select #{acl_schema}.user_is_role(_user_id,
|
81
|
+
select #{acl_schema}.user_is_role(_user_id, _object_id, '#{role}');
|
85
82
|
$$ language sql
|
86
83
|
security definer;
|
87
84
|
).align
|
@@ -92,28 +89,28 @@ module MkAcl
|
|
92
89
|
|
93
90
|
def generate_current_role_functions
|
94
91
|
# TODO: Test. Then combine with per-table methods
|
95
|
-
signature = "public.current_is_role(
|
92
|
+
signature = "public.current_is_role(_object_id integer, _roles text[])"
|
96
93
|
puts %(
|
97
94
|
-- Return true if the current user possess one or more of the given roles on the
|
98
|
-
--
|
95
|
+
-- object (cases, events, visits) with the given ID
|
99
96
|
--
|
100
97
|
drop function if exists #{signature} cascade;
|
101
98
|
create function #{signature} returns boolean as $$
|
102
|
-
select #{acl_schema}.user_is_role(public.current_user_id(),
|
99
|
+
select #{acl_schema}.user_is_role(public.current_user_id(), _object_id, _roles);
|
103
100
|
$$ language sql
|
104
101
|
security definer;
|
105
102
|
).align
|
106
103
|
puts
|
107
104
|
|
108
|
-
signature = "public.current_is_role(
|
105
|
+
signature = "public.current_is_role(_object_id integer, _role text)"
|
109
106
|
puts %(
|
110
|
-
-- Return true if the current user possess the given role on the
|
107
|
+
-- Return true if the current user possess the given role on the object
|
111
108
|
-- (cases, events, visits). Note that this function overloads the multi-role
|
112
109
|
-- version
|
113
110
|
--
|
114
111
|
drop function if exists #{signature} cascade;
|
115
112
|
create function #{signature} returns boolean as $$
|
116
|
-
select #{acl_schema}.user_is_role(public.current_user_id(),
|
113
|
+
select #{acl_schema}.user_is_role(public.current_user_id(), _object_id, array[_role]);
|
117
114
|
$$ language sql
|
118
115
|
security definer;
|
119
116
|
).align
|
@@ -121,13 +118,13 @@ module MkAcl
|
|
121
118
|
|
122
119
|
for domain, roles in DOMAINS.zip([CASE_ROLES, EVENT_ROLES, VISIT_ROLES])
|
123
120
|
for role in roles
|
124
|
-
signature = "public.current_is_#{role.downcase}(
|
121
|
+
signature = "public.current_is_#{role.downcase}(_object_id integer)"
|
125
122
|
puts %(
|
126
123
|
-- Return true if the current user possess the '#{role}' role
|
127
124
|
--
|
128
125
|
drop function if exists #{signature} cascade;
|
129
126
|
create function #{signature} returns boolean as $$
|
130
|
-
select #{acl_schema}.user_is_role(public.current_user_id(),
|
127
|
+
select #{acl_schema}.user_is_role(public.current_user_id(), _object_id, '#{role}');
|
131
128
|
$$ language sql
|
132
129
|
security definer;
|
133
130
|
).align
|
data/lib/mikras_utils/mkacl.rb
CHANGED
data/lib/mikras_utils/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mikras_utils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg_conn
|