mikras_utils 0.3.2 → 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 +31 -14
- 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/id_functions.rb +92 -103
- data/lib/mikras_utils/mkacl/generators/role_functions.rb +37 -40
- 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 +3 -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
@@ -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,
|
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
|
-
--
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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,
|
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
|
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,
|
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,
|
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,
|
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(
|
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
|
-
--
|
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(),
|
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(
|
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
|
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(),
|
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}(
|
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(),
|
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
|
@@ -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'
|
@@ -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)
|
@@ -19,6 +20,7 @@ module MkAcl
|
|
19
20
|
ROLES = CASE_ROLES + EVENT_ROLES + VISIT_ROLES
|
20
21
|
end
|
21
22
|
|
23
|
+
require_relative 'mkacl/simple_symtab.rb'
|
22
24
|
require_relative 'mkacl/spec.rb'
|
23
25
|
require_relative 'mkacl/parser.rb'
|
24
26
|
require_relative 'mkacl/analyzer.rb'
|