mikras_utils 0.13.0 → 0.14.0

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: '09748841bbccd2fc59e03f01fd240ac333963e7f6e8f35ed68d3d8268fdb4a97'
4
+ data.tar.gz: b058155721495e702725d5cf1a68d37139e3c8abe82089939e3c018ac5b3e7b3
5
5
  SHA512:
6
- metadata.gz: d9e3ab19f124f630d5651d608824930cbd5326bb682f659a951043c8b64fa3b1d0aedf7e255b862bf4e3ea961c66567bfad995babf90eb61f9b396e23a482340
7
- data.tar.gz: de73266c341b7d6193d056b73bc0e46a6925c69be9f06bad6fb47df09ed3055629a6ac87ab636876b8a59ea76b62133a9f87a1757203adf863288c5c4eb45bf4
6
+ metadata.gz: b25c043d64872f36c9b8d92e0f86d314164ef19695273255030d685e42497d0f7497395cf66718a41bf65e2ee6d4a4c1fad7db1b9202358ca6180e30df88eb76
7
+ data.tar.gz: b66d7b04e85ff125b6f8b5dc143bdb7893db08686c036899ca37ac0aa6aa7dd58edda99b976962266ebae4e00343fd80d1e66aa9c719846bf327dedc42c89888
@@ -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,7 +21,7 @@ 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;
@@ -68,14 +68,13 @@ module MkAcl
68
68
  puts
69
69
  }
70
70
 
71
- action.timestamps.each { |timestamp|
71
+ action.stamps.each { |stamp|
72
72
  puts %(
73
- insert into acl_portal.acl_timestamps (action_id, watch, assign, stamp)
73
+ insert into acl_portal.acl_stamps (action_id, watch, stamp)
74
74
  values (
75
75
  :action_id,
76
- #{conn.quote_value(timestamp.watch)},
77
- #{conn.quote_value(timestamp.assign)},
78
- #{conn.quote_value(timestamp.stamp)}
76
+ #{conn.quote_value(stamp.watch)},
77
+ #{conn.quote_value(stamp.stamp)}
79
78
  );
80
79
  ).align
81
80
  puts
@@ -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,38 +148,34 @@ 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}:"
167
+ def dump(header)
168
+ header = "#{header || name}:"
169
+ if rules.size == 1
170
+ puts header
171
+ indent { rules.first.dump }
172
+ elsif rules.size > 1
173
+ puts header
160
174
  indent {
161
175
  for rule in rules.sort_by(&:ordinal)
162
176
  print "- "
163
177
  indent(bol: false) { rule.dump }
164
178
  end
165
-
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
179
  }
176
180
  end
177
181
  end
@@ -180,36 +184,6 @@ module MkAcl
180
184
  def attach_rule(rule)
181
185
  @rules << rule
182
186
  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
187
  end
214
188
 
215
189
  class Rule
@@ -220,9 +194,10 @@ module MkAcl
220
194
  attr_accessor :roles # Roles that this rule applies to
221
195
  attr_accessor :filter # Goes into the postgres policy, may be nil
222
196
  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()
197
+ attr_accessor :mutation # Mutation function to use, may be nil. Default app_portal.mutate() except attach/detach
224
198
  attr_accessor :fields # Only used for insert and update, nil otherwise
225
199
  attr_accessor :tables # Only used for attach and detach, nil otherwise
200
+ attr_accessor :stamps
226
201
  attr_reader :ordinal
227
202
 
228
203
  # admin, internal, etc.
@@ -237,23 +212,63 @@ module MkAcl
237
212
  @roles = []
238
213
  @filter = nil
239
214
  @assert = nil
240
- @mutation = DEFAULT_MUTATION
215
+ @mutation = (action.name == "select" ? nil : DEFAULT_MUTATION)
241
216
  @fields = %w(insert update).include?(action.name) ? [] : nil
242
- @tables = %w(attach).include?(action.name) ? [] : nil
217
+ @tables = %w(attach detach).include?(action.name) ? [] : nil
218
+ @stamps = []
243
219
 
244
220
  action.send :attach_rule, self
245
221
  end
246
222
 
247
223
  def dump
248
- puts "roles: [#{roles.join(' ')}]"
224
+ puts "roles: #{roles.join(' ')}"
249
225
  puts "filter: #{filter}" if filter
250
226
  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?
227
+ puts "mutation: #{mutation || 'nil'}" if !mutation.nil? && mutation != DEFAULT_MUTATION
228
+ puts "fields: #{fields.join(' ')}" if fields && !fields.empty?
229
+ puts "tables: #{tables.join(' ')}" if tables && !tables.empty?
230
+
231
+ if stamps.size == 1
232
+ stamps.first.dump
233
+ elsif stamps.size > 1
234
+ puts "stamps:"
235
+ indent {
236
+ for stamp in stamps
237
+ print "- "
238
+ indent(bol: false) { stamp.dump }
239
+ end
240
+ }
241
+ end
242
+
255
243
  puts "ordinal: #{ordinal}"
256
244
  end
245
+ private
246
+ def attach_stamp(stamp)
247
+ @stamps << stamp
248
+ end
257
249
  end
250
+
251
+ class Stamp
252
+ attr_reader :rule
253
+ forward_to :rule, :table, :name
254
+
255
+ attr_accessor :watch
256
+ attr_accessor :stamp
257
+
258
+ def initialize(rule, watch = nil, stamp)
259
+ constrain rule, Rule
260
+ @rule = rule
261
+ @watch, @stamp = watch, stamp
262
+ rule.send :attach_stamp, self
263
+ end
264
+
265
+ def to_s() = watch
266
+
267
+ def dump
268
+ puts "watch: #{watch.join(" ")}" if watch
269
+ puts "stamp: #{stamp.join(" ")}" if stamp
270
+ end
271
+ end
272
+
258
273
  end
259
274
 
@@ -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.0"
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.0
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