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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 356975b3f96e8341dba97e96ca170a0fb181c3c8654bc874300144774094daba
4
- data.tar.gz: 5cb71f7c6add1f89ca5025bcc9630a58474055516295e50d9ad6a2386cf6eb84
3
+ metadata.gz: bf10f02dfcf24bfe2f518edfda534abbb78047b6876610f6a09adfac5217f73c
4
+ data.tar.gz: 7801d2245d2cd810b4defcf94b2bace9f8a6587e3fbebccc49240a689b47763e
5
5
  SHA512:
6
- metadata.gz: 74ba9b96d512f831d0dd1a691d1c6892447ac21f29fe91a1f62a5cd87dfefda93d95229c16ddd063ff6bfe3ae1782ea44bda0189eedf7cf757ee7ab5825e9497
7
- data.tar.gz: 1d5a378eddd2900ef31cfcd908bdcdd4f780eaf89a162946bdf445429ef076a8fee43e2b5b7709f3fbc2852f166f43983a4b87064f59574674f64cded967e0de
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
- SPEC FILE
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 now a RBAC
29
- fole, and PUP and NON that are excluded because they are not implemented in
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 a set of per-table functions that returns the associated
48
- # case_id/event_id/vist_id for the given record. The Functions are
49
- # generated for each table in the spec file
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 = "#{acl_schema}.#{id_field}_of_#{domain}(_id integer)"
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 _id;
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
- # Tables that references visits, events, and cases
124
- visit_tables = chains['visits'].map(&:first)
125
-
126
- # Tables that references events and cases
127
- event_tables = chains['events'].map(&:first) - visit_tables
128
-
129
- # Tables that references only cases
130
- case_tables = chains['cases'].map(&:first) - event_tables
131
-
132
- # Create domain functions
133
- for domain, tables in { case: case_tables, event: event_tables, visit: visit_tables }
134
- for table_name in tables
135
- record_name = Prick::Inflector.singularize(table_name)
136
- signature = "#{acl_schema}.domain_id_of_#{record_name}(_id integer)"
137
- domain_function = "#{acl_schema}.#{domain}_id_of_#{record_name}"
138
- puts %(
139
- drop function if exists #{signature} cascade;
140
- create function #{signature} returns integer as $$
141
- select #{domain_function}(_id);
142
- $$ language sql
143
- set search_path to ''; -- intentionally empty
144
- ).align
145
- puts
146
- end
147
- end
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 general case/event id-of functions:
151
- #
164
+ # Generate
152
165
  # case_id_of(table varchar, id integer)
153
166
  # event_id_of(table varchar, id integer)
154
- # domain_id_of(table varchar, id integer)
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, _id integer)"
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}(_id)"
186
+ puts "when '#{table_name}' then #{acl_schema}.#{function_name}(_object_id)"
177
187
  end
178
- puts "when 'cases' then _id"
179
- puts "when 'events' then _id"
180
- puts "when 'visits' then _id"
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
- # General domain-id-of-record functions
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
- signature = "#{acl_schema}.domain_id_of(_r record)"
280
- puts %(
281
- -- Note: Ugly implementation but maybe close to optimal
282
- drop function if exists #{signature} cascade;
283
- create function #{signature} returns integer as $$
284
- declare
285
- _id integer;
286
- begin
287
- select coalesce(acl_portal.visit_id_of(_r), acl_portal.event_id_of(_r), acl_portal.case_id_of(_r))
288
- into _id;
289
- return _id;
290
- end;
291
- $$ language plpgsql
292
- set search_path to ''; -- intentionally ''
293
- ).align
294
- puts
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, _domain_id integer, _roles text[])"
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
- -- domain record (cases, events, visits) with the given ID
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 #{app_schema}.case_roles cr
33
- join #{app_schema}.case_role_users cru on cru.case_role_id = cr.id
34
- where cr.case_id = _domain_id
35
- and cru.user_id = _user_id
36
- and cr.kind = any(_roles)
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, _domain_id integer, role text)"
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 domain record
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, _domain_id, array[role]);
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, _domain_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, _domain_id, '#{role}');
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(_domain_id integer, _roles text[])"
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
- -- domain record (cases, events, visits) with the given ID
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(), _domain_id, _roles);
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(_domain_id integer, _role text)"
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 domain record
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(), _domain_id, array[_role]);
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}(_domain_id integer)"
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(), _domain_id, '#{role}');
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
@@ -12,6 +12,7 @@ module MkAcl
12
12
  DOMAINS = %w(case event visit)
13
13
  DOMAIN_TABLES = DOMAINS.map { "#{_1}s" }
14
14
 
15
+ # TODO Read from database (or maybe not)
15
16
  CASE_ROLES = %w(LA TA KON AKK RLA CLA CTA)
16
17
  EVENT_ROLES = %w(ELA ETA)
17
18
  VISIT_ROLES = %w(VLA VTA)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MikrasUtils
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
  end
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.2
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-07 00:00:00.000000000 Z
11
+ date: 2024-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg_conn