mikras_utils 0.13.0 → 0.14.1

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: 9964faf5abafd14d5f5c1ecad85467e8d093298e94fa1a4b16ac6ea1f5239346
4
- data.tar.gz: 2e5445e590ef3c1f26766d79ed919fbb68481428724a68a0b5fa79ee11fd30c4
3
+ metadata.gz: a7a7d9d893a73103e2159bfbc3ec2d0ff48fc644dec46bff98e35173d98bcff0
4
+ data.tar.gz: 872e5596dead57f495e4b7f903a5607063a4efd2e46b7716414fdfd0c6688e34
5
5
  SHA512:
6
- metadata.gz: d9e3ab19f124f630d5651d608824930cbd5326bb682f659a951043c8b64fa3b1d0aedf7e255b862bf4e3ea961c66567bfad995babf90eb61f9b396e23a482340
7
- data.tar.gz: de73266c341b7d6193d056b73bc0e46a6925c69be9f06bad6fb47df09ed3055629a6ac87ab636876b8a59ea76b62133a9f87a1757203adf863288c5c4eb45bf4
6
+ metadata.gz: 198336a960602043f627077526bbdb1962ea32709aa9d82cecd6b244bdc64a59034db125f429c11fe51b19037e5d08141077f7b8d9145924169f06ebb9eb4daf
7
+ data.tar.gz: ace4fdcf35f66b9be6ee8e4ecdac0e0a70f6fdd4d9e79468bfbb39819a5a7c554f1be6ca4c73a7280139c0de1ad9e814c2eba90029d4482600c2ddf8fd8f937e
data/exe/mkacl CHANGED
@@ -79,10 +79,12 @@ end
79
79
 
80
80
  spec = MkAcl::Parser.parse(file)
81
81
  MkAcl::Analyzer.analyze(spec, conn, warn: !opts.no_warn?)
82
+
82
83
  if opts.dump?
83
84
  spec.dump
84
85
  exit
85
86
  end
87
+
86
88
  MkAcl::Generator.generate(spec, conn, modules, interactive: opts.interactive?)
87
89
 
88
90
 
@@ -15,6 +15,23 @@ module MkAcl
15
15
  end
16
16
 
17
17
  def analyze
18
+ # Check select-mutations are nil
19
+ spec.tables.each { |table|
20
+ table.actions.each { |name, action|
21
+ if name == "select"
22
+ action.rules.each { |rule|
23
+ rule.mutation.nil? or
24
+ raise ArgumentError, "Can't use mutations in select actions in table #{table.name}"
25
+ }
26
+ end
27
+ }
28
+ }
29
+
30
+ # Assign default detach
31
+ spec.tables.each { |table|
32
+ table.attach&.copy(:detach) if table.detach.nil?
33
+ }
34
+
18
35
  # Find child-parent relations between linked tables
19
36
  links = conn.tuples %(
20
37
  select
@@ -66,17 +83,6 @@ module MkAcl
66
83
  end
67
84
  }
68
85
 
69
- # Set mutation to nil select/attach/detach actions
70
- spec.tables.each { |table|
71
- table.actions.each { |name, action|
72
- if %w(select attach detach).include?(name)
73
- action.rules.each { |rule|
74
- rule.mutation = nil
75
- }
76
- end
77
- }
78
- }
79
-
80
86
  # Resolve domains
81
87
  spec.tables.select(&:acl).each { |t| resolve_domain(t) }
82
88
 
@@ -21,16 +21,20 @@ module MkAcl
21
21
  private
22
22
  def clean_tables
23
23
  puts %(
24
- delete from acl_portal.acl_timestamps;
24
+ delete from acl_portal.acl_stamps;
25
25
  delete from acl_portal.acl_rules;
26
26
  delete from acl_portal.acl_actions;
27
27
  delete from acl_portal.acl_tables;
28
28
  ).align
29
+ puts
29
30
  end
30
31
 
31
32
  def generate_seeds
32
33
  for table in spec.tables
33
34
  puts %(
35
+ --
36
+ -- #{table.name.gsub("_", " ").upcase}
37
+ --
34
38
  insert into acl_portal.acl_tables (
35
39
  schema_name, table_name, record_name, domain,
36
40
  parent_schema_name, parent_table_name, parent_link_field,
@@ -50,7 +54,7 @@ module MkAcl
50
54
 
51
55
  table.actions.values.each { |action|
52
56
  puts %(
53
- insert into acl_portal.acl_actions (table_id, kind)
57
+ insert into acl_portal.acl_actions (acl_table_id, kind)
54
58
  values (:table_id, '#{action.name.upcase}')
55
59
  returning id as "action_id"
56
60
  \\gset
@@ -63,22 +67,22 @@ module MkAcl
63
67
  puts %(
64
68
  insert into acl_portal.acl_rules (
65
69
  acl_action_id, roles, filter, assert, function_name, fields, tables, ordinal)
66
- values (:action_id, #{values.join(', ')});
70
+ values (:action_id, #{values.join(', ')})
71
+ returning id as "rule_id"
72
+ \\gset
67
73
  ).align
68
74
  puts
69
- }
70
75
 
71
- action.timestamps.each { |timestamp|
72
- puts %(
73
- insert into acl_portal.acl_timestamps (action_id, watch, assign, stamp)
74
- values (
75
- :action_id,
76
- #{conn.quote_value(timestamp.watch)},
77
- #{conn.quote_value(timestamp.assign)},
78
- #{conn.quote_value(timestamp.stamp)}
79
- );
80
- ).align
81
- puts
76
+ rule.stamps.each { |stamp|
77
+ puts %(
78
+ insert into acl_portal.acl_stamps (acl_rule_id, watch, stamp)
79
+ values (
80
+ :rule_id,
81
+ #{conn.quote_value(stamp.watch)},
82
+ #{conn.quote_value(stamp.stamp)}
83
+ );
84
+ ).align
85
+ }
82
86
  }
83
87
  }
84
88
  end
@@ -56,65 +56,23 @@ module MkAcl
56
56
 
57
57
  def parse_actions(table, actions)
58
58
  for action_name, entries in actions
59
- constrain?(action_name, :insert, :select, :update, :delete, :attach, :detach) or
60
- error "Illegal action '#{action_name}'"
61
-
62
- # real_action_names break :attach into both :attach and :detach
63
- for real_action_name in (action_name == :attach ? [:attach, :detach] : [action_name])
64
- constrain?(entries, String, Array, Hash) or
65
- error "Illegal value for #{action_name} entry '#{entries}'"
66
- action = Action.new(table, real_action_name)
67
-
68
- # Normalize entries
69
- case entries
70
- when Hash
71
- timestamps = entries.delete(:timestamps) and parse_timestamps(action, timestamps)
72
- entries = [entries]
73
- when String
74
- entries = [{ roles: entries }]
75
- when Array
76
- # Arrays can't specify mutations (for now)
77
- entries.reject! { |element|
78
- timestamps = element.delete(:timestamps) and parse_timestamps(action, timestamps)
79
- }
80
- end
81
- parse_rules(action, entries)
82
- end
83
- end
84
- end
85
-
86
- def parse_timestamps(table, timestamps)
87
- # Normalize timestamps
88
- timestamps = [timestamps] if timestamps.is_a?(Hash)
89
-
90
- case timestamps
91
- when String
92
- timestamps.split.each { |watch_field|
93
- case watch_field
94
- when /^(.*)(?:_by_id|_to_id)$/
95
- Timestamp.new(table, watch_field, nil, "#{$1}_at")
96
- when /^(created|updated|deleted)_at$/
97
- Timestamp.new(table, nil, "#{$1}_by_id", watch_field)
98
- else
99
- raise ArgumentError, "Illegal timestamp specifier: #{watch_field.inspect}"
100
- end
101
- }
102
-
103
- when Array
104
- for entry in timestamps
105
- timestamp = Timestamp.new(table)
106
- for key, value in entry
107
- case key
108
- when :watch; timestamp.watch = value
109
- when :assign; timestamp.assign = value
110
- when :stamp; timestamp.stamp = value
111
- else
112
- error "Illegal field '#{key}' in #{table} timestamps"
113
- end
59
+ ACTIONS.include?(action_name.to_s) or error "Illegal action '#{action_name}'"
60
+ constrain?(entries, String, Array, Hash) or
61
+ error "Illegal value for #{action_name} entry '#{entries}'"
62
+
63
+ action = Action.new(table, action_name)
64
+
65
+ # Normalize entries
66
+ entries =
67
+ case entries
68
+ when Hash
69
+ [entries]
70
+ when String
71
+ [{ roles: entries }]
72
+ when Array
73
+ entries
114
74
  end
115
- end
116
- else
117
- error "Illegal timestamp type: #{timestamps.class}"
75
+ parse_rules(action, entries)
118
76
  end
119
77
  end
120
78
 
@@ -122,8 +80,8 @@ module MkAcl
122
80
  ordinal = 0
123
81
 
124
82
  for entry in rules
125
- timestamps = entry.delete(:timestamps) and parse_timestamps(action, timestamps)
126
83
  rule = Rule.new(action, ordinal += 1)
84
+ stamps = entry.delete(:stamps) and parse_stamps(rule, stamps)
127
85
  for key, value in entry
128
86
  case key
129
87
  when :roles; rule.roles = norm_array(value)
@@ -141,11 +99,35 @@ module MkAcl
141
99
  error "At least one rule is required in #{action.table}.#{action}"
142
100
  end
143
101
  end
102
+
103
+ def parse_stamps(rule, stamps)
104
+ constrain rule, Rule
105
+ constrain stamps, String, Hash, Array
106
+ # constrain stamps, String, Hash[watch: NilClass | String | [String], stamp: String | [String]]
107
+
108
+ # Normalize stamps
109
+ stamps = Array(stamps)
110
+
111
+ stamps.map! { |stamp|
112
+ case stamp
113
+ when Hash; stamp
114
+ when String; { stamp: stamp }
115
+ else
116
+ raise ArgumentError, "Illegal element in #{rule.name} rule in table #{rule.table.name}"
117
+ end
118
+ }
119
+
120
+ # Check fields and create stamp
121
+ stamps.each { |stamp|
122
+ # Check fields
123
+ diff = stamp.keys - [:watch, :stamp]
124
+ diff.empty? or raise "Illegal field '#{diff.first}' in #{rule.name} rule in table #{rule.table.name}"
125
+ Stamp.new(rule, stamp[:watch]&.split, stamp[:stamp].split)
126
+ }
127
+ end
144
128
  end
145
129
  end
146
130
 
147
- #def declare_record(name, array_arg = nil, &record_
148
-
149
131
 
150
132
 
151
133
 
@@ -1,6 +1,7 @@
1
1
 
2
2
  module MkAcl
3
3
  DEFAULT_MUTATION = "app_portal.mutate"
4
+ ACTIONS = %w(insert select update delete attach detach)
4
5
 
5
6
  class Spec
6
7
  # Source SPEC file. Only for informational purposes
@@ -33,7 +34,6 @@ module MkAcl
33
34
  indent {
34
35
  puts "app_schema: #{app_schema}"
35
36
  puts "acl_schema: #{acl_schema}"
36
- puts
37
37
  tables.map(&:dump)
38
38
  }
39
39
  end
@@ -74,20 +74,22 @@ module MkAcl
74
74
  # True if the portal object triggers should be active on the table
75
75
  attr_accessor :triggers
76
76
 
77
- # SQL to create the ACL for a table. No ACL if false, default ACL if nil
77
+ # True if the table is under RLS control
78
78
  attr_accessor :acl
79
79
 
80
80
  # Associated record name. Used in function names and in conversions
81
81
  attr_reader :record_name
82
82
 
83
- # Action and timestamp objects
83
+ # Hash from action name to action object
84
+ attr_reader :actions
85
+
86
+ # Action objects
84
87
  def insert = @actions["insert"]
85
88
  def select = @actions["select"]
86
89
  def update = @actions["update"]
87
90
  def delete = @actions["delete"]
88
-
89
- # Hash from action name to action object
90
- attr_reader :actions
91
+ def attach = @actions["attach"]
92
+ def detach = @actions["detach"]
91
93
 
92
94
  def initialize(spec, name, domain, parent_name, triggers, acl)
93
95
  @spec = spec
@@ -102,31 +104,37 @@ module MkAcl
102
104
  @acl = acl
103
105
  @actions = {}
104
106
  @spec.send :attach_table, self
105
- for action_name in %w(insert select update delete)
106
- attach_action(Action.new(self, action_name))
107
- end
107
+ # for action_name in ACTIONS
108
+ # attach_action(Action.new(self, action_name))
109
+ # end
108
110
  end
109
111
 
110
112
  def to_s() = name
111
113
  def inspect() "<#{self.class}#name #{name.inspect}>" end
112
114
 
113
115
  def dump
116
+ puts
114
117
  puts "#{name}:"
115
118
  indent {
116
119
  puts "domain: #{domain}" if domain
117
120
  puts "parent: #{parent}" if parent
118
- puts "references: [#{references.values.map { |k,v| "#{v}->#{k.name}" }.join(' ')}]"
119
- for action_name in %w(insert select update delete)
120
- actions[action_name]&.dump
121
+ puts "references: #{references.values.map { |k,v| "#{v}->#{k.name}" }.join(' ')}"
122
+ for action_name in ACTIONS
123
+ next if !actions.key? action_name
124
+
125
+ if !(action_name == "detach" && attach.rules == detach.rules)
126
+ if action_name == "attach" && attach.rules == detach.rules
127
+ header = "attach/detach"
128
+ else
129
+ header = nil
130
+ end
131
+
132
+ action = actions[action_name]
133
+ actions[action_name]&.dump(header)
134
+ end
121
135
  end
122
-
123
136
  puts "triggers: #{triggers}"
124
- case acl
125
- when false; puts "acl: false"
126
- when true;
127
- puts "acl:"
128
- indent { puts acl }
129
- end
137
+ puts "acl: #{acl}"
130
138
  }
131
139
  end
132
140
 
@@ -140,39 +148,39 @@ module MkAcl
140
148
  attr_reader :table
141
149
  attr_reader :name
142
150
  attr_reader :rules
143
- attr_reader :timestamps
144
151
 
145
152
  def initialize(table, name)
146
153
  @table = table
147
154
  @name = name.to_s
148
155
  @rules = []
149
- @timestamps = []
150
156
  @table.send :attach_action, self
151
157
  end
152
158
 
159
+ def copy(name)
160
+ cp = Action.new(self.table, name)
161
+ cp.rules.concat self.rules
162
+ cp
163
+ end
164
+
153
165
  def to_s() name end
154
166
 
155
- def dump
156
- if rules.empty? && timestamps.empty?
157
- puts "#{name}: []"
158
- else
159
- puts "#{name}:"
160
- indent {
161
- for rule in rules.sort_by(&:ordinal)
162
- print "- "
163
- indent(bol: false) { rule.dump }
164
- end
167
+ def dump_body
168
+ if rules.size == 1
169
+ rules.first.dump
170
+ elsif rules.size > 1
171
+ for rule in rules.sort_by(&:ordinal)
172
+ print "- "
173
+ indent(bol: false) { rule.dump }
174
+ end
175
+ end
176
+ end
165
177
 
166
- if !timestamps.empty?
167
- puts "- timestamps:"
168
- indent {
169
- for timestamp in timestamps
170
- print "- "
171
- indent(bol: false) { timestamp.dump }
172
- end
173
- }
174
- end
175
- }
178
+ def dump(header = nil)
179
+ if header
180
+ header = "#{header || name}:"
181
+ indent { dump_body }
182
+ else
183
+ dump_body
176
184
  end
177
185
  end
178
186
 
@@ -180,36 +188,6 @@ module MkAcl
180
188
  def attach_rule(rule)
181
189
  @rules << rule
182
190
  end
183
-
184
- def attach_timestamp(timestamp)
185
- @timestamps << timestamp
186
- end
187
- end
188
-
189
- class Timestamp
190
- attr_reader :action
191
- forward_to :action, :table, :name
192
- attr_accessor :watch
193
- attr_accessor :assign
194
- attr_accessor :stamp
195
-
196
- # def created_at?() = watch == "insert"
197
- # def updated_at?() = watch == "update"
198
- # def deleted_at?() = watch == "delete"
199
-
200
- def initialize(action, watch = nil, assign = nil, stamp = nil)
201
- @action = action
202
- @watch, @assign, @stamp = watch, assign, stamp
203
- action.send :attach_timestamp, self
204
- end
205
-
206
- def to_s() = watch
207
-
208
- def dump
209
- puts "watch: #{watch}" if watch
210
- puts "assign: #{assign}" if assign
211
- puts "stamp: #{stamp}" if stamp
212
- end
213
191
  end
214
192
 
215
193
  class Rule
@@ -220,9 +198,10 @@ module MkAcl
220
198
  attr_accessor :roles # Roles that this rule applies to
221
199
  attr_accessor :filter # Goes into the postgres policy, may be nil
222
200
  attr_accessor :assert # Goes into the postgres trigger, may be nil
223
- attr_accessor :mutation # Mutation function to use, may be nil. Default app_portal.mutate()
201
+ attr_accessor :mutation # Mutation function to use, may be nil. Default app_portal.mutate() except attach/detach
224
202
  attr_accessor :fields # Only used for insert and update, nil otherwise
225
203
  attr_accessor :tables # Only used for attach and detach, nil otherwise
204
+ attr_accessor :stamps
226
205
  attr_reader :ordinal
227
206
 
228
207
  # admin, internal, etc.
@@ -237,23 +216,63 @@ module MkAcl
237
216
  @roles = []
238
217
  @filter = nil
239
218
  @assert = nil
240
- @mutation = DEFAULT_MUTATION
219
+ @mutation = (action.name == "select" ? nil : DEFAULT_MUTATION)
241
220
  @fields = %w(insert update).include?(action.name) ? [] : nil
242
- @tables = %w(attach).include?(action.name) ? [] : nil
221
+ @tables = %w(attach detach).include?(action.name) ? [] : nil
222
+ @stamps = []
243
223
 
244
224
  action.send :attach_rule, self
245
225
  end
246
226
 
247
227
  def dump
248
- puts "roles: [#{roles.join(' ')}]"
228
+ puts "roles: #{roles.join(' ')}"
249
229
  puts "filter: #{filter}" if filter
250
230
  puts "assert: #{assert}" if assert
251
- puts "mutation: #{mutation || 'nil'}" \
252
- if %w(insert update delete).include?(name) && mutation != DEFAULT_MUTATION
253
- puts "fields: [#{fields.join(' ')}]" if fields && !fields.empty?
254
- puts "tables: [#{tables.join(' ')}]" if tables && !tables.empty?
231
+ puts "mutation: #{mutation || 'nil'}" if !mutation.nil? && mutation != DEFAULT_MUTATION
232
+ puts "fields: #{fields.join(' ')}" if fields && !fields.empty?
233
+ puts "tables: #{tables.join(' ')}" if tables && !tables.empty?
234
+
235
+ if stamps.size == 1
236
+ stamps.first.dump
237
+ elsif stamps.size > 1
238
+ puts "stamps:"
239
+ indent {
240
+ for stamp in stamps
241
+ print "- "
242
+ indent(bol: false) { stamp.dump }
243
+ end
244
+ }
245
+ end
246
+
255
247
  puts "ordinal: #{ordinal}"
256
248
  end
249
+ private
250
+ def attach_stamp(stamp)
251
+ @stamps << stamp
252
+ end
257
253
  end
254
+
255
+ class Stamp
256
+ attr_reader :rule
257
+ forward_to :rule, :table, :name
258
+
259
+ attr_accessor :watch
260
+ attr_accessor :stamp
261
+
262
+ def initialize(rule, watch = nil, stamp)
263
+ constrain rule, Rule
264
+ @rule = rule
265
+ @watch, @stamp = watch, stamp
266
+ rule.send :attach_stamp, self
267
+ end
268
+
269
+ def to_s() = watch
270
+
271
+ def dump
272
+ puts "watch: #{watch.join(" ")}" if watch
273
+ puts "stamp: #{stamp.join(" ")}" if stamp
274
+ end
275
+ end
276
+
258
277
  end
259
278
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MikrasUtils
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.1"
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.13.0
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-20 00:00:00.000000000 Z
11
+ date: 2025-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg_conn