mikras_utils 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/mkacl +20 -9
- data/exe/rls +30 -0
- data/lib/mikras_utils/mikras.rb +24 -0
- data/lib/mikras_utils/mkacl/analyzer.rb +35 -21
- data/lib/mikras_utils/mkacl/generator.rb +4 -2
- data/lib/mikras_utils/mkacl/generators/acl_functions.rb +26 -9
- data/lib/mikras_utils/mkacl/generators/rules.rb +2 -2
- data/lib/mikras_utils/mkacl/generators/seeds.rb +80 -0
- data/lib/mikras_utils/mkacl/parser.rb +39 -15
- data/lib/mikras_utils/mkacl/simple_symtab.rb +58 -0
- data/lib/mikras_utils/mkacl/spec.rb +67 -29
- data/lib/mikras_utils/mkacl.rb +2 -1
- data/lib/mikras_utils/rls/analyzer.rb +108 -0
- data/lib/mikras_utils/rls/parser.rb +70 -0
- data/lib/mikras_utils/rls/spec.rb +86 -0
- data/lib/mikras_utils/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74256281ee10bf5186723bfa260cd62dda3057216660aa945c7b591fa1581177
|
4
|
+
data.tar.gz: 882636fab035922c146cc33c33a5af9deaf0da818bec6897e17a84ad8f1f2289
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ca4be02024c23f2109cc7f3919331c180f6403cb04906d5652446d80241e7b502ed9cfcebfcc5ddf66b098c4b6def8e3f1cd3863942f9e988cb3e5bd1abc501
|
7
|
+
data.tar.gz: 6d82b691b1b68214910dac0acc847a68b928b6e0f45c8a157e272ded5fdc84b2f3fe5e3f23ddbadbee4bd82f1621164eea1d736deb7923787fb9f999926ddf0e
|
data/exe/mkacl
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
SPEC = %(
|
4
4
|
@ Make RLS functions and policies
|
5
5
|
|
6
|
-
-- [DATABASE
|
6
|
+
-- [DATABASE] SPEC-FILE
|
7
7
|
|
8
|
-
Read SPEC and generate following functions, policies, and triggers
|
8
|
+
Read SPEC and generate following functions, policies, and triggers. The
|
9
|
+
default DATABASE and USERNAME variables are read from the environment and
|
10
|
+
then .prick.state.yml if present; otherwise the current user's database is
|
11
|
+
used
|
9
12
|
|
10
13
|
FUNCTIONS
|
11
14
|
todo
|
@@ -36,12 +39,15 @@ SPEC = %(
|
|
36
39
|
implemented in Mikras
|
37
40
|
|
38
41
|
OPTIONS
|
42
|
+
-d,dump
|
43
|
+
Dump internal format. Used for debugging
|
44
|
+
|
39
45
|
-i,interactive
|
40
46
|
Generate code for interactive use by added ON_ERROR_STOP and other variables
|
41
47
|
|
42
48
|
-g,generate=LIST
|
43
49
|
Generate only the given modules. LIST is a comma-separated list of
|
44
|
-
modules. The following modules are currently available: id_functions,
|
50
|
+
modules. The following modules are currently available: seeds, id_functions,
|
45
51
|
insert_triggers, rules, acl_functions, and role_functions
|
46
52
|
|
47
53
|
-W,no-warn
|
@@ -52,14 +58,17 @@ SPEC = %(
|
|
52
58
|
require 'yaml'
|
53
59
|
require 'shellopts'
|
54
60
|
|
61
|
+
require_relative '../lib/mikras_utils/mikras.rb'
|
55
62
|
require_relative '../lib/mikras_utils/mkacl.rb'
|
56
63
|
|
64
|
+
PRICK_STATE_FILE = "prick.state.yml"
|
65
|
+
|
57
66
|
opts, args = ShellOpts::process(SPEC, ARGV)
|
58
67
|
file = args.extract(-1)
|
59
|
-
database
|
60
|
-
|
61
|
-
|
62
|
-
conn
|
68
|
+
database = args.extract(0..1)
|
69
|
+
|
70
|
+
conn = PgConn.new *Mikras.credentials(database)
|
71
|
+
conn.schema.exist?("prick") or ShellOpts.error "Database '#{database}' is not a prick database"
|
63
72
|
|
64
73
|
if opts.generate?
|
65
74
|
modules = opts.generate.split(',').map(&:to_sym)
|
@@ -70,8 +79,10 @@ end
|
|
70
79
|
|
71
80
|
spec = MkAcl::Parser.parse(file)
|
72
81
|
MkAcl::Analyzer.analyze(spec, conn, warn: !opts.no_warn?)
|
73
|
-
|
74
|
-
|
82
|
+
if opts.dump?
|
83
|
+
spec.dump
|
84
|
+
exit
|
85
|
+
end
|
75
86
|
MkAcl::Generator.generate(spec, conn, modules, interactive: opts.interactive?)
|
76
87
|
|
77
88
|
|
data/exe/rls
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'indented_io'
|
4
|
+
require 'shellopts'
|
5
|
+
require 'forward_to'
|
6
|
+
|
7
|
+
include ForwardTo
|
8
|
+
|
9
|
+
require_relative '../lib/mikras_utils/rls/spec.rb'
|
10
|
+
require_relative '../lib/mikras_utils/rls/parser.rb'
|
11
|
+
require_relative '../lib/mikras_utils/rls/analyzer.rb'
|
12
|
+
|
13
|
+
def error(msg) = ShellOpts.error(msg)
|
14
|
+
|
15
|
+
SPEC = %(
|
16
|
+
Parse RLS spec file
|
17
|
+
|
18
|
+
-- FILE
|
19
|
+
)
|
20
|
+
|
21
|
+
opts, args = ShellOpts.process(SPEC, ARGV)
|
22
|
+
|
23
|
+
file = args.expect(1)
|
24
|
+
spec = Parser.parse(file)
|
25
|
+
Analyzer.analyze(spec)
|
26
|
+
|
27
|
+
spec.dump
|
28
|
+
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Mikras
|
3
|
+
def self.credentials(database_argument)
|
4
|
+
if database_argument
|
5
|
+
database = database_argument
|
6
|
+
username = database
|
7
|
+
else
|
8
|
+
username ||= database || ENV['PRICK_USERNAME']
|
9
|
+
database ||= ENV['PRICK_DATABASE']
|
10
|
+
|
11
|
+
if database.nil? && File.exist?(PRICK_STATE_FILE)
|
12
|
+
prick_state = YAML.load(IO.read PRICK_STATE_FILE)
|
13
|
+
database = prick_state["database"]
|
14
|
+
username = prick_state["username"]
|
15
|
+
else
|
16
|
+
database ||= ENV["USER"]
|
17
|
+
username ||= database
|
18
|
+
end
|
19
|
+
end
|
20
|
+
[database, username]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -15,19 +15,21 @@ module MkAcl
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def analyze
|
18
|
+
# Find child-parent relations between linked tables
|
18
19
|
links = conn.tuples %(
|
19
20
|
select
|
20
|
-
table_name,
|
21
|
-
|
22
|
-
|
23
|
-
ref_schema_name || '.' || ref_table_name
|
21
|
+
table_name as "child_table_name",
|
22
|
+
schema_name || '.' || table_name as "child_table_uid",
|
23
|
+
ref_table_name as "parent_table_name",
|
24
|
+
ref_schema_name || '.' || ref_table_name as "parent_table_uid",
|
25
|
+
column_name as "parent_link_field"
|
24
26
|
from meta.links
|
25
|
-
where schema_name = '#{spec.app_schema}'
|
27
|
+
where schema_name = '#{spec.app_schema}'
|
26
28
|
and ref_schema_name = '#{spec.app_schema}'
|
27
29
|
)
|
28
30
|
|
29
|
-
#
|
30
|
-
for child_table_name,
|
31
|
+
# Assign table references
|
32
|
+
for child_table_name, child_table_uid, parent_table_name, parent_table_uid, parent_link_field in links
|
31
33
|
if !spec.key?(child_table_name)
|
32
34
|
@uncovered_tables << child_table_name
|
33
35
|
next
|
@@ -35,8 +37,11 @@ module MkAcl
|
|
35
37
|
@uncovered_tables << parent_table_name
|
36
38
|
next
|
37
39
|
end
|
38
|
-
|
39
|
-
spec[child_table_name]
|
40
|
+
|
41
|
+
child_table = spec[child_table_name] or raise "Can't find table #{parent_table_name.inspect}"
|
42
|
+
parent_table = spec[parent_table_name] or raise "Can't find referenced table #{parent_table_name.inspect}"
|
43
|
+
|
44
|
+
child_table.references[parent_table.name] = [parent_table, parent_link_field]
|
40
45
|
end
|
41
46
|
|
42
47
|
# if warn && !@uncovered_tables.empty?
|
@@ -44,29 +49,38 @@ module MkAcl
|
|
44
49
|
# indent { puts uncovered_tables }
|
45
50
|
# end
|
46
51
|
|
47
|
-
# Assign
|
48
|
-
spec.tables.each { |table|
|
49
|
-
resolve_domain(table)
|
50
|
-
}
|
51
|
-
|
52
|
-
# Check that no table has more than one parent. FIXME
|
52
|
+
# Assign parents
|
53
53
|
spec.tables.each { |table|
|
54
|
-
|
54
|
+
if table.parent_name
|
55
|
+
table.parent = spec[table.parent_name] or raise ArgumentError, "Can't find '#{table.parent_name}'"
|
56
|
+
table.parent_link_field = table.references[table.parent.name].last
|
57
|
+
else
|
58
|
+
table.references.size <= 1 or raise ArgumentError, "Table '#{table.name}' has multiple references"
|
59
|
+
parent, link_field = table.references.values.first
|
60
|
+
if parent&.acl
|
61
|
+
table.parent = parent
|
62
|
+
table.parent_link_field = link_field
|
63
|
+
end
|
64
|
+
end
|
55
65
|
}
|
56
66
|
|
67
|
+
# Resolve domains
|
68
|
+
spec.tables.select(&:acl).each { |t| resolve_domain(t) }
|
69
|
+
|
57
70
|
spec
|
58
71
|
end
|
59
72
|
|
60
73
|
def self.analyze(spec, conn, **opts) self.new(spec, conn, **opts).analyze end
|
61
74
|
|
62
75
|
private
|
63
|
-
def find_tables
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
76
|
def resolve_domain(table)
|
68
|
-
table.domain
|
77
|
+
if table.domain.nil?
|
78
|
+
!table.parent.nil? or raise ArgumentError, "Domain table without domain name - '#{table.name}'"
|
79
|
+
resolve_domain(table.parent)
|
80
|
+
table.domain = table.parent.domain
|
81
|
+
end
|
69
82
|
end
|
70
83
|
end
|
71
84
|
end
|
72
85
|
|
86
|
+
|
@@ -4,10 +4,11 @@ require_relative './generators/acl_functions.rb'
|
|
4
4
|
require_relative './generators/insert_triggers.rb'
|
5
5
|
require_relative './generators/role_functions.rb'
|
6
6
|
require_relative './generators/rules.rb'
|
7
|
+
require_relative './generators/seeds.rb'
|
7
8
|
|
8
9
|
module MkAcl
|
9
10
|
class Generator
|
10
|
-
MODULES = [:id_functions, :insert_triggers, :rules, :acl_functions, :role_functions]
|
11
|
+
MODULES = [:id_functions, :insert_triggers, :rules, :acl_functions, :role_functions, :seeds]
|
11
12
|
|
12
13
|
using String::Text
|
13
14
|
|
@@ -32,6 +33,7 @@ module MkAcl
|
|
32
33
|
|
33
34
|
matches = modules.map { |k| [k, true] }.to_h
|
34
35
|
|
36
|
+
Seeds.generate(self) if matches.key?(:seeds)
|
35
37
|
IdFunctions.generate(self) if matches.key?(:id_functions)
|
36
38
|
for table in spec.tables
|
37
39
|
InsertTriggers.generate(self, table) if matches.key?(:insert_triggers)
|
@@ -42,7 +44,7 @@ module MkAcl
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def self.generate(spec, conn, modules = MODULES, **opts)
|
45
|
-
self.new(spec, conn).generate(modules, **opts)
|
47
|
+
self.new(spec, conn).generate(modules, **opts)
|
46
48
|
end
|
47
49
|
|
48
50
|
def inspect() "Generator(#{@database.inspect}, #{@username.inspect}, #{@spec})" end
|
@@ -21,11 +21,28 @@ module MkAcl
|
|
21
21
|
def self.generate(generator) self.new(generator).generate end
|
22
22
|
|
23
23
|
private
|
24
|
+
|
25
|
+
# %(
|
26
|
+
# update TABLE tbl
|
27
|
+
# set read_acls = (
|
28
|
+
# select array_agg(role_id)
|
29
|
+
# from acl_portal.eff_object_roles eor
|
30
|
+
# where tbl.DOMAIN_ID = eor.object_id
|
31
|
+
# )
|
32
|
+
# )
|
33
|
+
|
34
|
+
# %(
|
35
|
+
# select
|
36
|
+
# from
|
37
|
+
# where
|
38
|
+
# domain_id_of(DOMAIN, TABLE_NAME, id)
|
39
|
+
# )
|
40
|
+
|
24
41
|
def generate_acl_update_function
|
25
42
|
signature = "#{acl_schema}.update_acls()"
|
26
43
|
|
27
|
-
stmts = spec.tables.map { |table|
|
28
|
-
"perform #{acl_schema}.#{table}_update_acls(id) from #{app_schema}.#{table};"
|
44
|
+
stmts = spec.tables.map { |table|
|
45
|
+
"perform #{acl_schema}.#{table}_update_acls(id) from #{app_schema}.#{table};"
|
29
46
|
}
|
30
47
|
|
31
48
|
puts %(
|
@@ -56,7 +73,7 @@ module MkAcl
|
|
56
73
|
-- Note: The different table-level variations of this function can be
|
57
74
|
-- collapsed into a single function but it requires dynamic execution of
|
58
75
|
-- a delete statement with array-of-array arguments :-O
|
59
|
-
--
|
76
|
+
--
|
60
77
|
-- Note: This function is auto-generated by #{$PROGRAM_NAME} using #{spec.file}
|
61
78
|
--
|
62
79
|
drop function if exists #{signature} cascade;
|
@@ -68,7 +85,7 @@ module MkAcl
|
|
68
85
|
_domain_id integer;
|
69
86
|
_acls integer[]; -- per-rule ACLs
|
70
87
|
_acl_select integer[][]; -- per-action ACLs
|
71
|
-
_acl_update integer[][]; --
|
88
|
+
_acl_update integer[][]; --
|
72
89
|
_acl_delete integer[][]; --
|
73
90
|
begin
|
74
91
|
).align
|
@@ -132,7 +149,7 @@ module MkAcl
|
|
132
149
|
end
|
133
150
|
puts %(
|
134
151
|
-- Update ACL fields
|
135
|
-
update #{table.uid}
|
152
|
+
update #{table.uid}
|
136
153
|
set acl_select = _acl_select,
|
137
154
|
acl_update = _acl_update,
|
138
155
|
acl_delete = _acl_delete
|
@@ -165,7 +182,7 @@ module MkAcl
|
|
165
182
|
|
166
183
|
-- Create attach ACLs
|
167
184
|
insert into acl_portal.attach_acls (parent_table, parent_id, child_table, child_field, acls)
|
168
|
-
with
|
185
|
+
with
|
169
186
|
case_role_acls as (
|
170
187
|
select
|
171
188
|
id
|
@@ -182,8 +199,8 @@ module MkAcl
|
|
182
199
|
where rolename = any(array[#{auth_role_list}]::text[])
|
183
200
|
),
|
184
201
|
role_acls as (
|
185
|
-
select * from case_role_acls
|
186
|
-
union
|
202
|
+
select * from case_role_acls
|
203
|
+
union
|
187
204
|
select * from auth_role_acls
|
188
205
|
)
|
189
206
|
select
|
@@ -192,7 +209,7 @@ module MkAcl
|
|
192
209
|
l.table_name as "child_table",
|
193
210
|
l.column_name as "child_field",
|
194
211
|
array_agg(ra.id) as "acls"
|
195
|
-
from
|
212
|
+
from
|
196
213
|
acl.links l,
|
197
214
|
role_acls ra
|
198
215
|
where l.table_name = '#{table}'
|
@@ -56,13 +56,13 @@ module MkAcl
|
|
56
56
|
for action in %w(select update delete).map { table.actions[_1] }
|
57
57
|
rule_expr = action.rules.map { |rule|
|
58
58
|
exprs = []
|
59
|
-
exprs << "acl_#{action.name}[#{rule.
|
59
|
+
exprs << "acl_#{action.name}[#{rule.ordinal}:#{rule.ordinal}][1:] && public.current_role_ids()" \
|
60
60
|
if !rule.roles.empty?
|
61
61
|
exprs << "(#{rule.using})" if rule.using
|
62
62
|
"(#{exprs.join(' and ')})"
|
63
63
|
}.join(" or ")
|
64
64
|
rule_expr = "false" if rule_expr.empty?
|
65
|
-
|
65
|
+
|
66
66
|
puts %(
|
67
67
|
drop policy if exists rls_#{action.name} on #{table.uid} cascade;
|
68
68
|
create policy rls_#{action.name} on #{table.uid} for #{action.name}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module MkAcl
|
3
|
+
class Generator
|
4
|
+
class Seeds
|
5
|
+
using String::Text
|
6
|
+
|
7
|
+
attr_reader :generator
|
8
|
+
forward_to :generator, :conn, :spec, :app_schema, :acl_schema
|
9
|
+
|
10
|
+
def initialize(generator)
|
11
|
+
@generator = generator
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate
|
15
|
+
clean_tables
|
16
|
+
generate_seeds
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.generate(generator) self.new(generator).generate end
|
20
|
+
|
21
|
+
private
|
22
|
+
def clean_tables
|
23
|
+
puts %(
|
24
|
+
delete from acl_portal.acl_rules;
|
25
|
+
delete from acl_portal.acl_actions;
|
26
|
+
delete from acl_portal.acl_tables;
|
27
|
+
).align
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_seeds
|
31
|
+
for table in spec.tables
|
32
|
+
puts %(
|
33
|
+
insert into acl_portal.acl_tables (
|
34
|
+
schema_name, table_name, domain,
|
35
|
+
parent_schema_name, parent_table_name, parent_link_field,
|
36
|
+
acl)
|
37
|
+
values (
|
38
|
+
'#{app_schema}', '#{table}', #{conn.quote_value(table.domain)},
|
39
|
+
'#{table.parent && table.app_schema}', '#{table.parent}', '#{table.parent_link_field}',
|
40
|
+
#{table.acl || 'false'})
|
41
|
+
returning id as "table_id"
|
42
|
+
\\gset
|
43
|
+
).align
|
44
|
+
puts
|
45
|
+
|
46
|
+
table.actions.values.each { |action|
|
47
|
+
puts %(
|
48
|
+
insert into acl_portal.acl_actions (acl_table_id, kind)
|
49
|
+
values (:table_id, '#{action.name.upcase}')
|
50
|
+
returning id as "action_id"
|
51
|
+
\\gset
|
52
|
+
).align
|
53
|
+
puts
|
54
|
+
|
55
|
+
action.rules.each { |rule|
|
56
|
+
fields = %w(roles filter assert fields tables ordinal)
|
57
|
+
values = fields.map { |field| conn.quote_value(rule.send(field.to_sym), elem_type: :text) }
|
58
|
+
puts %(
|
59
|
+
insert into acl_portal.acl_rules (acl_action_id, roles, filter, assert, fields, tables, ordinal)
|
60
|
+
values (:action_id, #{values.join(', ')});
|
61
|
+
).align
|
62
|
+
puts
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
puts %(
|
68
|
+
update acl_portal.acl_tables sub
|
69
|
+
set parent_id = (
|
70
|
+
select id
|
71
|
+
from acl_portal.acl_tables super
|
72
|
+
where super.schema_name = sub.parent_schema_name
|
73
|
+
and super.table_name = sub.parent_table_name
|
74
|
+
);
|
75
|
+
).align
|
76
|
+
puts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -2,14 +2,29 @@
|
|
2
2
|
module MkAcl
|
3
3
|
class Parser
|
4
4
|
attr_reader :file
|
5
|
+
attr_reader :symtab
|
5
6
|
|
6
|
-
def initialize(file)
|
7
|
+
def initialize(file)
|
8
|
+
@file = file
|
9
|
+
@symtab = SimpleSymtab::SimpleSymtab.new
|
10
|
+
end
|
7
11
|
def parse() parse_spec end
|
8
12
|
def self.parse(file) Parser.new(file).parse end
|
9
13
|
|
10
14
|
private
|
11
15
|
def error(*msg) raise ParseError, *msg end
|
12
|
-
|
16
|
+
|
17
|
+
def norm_value(value)
|
18
|
+
value && symtab.interpolate(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def norm_array(value)
|
22
|
+
if value.is_a?(Array)
|
23
|
+
value.map { |v| norm_value(v) }
|
24
|
+
else
|
25
|
+
norm_value(value)&.split
|
26
|
+
end
|
27
|
+
end
|
13
28
|
|
14
29
|
def parse_spec
|
15
30
|
hash = YAML.load(IO.read(file), symbolize_names: true)
|
@@ -18,21 +33,31 @@ module MkAcl
|
|
18
33
|
app_schema = schema[:app] or raise ArgumentError, "Can't find 'schema.app' attribute"
|
19
34
|
acl_schema = schema[:acl] or raise ArgumentError, "Can't find 'schema.acl' attribute"
|
20
35
|
spec = Spec.new(file, app_schema, acl_schema)
|
36
|
+
parse_variables(hash)
|
21
37
|
parse_tables(spec, hash)
|
22
38
|
spec
|
23
39
|
end
|
24
40
|
|
41
|
+
def parse_variables(hash)
|
42
|
+
hash.delete_if { |k,v| k.to_s =~ /^(\$[A-Za-z0-9_]+)$/ and symtab[k] = v }
|
43
|
+
end
|
44
|
+
|
25
45
|
def parse_tables(spec, tables)
|
26
46
|
for table_name, actions in tables
|
27
|
-
|
47
|
+
acl = actions.key?(:acl) ? actions.delete(:acl) : true
|
48
|
+
parent = actions.delete(:parent)
|
49
|
+
domain = actions.delete(:domain)
|
50
|
+
table = Table.new(spec, table_name, domain, parent, acl)
|
28
51
|
parse_actions(table, actions)
|
29
52
|
end
|
30
53
|
end
|
31
54
|
|
32
55
|
def parse_actions(table, actions)
|
33
56
|
for action_name, rules in actions
|
34
|
-
constrain?(action_name, :insert, :select, :update, :delete) or
|
35
|
-
|
57
|
+
constrain?(action_name, :insert, :select, :update, :delete, :attach) or
|
58
|
+
error "Illegal action '#{action_name}'"
|
59
|
+
constrain?(rules, String, Array, Hash) or
|
60
|
+
error "Illegal value for #{action} action '#{rules}'"
|
36
61
|
action = Action.new(table, action_name)
|
37
62
|
|
38
63
|
# Normalize rules
|
@@ -40,7 +65,7 @@ module MkAcl
|
|
40
65
|
when Hash
|
41
66
|
rules = [rules]
|
42
67
|
when String
|
43
|
-
rules = [{
|
68
|
+
rules = [{ roles: rules }]
|
44
69
|
end
|
45
70
|
|
46
71
|
parse_rules(action, rules)
|
@@ -48,23 +73,22 @@ module MkAcl
|
|
48
73
|
end
|
49
74
|
|
50
75
|
def parse_rules(action, rules)
|
51
|
-
|
76
|
+
ordinal = 0
|
52
77
|
for entry in rules
|
53
|
-
rule = Rule.new(action,
|
54
|
-
|
78
|
+
rule = Rule.new(action, ordinal += 1)
|
55
79
|
for key, value in entry
|
56
80
|
case key
|
57
|
-
when :
|
58
|
-
when :
|
59
|
-
when :
|
60
|
-
when :
|
61
|
-
when :
|
81
|
+
when :roles; rule.roles = norm_array(value)
|
82
|
+
when :filter; rule.filter = norm_value(value)
|
83
|
+
when :assert; rule.assert = norm_value(value)
|
84
|
+
when :fields; rule.fields = norm_array(value)
|
85
|
+
when :tables; rule.tables = norm_array(value)
|
62
86
|
else
|
63
87
|
raise ArgumentError, "Illegal field '#{key}' in #{action.table}.#{action}"
|
64
88
|
end
|
65
89
|
end
|
66
90
|
|
67
|
-
!action.rules.empty? or
|
91
|
+
!action.rules.empty? or
|
68
92
|
error "At least one rule is required in #{action.table}.#{action}"
|
69
93
|
end
|
70
94
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'forward_to'
|
5
|
+
|
6
|
+
include ForwardTo
|
7
|
+
|
8
|
+
module SimpleSymtab
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class SimpleSymtab
|
12
|
+
def [](key)
|
13
|
+
resolve if !resolved?
|
14
|
+
@variables[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
!@variables.key?(key) or raise Error, "Redefinition of '#{key}'"
|
19
|
+
@variables[key] = value
|
20
|
+
@unresolved << key
|
21
|
+
end
|
22
|
+
|
23
|
+
forward_to :"@variables", :key?, :size, :empty?
|
24
|
+
|
25
|
+
def initialize(hash = {})
|
26
|
+
@variables = hash.dup
|
27
|
+
@unresolved = @variables.keys
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpolate(s)
|
31
|
+
resolve if !resolved?
|
32
|
+
resolve_string(s)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def resolved?() @unresolved.empty? end
|
37
|
+
|
38
|
+
def resolve_string(val)
|
39
|
+
val.is_a?(String) or raise ArgumentError
|
40
|
+
seen = Set.new
|
41
|
+
while val =~ /(\$[A-Za-z0-9_]+\b)/
|
42
|
+
var = $1
|
43
|
+
seen.add?(var) or raise Error, "Circular definition of '#{var}'"
|
44
|
+
rep = @variables[var.to_sym] or raise Error, "Unknown variable '#{var}'"
|
45
|
+
val.gsub!(var, rep)
|
46
|
+
end
|
47
|
+
val
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolve
|
51
|
+
@unresolved.each { |var|
|
52
|
+
@variables[var] = resolve_string(@variables[var])
|
53
|
+
}
|
54
|
+
@unresolved = []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module MkAcl
|
3
3
|
class Spec
|
4
4
|
# Source SPEC file. Only for informational purposes
|
5
|
-
attr_reader :file
|
5
|
+
attr_reader :file
|
6
6
|
|
7
7
|
# Application schema that contains the ACL controlled tables
|
8
8
|
attr_reader :app_schema
|
@@ -11,7 +11,7 @@ module MkAcl
|
|
11
11
|
# (relatively) clean
|
12
12
|
attr_reader :acl_schema
|
13
13
|
|
14
|
-
# List of tables.
|
14
|
+
# List of tables. Maintained by #attach_table
|
15
15
|
attr_reader :tables
|
16
16
|
|
17
17
|
# Spec acts as a hash from table name to Table object. Initialized by
|
@@ -45,11 +45,35 @@ module MkAcl
|
|
45
45
|
|
46
46
|
class Table
|
47
47
|
attr_reader :spec
|
48
|
-
|
49
|
-
|
48
|
+
forward_to :@spec, :app_schema, :acl_schema
|
49
|
+
|
50
|
+
# Hash from referenced table name to a tuple of the table object and the
|
51
|
+
# link field. Initialized by the analyzer
|
52
|
+
attr_accessor :references
|
53
|
+
|
54
|
+
# Table name and uid
|
50
55
|
attr_reader :name
|
51
|
-
attr_reader :
|
52
|
-
|
56
|
+
attr_reader :uid # SCHEMA.TABLE name
|
57
|
+
|
58
|
+
# Parent domain table. Initialized by the analyzer
|
59
|
+
attr_accessor :parent
|
60
|
+
|
61
|
+
# Name of parent table. May be nil. Initialized by the parser
|
62
|
+
attr_accessor :parent_name
|
63
|
+
|
64
|
+
# Name of link field to parent record. Initialized by the analyzer
|
65
|
+
attr_accessor :parent_link_field
|
66
|
+
|
67
|
+
# Security domain name for this object. Domain object have themselves as
|
68
|
+
# domain, all other portal objects use the parent's domain. Initialized by
|
69
|
+
# the analyzer
|
70
|
+
attr_accessor :domain
|
71
|
+
|
72
|
+
# SQL to create the ACL for a table. No ACL if false, default ACL if nil
|
73
|
+
attr_accessor :acl
|
74
|
+
|
75
|
+
# Associated record name. Used in function names
|
76
|
+
attr_reader :record_name
|
53
77
|
|
54
78
|
# Action objects
|
55
79
|
def insert = @actions["insert"]
|
@@ -60,13 +84,15 @@ module MkAcl
|
|
60
84
|
# Hash from action name to action object
|
61
85
|
attr_reader :actions
|
62
86
|
|
63
|
-
def initialize(spec, name, domain)
|
87
|
+
def initialize(spec, name, domain, parent_name, acl)
|
64
88
|
@spec = spec
|
65
|
-
@
|
89
|
+
@references = {}
|
66
90
|
@name = name.to_s
|
67
|
-
@uid = "#{
|
91
|
+
@uid = "#{app_schema}.#{@name}"
|
68
92
|
@record_name = Prick::Inflector.singularize(@name)
|
93
|
+
@parent_name = parent_name
|
69
94
|
@domain = domain
|
95
|
+
@acl = acl
|
70
96
|
@actions = {}
|
71
97
|
@spec.send :attach_table, self
|
72
98
|
for action_name in %w(insert select update delete)
|
@@ -79,12 +105,19 @@ module MkAcl
|
|
79
105
|
|
80
106
|
def dump
|
81
107
|
puts "#{name}:"
|
82
|
-
indent {
|
108
|
+
indent {
|
83
109
|
puts "domain: #{domain}" if domain
|
84
|
-
puts "
|
110
|
+
puts "parent: #{parent}" if parent
|
111
|
+
puts "references: [#{references.values.map(&:first).map(&:name).join(' ')}]"
|
85
112
|
for action_name in %w(insert select update delete)
|
86
113
|
actions[action_name]&.dump
|
87
114
|
end
|
115
|
+
case acl
|
116
|
+
when false; puts "acl: false"
|
117
|
+
when true;
|
118
|
+
puts "acl:"
|
119
|
+
indent { puts acl }
|
120
|
+
end
|
88
121
|
}
|
89
122
|
end
|
90
123
|
|
@@ -106,8 +139,6 @@ module MkAcl
|
|
106
139
|
@table.send :attach_action, self
|
107
140
|
end
|
108
141
|
|
109
|
-
def fields() @include + @exclude.map { "-#{_1}" } end
|
110
|
-
|
111
142
|
def to_s() name end
|
112
143
|
|
113
144
|
def dump
|
@@ -116,7 +147,7 @@ module MkAcl
|
|
116
147
|
else
|
117
148
|
puts name
|
118
149
|
indent {
|
119
|
-
for rule in rules
|
150
|
+
for rule in rules.sort_by(&:ordinal)
|
120
151
|
print "- "
|
121
152
|
indent(bol: false) { rule.dump }
|
122
153
|
end
|
@@ -131,33 +162,40 @@ module MkAcl
|
|
131
162
|
end
|
132
163
|
|
133
164
|
class Rule
|
165
|
+
using String::Text
|
166
|
+
|
134
167
|
attr_reader :action
|
135
168
|
forward_to :action, :table, :name
|
136
|
-
attr_reader :index
|
137
169
|
attr_accessor :roles
|
138
|
-
attr_accessor :
|
139
|
-
attr_accessor :
|
140
|
-
attr_accessor :
|
141
|
-
attr_accessor :
|
170
|
+
attr_accessor :filter # Goes into the postgres policy
|
171
|
+
attr_accessor :assert # Goes into the postgres trigger
|
172
|
+
attr_accessor :fields # Only used for insert and update
|
173
|
+
attr_accessor :tables # Only used for attach
|
174
|
+
attr_reader :ordinal
|
142
175
|
|
176
|
+
# admin, internal, etc.
|
143
177
|
def auth_roles() @auth_roles ||= roles.select { _1 == _1.downcase } end
|
178
|
+
|
179
|
+
# KON, AKK, etc.
|
144
180
|
def case_roles() @case_roles ||= roles.select { _1 == _1.upcase } end
|
145
181
|
|
146
|
-
def initialize(action,
|
182
|
+
def initialize(action, ordinal)
|
147
183
|
@action = action
|
148
|
-
@
|
149
|
-
@roles
|
184
|
+
@ordinal = ordinal
|
185
|
+
@roles = []
|
186
|
+
@fields = []
|
187
|
+
@tables = []
|
188
|
+
|
150
189
|
action.send :attach_rule, self
|
151
190
|
end
|
152
191
|
|
153
|
-
def fields() @include + @exclude.map { "-#{_1}" } end
|
154
|
-
|
155
192
|
def dump
|
156
|
-
puts "
|
157
|
-
puts "
|
158
|
-
puts "
|
159
|
-
puts "
|
160
|
-
puts "
|
193
|
+
puts "roles: [#{roles.join(' ')}]"
|
194
|
+
puts "filter: #{filter}" if filter
|
195
|
+
puts "assert: #{assert}" if assert
|
196
|
+
puts "fields: [#{fields.join(' ')}]" if !fields.empty?
|
197
|
+
puts "tables: [#{tables.join(' ')}]" if !tables.empty?
|
198
|
+
puts "ordinal: #{ordinal}"
|
161
199
|
end
|
162
200
|
end
|
163
201
|
end
|
data/lib/mikras_utils/mkacl.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
require 'pg_conn'
|
3
3
|
require 'indented_io'
|
4
|
-
require 'string-text'
|
4
|
+
require 'string-text'; using String::Text
|
5
5
|
require 'forward_to'; include ForwardTo
|
6
6
|
require 'constrain'; include Constrain
|
7
7
|
require 'prick-inflector'
|
@@ -20,6 +20,7 @@ module MkAcl
|
|
20
20
|
ROLES = CASE_ROLES + EVENT_ROLES + VISIT_ROLES
|
21
21
|
end
|
22
22
|
|
23
|
+
require_relative 'mkacl/simple_symtab.rb'
|
23
24
|
require_relative 'mkacl/spec.rb'
|
24
25
|
require_relative 'mkacl/parser.rb'
|
25
26
|
require_relative 'mkacl/analyzer.rb'
|
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
class Analyzer
|
3
|
+
attr_reader :spec
|
4
|
+
def vars = spec.variables
|
5
|
+
|
6
|
+
def initialize(spec)
|
7
|
+
@spec = spec
|
8
|
+
end
|
9
|
+
|
10
|
+
def analyze
|
11
|
+
interpolate
|
12
|
+
expand_rules
|
13
|
+
merge_rules
|
14
|
+
spec
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.analyze(spec)
|
18
|
+
self.new(spec).analyze
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
#
|
23
|
+
# Interpolate variables
|
24
|
+
#
|
25
|
+
def interpolate
|
26
|
+
interpolate_variables
|
27
|
+
interpolate_spec
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpolate_variables
|
31
|
+
spec.variables.transform_values! { |a|
|
32
|
+
a.map { |e|
|
33
|
+
if e =~ /^\$[A-Za-z0-9_]+$/
|
34
|
+
spec.variables[e] or ShellOpts::error "Unknown variable #{e}"
|
35
|
+
else
|
36
|
+
e
|
37
|
+
end
|
38
|
+
}.flatten
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def interpolate_spec
|
43
|
+
spec.rules.each { |r| interpolate_rule(r) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def interpolate_rule(rule)
|
47
|
+
rule.tables = interpolate_array(rule.tables)
|
48
|
+
p rule.opers.map(&:class)
|
49
|
+
|
50
|
+
rule.opers.each { |op| interpolate_oper(op) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def interpolate_oper(oper)
|
54
|
+
oper.accesses.each { |a| interpolate_access(a) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def interpolate_access(access)
|
58
|
+
access.roles = interpolate_array(access.roles)
|
59
|
+
access.tables = interpolate_string(access.tables)
|
60
|
+
access.fields = interpolate_array(access.fields)
|
61
|
+
access.where = interpolate_string(access.where)
|
62
|
+
end
|
63
|
+
|
64
|
+
def interpolate_string(string)
|
65
|
+
return nil if string.nil?
|
66
|
+
for key, value in vars
|
67
|
+
next if !value.is_a?(String)
|
68
|
+
string.gsub!(/#{key}/, value)
|
69
|
+
end
|
70
|
+
string
|
71
|
+
end
|
72
|
+
|
73
|
+
def interpolate_array(array)
|
74
|
+
return nil if array.nil?
|
75
|
+
array.map { |e| vars[e] || e }.flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Expand rules
|
80
|
+
#
|
81
|
+
def expand_rules()
|
82
|
+
rules = []
|
83
|
+
spec.rules.each { |rule|
|
84
|
+
rule.tables.each { |t| r = rule.dup; r.tables = [t]; rules << r }
|
85
|
+
}
|
86
|
+
spec.rules = rules
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Merge rules
|
91
|
+
#
|
92
|
+
def merge_rules()
|
93
|
+
spec.rules = spec.rules.group_by { |rule| rule.tables.first }.map { |table, rules|
|
94
|
+
rule = Rule.new([table])
|
95
|
+
for oper in Spec::OPERS
|
96
|
+
rule[oper] = merge_opers(oper, rules.map { |r| r[oper] }.flatten)
|
97
|
+
end
|
98
|
+
rule
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def merge_opers(oper, opers)
|
103
|
+
oper = Oper.new(oper)
|
104
|
+
oper.accesses = opers.map { |op| op.accesses }.flatten
|
105
|
+
oper
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
include ForwardTo
|
6
|
+
|
7
|
+
class Parser
|
8
|
+
def initialize(file)
|
9
|
+
@file = file
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
yaml = YAML.load(IO.read(@file).sub(/^__END__$.*/m, ""), symbolize_names: true)
|
14
|
+
rules = nil
|
15
|
+
variables = {}
|
16
|
+
yaml.each { |key, value|
|
17
|
+
case key
|
18
|
+
when :rules; rules = value.map { |v| parse_rule(v) }
|
19
|
+
when /^\$[A-Z0-9_]+$/; variables[key.to_s] = parse_array(value)
|
20
|
+
else
|
21
|
+
raise "Illegal section: #{key}"
|
22
|
+
end
|
23
|
+
}
|
24
|
+
Spec.new(@file, rules, variables)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse(file) self.new(file).parse end
|
28
|
+
|
29
|
+
private
|
30
|
+
def parse_rule(hash)
|
31
|
+
tables = parse_array(hash[:tables]) or error "Can't find 'table' key"
|
32
|
+
rule = Rule.new(tables)
|
33
|
+
for oper in Spec::OPERS
|
34
|
+
value = hash[oper] or next
|
35
|
+
rule.send(:"#{oper}=", parse_oper(oper, value))
|
36
|
+
end
|
37
|
+
rule
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_oper(oper, value)
|
41
|
+
case value
|
42
|
+
when String; Oper.new(oper, [Access.new(parse_array(value), nil, nil, nil)])
|
43
|
+
when Hash; Oper.new(oper, [parse_access(value)])
|
44
|
+
when Array; Oper.new(oper, value.map { |hash| parse_access(hash) })
|
45
|
+
else
|
46
|
+
error "Illegal argument: #{value.invaluet}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_access(hash)
|
51
|
+
illegal_keys = hash.keys - Spec::FIELDS
|
52
|
+
illegal_keys.empty? or error "Illegal keys: #{illegal_keys.join(', ')}"
|
53
|
+
roles = parse_array(hash[:roles])
|
54
|
+
tables = parse_array(hash[:tables])
|
55
|
+
fields = parse_array(hash[:fields])
|
56
|
+
where = hash[:where]
|
57
|
+
Access.new(roles, tables, fields, where)
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_array(value)
|
61
|
+
case value
|
62
|
+
when String; value.split
|
63
|
+
when Array; value
|
64
|
+
when nil; nil
|
65
|
+
else
|
66
|
+
error "Illegal array value: #{value.inspect}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
class Spec
|
3
|
+
OPERS = [:select, :insert, :update, :delete, :attach]
|
4
|
+
FIELDS = [:roles, :tables, :fields, :where]
|
5
|
+
|
6
|
+
attr_reader :file
|
7
|
+
attr_accessor :rules
|
8
|
+
attr_accessor :variables
|
9
|
+
|
10
|
+
forward_to :variables, :[], :[]=
|
11
|
+
|
12
|
+
def initialize(file, rules, variables)
|
13
|
+
@file, @rules, @variables = file, rules, variables
|
14
|
+
end
|
15
|
+
|
16
|
+
def dump
|
17
|
+
puts "File: #{file}"
|
18
|
+
puts "Variables"
|
19
|
+
indent { variables.each { |k,v| puts "#{k} = #{v.inspect}" } }
|
20
|
+
puts "Rules"
|
21
|
+
indent { rules.sort_by { |rule| rule.tables.first }.each(&:dump) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Rule
|
26
|
+
attr_accessor :tables
|
27
|
+
attr_accessor *Spec::OPERS
|
28
|
+
|
29
|
+
def initialize(tables)
|
30
|
+
@tables = tables
|
31
|
+
Spec::OPERS.each { |op| self[op] = Oper.new(op) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def opers() = Spec::OPERS.map { |op| self.send(op) }.compact
|
35
|
+
def [](key) = self.send(key)
|
36
|
+
def []=(key, value) self.send(:"#{key}=", value) end
|
37
|
+
|
38
|
+
def to_s() = "Rule, tables: #{tables.join(', ')}"
|
39
|
+
|
40
|
+
def dump
|
41
|
+
puts "Rule"
|
42
|
+
indent {
|
43
|
+
puts "tables: #{tables}"
|
44
|
+
opers.each { |op| op.dump }
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Oper
|
50
|
+
attr_reader :name
|
51
|
+
attr_accessor :accesses
|
52
|
+
|
53
|
+
def self.symbol = self.to_s.sub(/Decl/, "").downcase.to_sym
|
54
|
+
|
55
|
+
def initialize(name, accesses = [])
|
56
|
+
@name = name
|
57
|
+
@accesses = accesses
|
58
|
+
end
|
59
|
+
|
60
|
+
def dump
|
61
|
+
puts "#{name}"
|
62
|
+
indent { accesses.each(&:dump) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Access
|
67
|
+
attr_accessor *Spec::FIELDS
|
68
|
+
|
69
|
+
def initialize(roles, table, fields, where)
|
70
|
+
@roles = roles
|
71
|
+
@table = table
|
72
|
+
@fields = fields
|
73
|
+
@where = where
|
74
|
+
end
|
75
|
+
|
76
|
+
def dump
|
77
|
+
puts "access:"
|
78
|
+
indent {
|
79
|
+
Spec::FIELDS.each { |f|
|
80
|
+
value = self.send(f) or next
|
81
|
+
puts "#{f}: #{value}"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
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.4.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:
|
11
|
+
date: 2025-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg_conn
|
@@ -72,6 +72,7 @@ email:
|
|
72
72
|
executables:
|
73
73
|
- mikras_utils
|
74
74
|
- mkacl
|
75
|
+
- rls
|
75
76
|
extensions: []
|
76
77
|
extra_rdoc_files: []
|
77
78
|
files:
|
@@ -84,7 +85,9 @@ files:
|
|
84
85
|
- build
|
85
86
|
- exe/mikras_utils
|
86
87
|
- exe/mkacl
|
88
|
+
- exe/rls
|
87
89
|
- lib/mikras_utils.rb
|
90
|
+
- lib/mikras_utils/mikras.rb
|
88
91
|
- lib/mikras_utils/mkacl.rb
|
89
92
|
- lib/mikras_utils/mkacl/analyzer.rb
|
90
93
|
- lib/mikras_utils/mkacl/generator.rb
|
@@ -93,8 +96,13 @@ files:
|
|
93
96
|
- lib/mikras_utils/mkacl/generators/insert_triggers.rb
|
94
97
|
- lib/mikras_utils/mkacl/generators/role_functions.rb
|
95
98
|
- lib/mikras_utils/mkacl/generators/rules.rb
|
99
|
+
- lib/mikras_utils/mkacl/generators/seeds.rb
|
96
100
|
- lib/mikras_utils/mkacl/parser.rb
|
101
|
+
- lib/mikras_utils/mkacl/simple_symtab.rb
|
97
102
|
- lib/mikras_utils/mkacl/spec.rb
|
103
|
+
- lib/mikras_utils/rls/analyzer.rb
|
104
|
+
- lib/mikras_utils/rls/parser.rb
|
105
|
+
- lib/mikras_utils/rls/spec.rb
|
98
106
|
- lib/mikras_utils/version.rb
|
99
107
|
- sig/mikras_utils.rbs
|
100
108
|
- tests/acl.fox
|