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 +4 -4
- data/exe/mkacl +2 -0
- data/lib/mikras_utils/mkacl/analyzer.rb +17 -11
- data/lib/mikras_utils/mkacl/generators/seeds.rb +19 -15
- data/lib/mikras_utils/mkacl/parser.rb +43 -61
- data/lib/mikras_utils/mkacl/spec.rb +98 -79
- 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: a7a7d9d893a73103e2159bfbc3ec2d0ff48fc644dec46bff98e35173d98bcff0
|
4
|
+
data.tar.gz: 872e5596dead57f495e4b7f903a5607063a4efd2e46b7716414fdfd0c6688e34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 198336a960602043f627077526bbdb1962ea32709aa9d82cecd6b244bdc64a59034db125f429c11fe51b19037e5d08141077f7b8d9145924169f06ebb9eb4daf
|
7
|
+
data.tar.gz: ace4fdcf35f66b9be6ee8e4ecdac0e0a70f6fdd4d9e79468bfbb39819a5a7c554f1be6ca4c73a7280139c0de1ad9e814c2eba90029d4482600c2ddf8fd8f937e
|
data/exe/mkacl
CHANGED
@@ -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.
|
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 (
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
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:
|
119
|
-
for action_name in
|
120
|
-
actions
|
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
|
-
|
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
|
156
|
-
if rules.
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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:
|
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
|
-
|
253
|
-
puts "
|
254
|
-
|
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
|
|
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.
|
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-
|
11
|
+
date: 2025-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg_conn
|