arbac_verifier 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +24 -12
- data/lib/arbac_verifier/classes/instance.rb +151 -0
- data/lib/arbac_verifier/classes/reachability_verifier.rb +154 -0
- data/lib/arbac_verifier/classes/rules/can_assign.rb +57 -0
- data/lib/arbac_verifier/classes/rules/can_revoke.rb +43 -0
- data/lib/arbac_verifier/classes/user_role.rb +22 -20
- data/lib/arbac_verifier/modules/utils.rb +76 -0
- data/lib/arbac_verifier.rb +1 -1
- data/logo.png +0 -0
- metadata +22 -8
- data/lib/arbac_verifier/classes/arbac_instance.rb +0 -149
- data/lib/arbac_verifier/classes/arbac_reachability_verifier.rb +0 -70
- data/lib/arbac_verifier/classes/rules/can_assign_rule.rb +0 -53
- data/lib/arbac_verifier/classes/rules/can_revoke_rule.rb +0 -39
- data/lib/arbac_verifier/exceptions/computation_timed_out_exception.rb +0 -4
- data/lib/arbac_verifier/modules/arbac_utils_module.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16f5236fed8bc1a233deb5cf6b2c866704c2ef658dff494fbb2f636c96174cbe
|
4
|
+
data.tar.gz: e14caa0d96905d68a57cec19ca55c44dd6467268ed5da9e6d1950ed91b3a5ac8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8faf2cb1888330e0d0559f340423f3d07e972c1539b561556d661edbf3a1c3475cb7665e0b603934b0d289f8e90c691d39a711941f8f8885db9d5ccc42f09027
|
7
|
+
data.tar.gz: f85fa7100e6376127296e106ec2d09b53c785eadfa873589907197489563c815a08904e05ae6cc376ebc79a821e430d6f166b3b3795193a190fc43cce68b7645
|
data/README.md
CHANGED
@@ -54,48 +54,60 @@ Goal Student ;
|
|
54
54
|
|
55
55
|
## Usage
|
56
56
|
Once installed, the gem can be used to manage different tasks related to arbac policies.
|
57
|
+
### Create ARBAC instance
|
58
|
+
ARBAC instances can be created by providing the path of an `.arbac` definition file or passing explicit instance attributes.
|
57
59
|
```{Ruby}
|
58
60
|
require 'arbac_verifier'
|
59
61
|
require 'set
|
60
62
|
|
61
63
|
# Create new Arbac instance from .arbac file
|
62
|
-
policy0 =
|
64
|
+
policy0 = ARBACVerifier::Instance.new(path: 'policy0.arbac')
|
63
65
|
|
64
66
|
# Create new Arbac instance passing single attributes
|
65
|
-
policy1 =
|
67
|
+
policy1 = ARBACVerifier::Instance.new(
|
66
68
|
goal: :Student,
|
67
69
|
roles: [:Teacher, :Student, :TA].to_set,
|
68
70
|
users: ["stefano", "alice", "bob"].to_set,
|
69
|
-
user_to_role: [UserRole.new("stefano", :Teacher), UserRole.new("alice", :TA)].to_set,
|
71
|
+
user_to_role: [ARBACVerifier::UserRole.new("stefano", :Teacher), ARBACVerifier::UserRole.new("alice", :TA)].to_set,
|
70
72
|
can_assign_rules: [
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
ARBACVerifier::Rules::CanAssign.new(:Teacher, [].to_set, [:Teacher, :TA].to_set, :Student),
|
74
|
+
ARBACVerifier::Rules::CanAssign.new(:Teacher, [].to_set, [:Student].to_set, :TA),
|
75
|
+
ARBACVerifier::Rules::CanAssign.new(:Teacher, [:TA].to_set, [:Student].to_set, :Teacher)
|
74
76
|
].to_set,
|
75
|
-
can_revoke_rules: [
|
77
|
+
can_revoke_rules: [
|
78
|
+
ARBACVerifier::Rules::CanRevoke.new(:Teacher, :Student),
|
79
|
+
ARBACVerifier::Rules::CanRevoke.new(:Teacher, :TA)
|
80
|
+
].to_set
|
76
81
|
)
|
77
82
|
```
|
78
|
-
|
83
|
+
### Instance pruning
|
79
84
|
Once the problem instance has been defined, the gem provides two simplification algorithms that can be used to reduce the size of the reachability problem.
|
80
85
|
These algorithms do not modify the original policy and return a new simplified policy.
|
81
86
|
```{Ruby}
|
82
87
|
require 'arbac_verifier'
|
83
88
|
|
89
|
+
include ARBACVerifier::Utils
|
90
|
+
|
84
91
|
# apply backward slicing
|
85
|
-
policy0bs =
|
86
|
-
|
92
|
+
policy0bs = backward_slicing(policy0)
|
93
|
+
|
94
|
+
# apply forward slicing
|
95
|
+
policy0fs = forward_slicing(policy0)
|
87
96
|
```
|
97
|
+
### Role reachability solution
|
88
98
|
A Role Reachability Problem solution can be computed using the `ArbacReachabilityVerifier` class.
|
89
99
|
```{Ruby}
|
90
100
|
require 'arbac_verifier'
|
91
101
|
|
92
102
|
# Creare new reachability verifier instance starting from an .arbac file
|
93
|
-
verifier0 =
|
103
|
+
verifier0 = ARBACVerifier::ReachabilityVerifier.new(path: 'policy0.arbac')
|
94
104
|
|
95
105
|
# or from an already created ArbacInstance
|
96
|
-
verifier1 =
|
106
|
+
verifier1 = ARBACVerifier::ReachabilityVerifier.new(instance: policy1)
|
97
107
|
|
98
108
|
# and then compute reachability
|
99
109
|
verifier0.verify # => true
|
100
110
|
```
|
101
111
|
**NB:** when a verifier instance is created starting from an `.arbac` file, backward and forward slicing are applied to the parsed policy.
|
112
|
+
### Logging
|
113
|
+
By default, `ARBACVerifier::ReachabilityVerifier` logs context info to `$stdout`. Custom loggers can be set using `ARBACVerifier::ReachabilityVerifier#set_logger(logger)`.
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
require 'set'
|
5
|
+
require 'arbac_verifier/classes/user_role'
|
6
|
+
require 'arbac_verifier/classes/rules/can_assign'
|
7
|
+
require 'arbac_verifier/classes/rules/can_revoke'
|
8
|
+
|
9
|
+
module ARBACVerifier
|
10
|
+
class Instance
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { returns T::Set[Symbol] }
|
14
|
+
attr_reader :roles
|
15
|
+
|
16
|
+
sig { returns T::Set[String] }
|
17
|
+
attr_reader :users
|
18
|
+
|
19
|
+
sig { returns T::Set[UserRole] }
|
20
|
+
attr_reader :user_to_role
|
21
|
+
|
22
|
+
sig { returns T::Set[Rules::CanRevoke]}
|
23
|
+
attr_reader :can_revoke_rules
|
24
|
+
|
25
|
+
sig { returns T::Set[Rules::CanAssign]}
|
26
|
+
attr_reader :can_assign_rules
|
27
|
+
|
28
|
+
sig { returns Symbol }
|
29
|
+
attr_reader :goal
|
30
|
+
|
31
|
+
sig { params(params: T.any(Symbol, T::Set[String], T::Set[Symbol], T::Set[UserRole], T::Set[Rules::CanAssign], T::Set[Rules::CanRevoke], String)).void }
|
32
|
+
def initialize(**params)
|
33
|
+
if params[:path].nil?
|
34
|
+
initialize_by_attributes(
|
35
|
+
T.cast(params[:goal], Symbol),
|
36
|
+
T.cast(params[:roles], T::Set[Symbol]),
|
37
|
+
T.cast(params[:users], T::Set[String]),
|
38
|
+
T.cast(params[:user_to_role], T::Set[UserRole]),
|
39
|
+
T.cast(params[:can_assign_rules], T::Set[Rules::CanAssign]),
|
40
|
+
T.cast(params[:can_revoke_rules], T::Set[Rules::CanRevoke])
|
41
|
+
)
|
42
|
+
else
|
43
|
+
initialize_by_file_path(T.cast(params[:path], String))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(goal: Symbol, roles: T::Set[Symbol], users: T::Set[String], user_to_role: T::Set[UserRole], can_assign_rules: T::Set[Rules::CanAssign], can_revoke_rules: T::Set[Rules::CanRevoke]).void }
|
48
|
+
private def initialize_by_attributes(goal, roles, users, user_to_role, can_assign_rules, can_revoke_rules)
|
49
|
+
@goal = goal
|
50
|
+
@roles = roles
|
51
|
+
@users = users
|
52
|
+
@user_to_role = user_to_role
|
53
|
+
@can_assign_rules = can_assign_rules
|
54
|
+
@can_revoke_rules = can_revoke_rules
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { params(path: String).void }
|
58
|
+
private def initialize_by_file_path(path)
|
59
|
+
file = File.open(path)
|
60
|
+
spec_key_to_line_content = get_lines(file)
|
61
|
+
@goal = get_goal(T.must(spec_key_to_line_content[:Goal]))
|
62
|
+
@roles = get_roles(T.must(spec_key_to_line_content[:Roles]))
|
63
|
+
@users = T.must(spec_key_to_line_content[:Users]).to_set
|
64
|
+
@user_to_role = get_user_to_roles(T.must(spec_key_to_line_content[:UA]))
|
65
|
+
@can_assign_rules = get_assign_rules(T.must(spec_key_to_line_content[:CA]))
|
66
|
+
@can_revoke_rules = get_revoke_rules(T.must(spec_key_to_line_content[:CR]))
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(file: File).returns T::Hash[Symbol,T::Array[String]]}
|
70
|
+
private def get_lines(file)
|
71
|
+
lines = T.let(file.readlines
|
72
|
+
.map { |l| l.chomp!(" ;\n") }
|
73
|
+
.select { |l| !(l.nil?) }
|
74
|
+
.map { |l| T.must l }, T::Array[String])
|
75
|
+
|
76
|
+
spec_key_to_line_content = lines.map do |l|
|
77
|
+
line_items = T.must l.split(" ")
|
78
|
+
key = T.must line_items.first
|
79
|
+
value = T.must(line_items[1..]).map { |l| T.must l }
|
80
|
+
[key.to_sym, value]
|
81
|
+
end.to_h
|
82
|
+
|
83
|
+
validate_line_keys spec_key_to_line_content
|
84
|
+
|
85
|
+
spec_key_to_line_content
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(lines: T::Hash[Symbol,T::Array[String]]).void }
|
89
|
+
private def validate_line_keys(lines)
|
90
|
+
unless (lines.keys - [:Goal,:CA,:CR,:UA,:Users,:Roles]).empty?
|
91
|
+
throw Exception.new("Wrong spec file format.")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { params(goal_ary: T::Array[String]).returns Symbol }
|
96
|
+
private def get_goal(goal_ary)
|
97
|
+
goal = T.must goal_ary[0]
|
98
|
+
goal.to_sym
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { params(roles_ary: T::Array[String]).returns T::Set[Symbol] }
|
102
|
+
private def get_roles(roles_ary)
|
103
|
+
not_null_roles = T.must roles_ary.reject(&:nil?)
|
104
|
+
not_null_roles.map do |r|
|
105
|
+
role = T.must r
|
106
|
+
role.to_sym
|
107
|
+
end.to_set
|
108
|
+
end
|
109
|
+
|
110
|
+
sig { params(user_to_role: T::Array[String]).returns T::Set[UserRole] }
|
111
|
+
private def get_user_to_roles(user_to_role)
|
112
|
+
user_to_role.map do |item|
|
113
|
+
params = T.must item.slice(1,item.length - 2)
|
114
|
+
params = T.must params.split(",")
|
115
|
+
user = T.must params[0]
|
116
|
+
role = T.must params[1]
|
117
|
+
UserRole.new(user, role.to_sym)
|
118
|
+
end.to_set
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(rules: T::Array[String]).returns T::Set[Rules::CanRevoke] }
|
122
|
+
private def get_revoke_rules(rules)
|
123
|
+
rules.map do |item|
|
124
|
+
params = T.must item.slice(1,item.length - 2)
|
125
|
+
params = T.must params.split(",")
|
126
|
+
revoker_role = T.must params[0]
|
127
|
+
revoked_role = T.must params[1]
|
128
|
+
Rules::CanRevoke.new(revoker_role.to_sym, revoked_role.to_sym)
|
129
|
+
end.to_set
|
130
|
+
end
|
131
|
+
|
132
|
+
sig { params(rules: T::Array[String]).returns T::Set[Rules::CanAssign] }
|
133
|
+
private def get_assign_rules(rules)
|
134
|
+
rules.map do |item|
|
135
|
+
params = T.must item.slice(1,item.length - 2)
|
136
|
+
params = T.must params.split(",")
|
137
|
+
assigner_role = T.must params[0]
|
138
|
+
assigned_role = T.must params[2]
|
139
|
+
preconditions_string = T.must params[1]
|
140
|
+
|
141
|
+
roles = T.must preconditions_string.split("&")
|
142
|
+
negatives_string = roles.select{|i| i.start_with? "-"}
|
143
|
+
positives_string = roles - negatives_string
|
144
|
+
|
145
|
+
positives = positives_string.map { |p| p.to_sym }.to_set
|
146
|
+
negatives = negatives_string.map { |n| T.must(n.slice(1, n.length - 1)).to_sym }.to_set
|
147
|
+
Rules::CanAssign.new(assigner_role.to_sym, positives, negatives, assigned_role.to_sym)
|
148
|
+
end.to_set
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'etc'
|
3
|
+
require 'concurrent'
|
4
|
+
require 'logger'
|
5
|
+
require 'arbac_verifier/classes/instance'
|
6
|
+
require 'arbac_verifier/modules/utils'
|
7
|
+
|
8
|
+
module ARBACVerifier
|
9
|
+
class ReachabilityVerifier
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
include Utils
|
13
|
+
|
14
|
+
sig { returns Instance }
|
15
|
+
attr_reader :instance
|
16
|
+
|
17
|
+
sig { returns Logger }
|
18
|
+
def self.logger
|
19
|
+
@@logger ||= Logger.new($stdout).tap do |log|
|
20
|
+
log.progname = self.name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { params(logger: T.nilable(Logger)).returns T.nilable(Logger) }
|
25
|
+
def self.set_logger(logger)
|
26
|
+
@@logger = logger
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(params: T.any(String, Instance)).void }
|
30
|
+
def initialize(**params)
|
31
|
+
if params[:instance].nil?
|
32
|
+
path = T.cast(params[:path], String)
|
33
|
+
logger.info("Initializing reachability problem for policy from file #{path}...")
|
34
|
+
instance = T.let(Instance.new(path: path), Instance)
|
35
|
+
logger.info("*** Initial instance info ***")
|
36
|
+
log_complexity(instance)
|
37
|
+
@instance = forward_slicing(backward_slicing(instance))
|
38
|
+
logger.info("*** Post pruning instance info ***")
|
39
|
+
log_complexity(@instance)
|
40
|
+
else
|
41
|
+
instance = T.cast(params[:instance], Instance)
|
42
|
+
logger.info("Initializing reachability problem for policy #{instance.hash}...")
|
43
|
+
logger.info("*** Initial instance info ***")
|
44
|
+
log_complexity(instance)
|
45
|
+
@instance = forward_slicing(backward_slicing(instance))
|
46
|
+
logger.info("*** Post pruning instance info ***")
|
47
|
+
log_complexity(@instance)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { returns T::Boolean }
|
52
|
+
def verify
|
53
|
+
all_states = {}
|
54
|
+
initial_state = @instance.user_to_role
|
55
|
+
new_states = { initial_state => true }
|
56
|
+
found = Concurrent::AtomicBoolean.new(false)
|
57
|
+
|
58
|
+
users = @instance.users.to_a
|
59
|
+
user_pairs = users.product(users)
|
60
|
+
|
61
|
+
num_cpus = Concurrent.processor_count
|
62
|
+
pool = Concurrent::ThreadPoolExecutor.new(
|
63
|
+
min_threads: num_cpus,
|
64
|
+
max_threads: num_cpus,
|
65
|
+
max_queue: num_cpus * 2,
|
66
|
+
fallback_policy: :caller_runs
|
67
|
+
)
|
68
|
+
|
69
|
+
until found.true? || new_states.empty?
|
70
|
+
all_states.merge!(new_states)
|
71
|
+
current_states = new_states.keys
|
72
|
+
new_states.clear
|
73
|
+
|
74
|
+
futures = current_states.flat_map do |current_state|
|
75
|
+
user_pairs.map do |subject, object|
|
76
|
+
Concurrent::Future.execute(executor: pool) do
|
77
|
+
new_local_states = []
|
78
|
+
perform_assignments(subject, object, new_local_states, all_states, current_state, found)
|
79
|
+
perform_revocations(subject, object, new_local_states, all_states, current_state)
|
80
|
+
new_local_states
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
futures.each do |future|
|
86
|
+
future.value.each { |state| new_states[state] = true }
|
87
|
+
end
|
88
|
+
break if found.true?
|
89
|
+
end
|
90
|
+
|
91
|
+
pool.shutdown
|
92
|
+
pool.wait_for_termination
|
93
|
+
|
94
|
+
found.true?
|
95
|
+
end
|
96
|
+
|
97
|
+
sig do
|
98
|
+
params(
|
99
|
+
subject: String,
|
100
|
+
object: String,
|
101
|
+
new_states: T::Array[Symbol],
|
102
|
+
all_states: T::Hash[T::Set[UserRole], T::Boolean],
|
103
|
+
current_state: T::Set[UserRole],
|
104
|
+
found: Concurrent::AtomicBoolean
|
105
|
+
).void
|
106
|
+
end
|
107
|
+
private def perform_assignments(subject, object, new_states, all_states, current_state, found)
|
108
|
+
@instance.can_assign_rules.each do |rule|
|
109
|
+
if rule.can_apply?(current_state, subject, object)
|
110
|
+
new_state = rule.apply(current_state, object)
|
111
|
+
if new_state.any? { |ur| ur.role == @instance.goal }
|
112
|
+
found.make_true
|
113
|
+
break
|
114
|
+
end
|
115
|
+
new_states << new_state unless all_states.include?(new_state)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
sig do
|
121
|
+
params(
|
122
|
+
subject: String,
|
123
|
+
object: String,
|
124
|
+
new_states: T::Array[Symbol],
|
125
|
+
all_states: T::Hash[T::Set[UserRole], T::Boolean],
|
126
|
+
current_state: T::Set[UserRole]
|
127
|
+
).void
|
128
|
+
end
|
129
|
+
private def perform_revocations(subject, object, new_states, all_states, current_state)
|
130
|
+
@instance.can_revoke_rules.each do |rule|
|
131
|
+
if rule.can_apply?(current_state, subject, object)
|
132
|
+
new_state = rule.apply(current_state, object)
|
133
|
+
new_states << new_state unless all_states.include?(new_state)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { returns Logger }
|
139
|
+
private def logger
|
140
|
+
self.class.logger
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(instance: Instance).void }
|
144
|
+
private def log_complexity(instance)
|
145
|
+
n_users, n_roles, n_can_assign, n_can_revoke = instance.users.size, instance.roles.size, instance.can_assign_rules.size, instance.can_revoke_rules.size
|
146
|
+
logger.info("# users => #{n_users}")
|
147
|
+
logger.info("# roles => #{n_roles}")
|
148
|
+
logger.info("# can_assign rules => #{n_can_assign}")
|
149
|
+
logger.info("# can_revoke rules => #{n_can_revoke}")
|
150
|
+
logger.info("# states: #{2**(n_users*n_roles)}")
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
module ARBACVerifier
|
6
|
+
module Rules
|
7
|
+
class CanAssign
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns Symbol }
|
11
|
+
attr_reader :user_role
|
12
|
+
|
13
|
+
sig { returns T::Set[Symbol] }
|
14
|
+
attr_reader :positive_precondition_roles
|
15
|
+
|
16
|
+
sig { returns T::Set[Symbol] }
|
17
|
+
attr_reader :negative_precondition_roles
|
18
|
+
|
19
|
+
sig { returns Symbol }
|
20
|
+
attr_reader :target_role
|
21
|
+
|
22
|
+
sig do params(
|
23
|
+
user_role: Symbol,
|
24
|
+
positive_precondition_roles: T::Set[Symbol],
|
25
|
+
negative_precondition_roles: T::Set[Symbol],
|
26
|
+
target_role: Symbol).void
|
27
|
+
end
|
28
|
+
def initialize(user_role, positive_precondition_roles, negative_precondition_roles, target_role)
|
29
|
+
@user_role = user_role
|
30
|
+
@positive_precondition_roles = positive_precondition_roles
|
31
|
+
@negative_precondition_roles = negative_precondition_roles
|
32
|
+
@target_role = target_role
|
33
|
+
end
|
34
|
+
|
35
|
+
sig do params(
|
36
|
+
state: T::Set[UserRole],
|
37
|
+
assigner: String,
|
38
|
+
assignee: String).returns T::Boolean
|
39
|
+
end
|
40
|
+
def can_apply?(state, assigner, assignee)
|
41
|
+
assigner_has_rights = state.to_a.any?{ |ur| ur.user == assigner and ur.role == @user_role}
|
42
|
+
assignee_roles = state.select { |ur| ur.user == assignee}.map { |ar| ar.role }.to_set
|
43
|
+
assigner_has_rights and
|
44
|
+
positive_precondition_roles.subset? assignee_roles and
|
45
|
+
!negative_precondition_roles.intersect? assignee_roles
|
46
|
+
end
|
47
|
+
|
48
|
+
sig do params(
|
49
|
+
state: T::Set[UserRole],
|
50
|
+
assignee: String).returns T::Set[UserRole]
|
51
|
+
end
|
52
|
+
def apply(state, assignee)
|
53
|
+
state | [UserRole.new(assignee, @target_role)]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
module ARBACVerifier
|
6
|
+
module Rules
|
7
|
+
class CanRevoke
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns Symbol }
|
11
|
+
attr_reader :user_role
|
12
|
+
|
13
|
+
sig { returns Symbol }
|
14
|
+
attr_reader :target_role
|
15
|
+
|
16
|
+
sig { params(user_role: Symbol, target_role: Symbol).void }
|
17
|
+
def initialize(user_role, target_role)
|
18
|
+
@target_role = target_role
|
19
|
+
@user_role = user_role
|
20
|
+
end
|
21
|
+
|
22
|
+
sig do params(
|
23
|
+
state: T::Set[UserRole],
|
24
|
+
revoker: String,
|
25
|
+
revokee: String).returns T::Boolean
|
26
|
+
end
|
27
|
+
def can_apply?(state, revoker, revokee)
|
28
|
+
assigner_has_rights = state.to_a.any?{ |ur| ur.user == revoker and ur.role == @user_role}
|
29
|
+
assignee_has_revoking_role = state.to_a.any?{ |ur| ur.user == revokee and ur.role == target_role}
|
30
|
+
assigner_has_rights and assignee_has_revoking_role
|
31
|
+
end
|
32
|
+
|
33
|
+
sig do params(
|
34
|
+
state: T::Set[UserRole],
|
35
|
+
revokee: String).returns T::Set[UserRole]
|
36
|
+
end
|
37
|
+
def apply(state, revokee)
|
38
|
+
new_state = state.dup
|
39
|
+
new_state.delete_if{ |ur| ur.user == revokee and ur.role == @target_role }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -2,31 +2,33 @@
|
|
2
2
|
# typed: true
|
3
3
|
require 'sorbet-runtime'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
module ARBACVerifier
|
6
|
+
class UserRole
|
7
|
+
extend T::Sig
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
sig { returns String }
|
10
|
+
attr_reader :user
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
sig { returns Symbol }
|
13
|
+
attr_reader :role
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
sig { params(user: String, role: Symbol).void }
|
16
|
+
def initialize(user, role)
|
17
|
+
@user = user
|
18
|
+
@role = role
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
# overrides
|
22
|
+
def ==(other)
|
23
|
+
other.is_a?(UserRole) && self.user == other.user && self.role == other.role
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def eql?(other)
|
27
|
+
self == other
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
def hash
|
31
|
+
[user, role].hash
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module ARBACVerifier
|
6
|
+
module Utils
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(policy: Instance).returns Instance }
|
10
|
+
def forward_slicing(policy)
|
11
|
+
reachable_roles = overaproximate_reachable_roles(policy)
|
12
|
+
unused_roles = policy.roles - reachable_roles
|
13
|
+
reduced_can_assign_rules = policy.can_assign_rules.to_a
|
14
|
+
.select { |rule| not(unused_roles.include?(rule.target_role)) && (rule.positive_precondition_roles & unused_roles).empty? }
|
15
|
+
.map { |rule| Rules::CanAssign.new(rule.user_role, rule.positive_precondition_roles, rule.negative_precondition_roles - unused_roles, rule.target_role )}
|
16
|
+
.to_set
|
17
|
+
reduced_can_revoke_rules = policy.can_revoke_rules.to_a
|
18
|
+
.select { |rule| not(unused_roles.include? rule.target_role) }
|
19
|
+
.to_set
|
20
|
+
new_instance = Instance.new(
|
21
|
+
can_assign_rules: reduced_can_assign_rules,
|
22
|
+
can_revoke_rules: reduced_can_revoke_rules,
|
23
|
+
user_to_role: policy.user_to_role,
|
24
|
+
roles: policy.roles - unused_roles,
|
25
|
+
users: policy.users,
|
26
|
+
goal: policy.goal
|
27
|
+
)
|
28
|
+
new_instance
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(policy: Instance).returns T::Enumerable[Symbol] }
|
32
|
+
def overaproximate_reachable_roles(policy)
|
33
|
+
reachable_roles = T.let(Set.new, T::Set[Symbol])
|
34
|
+
evolving_roles_set = policy.user_to_role.map(&:role).to_set
|
35
|
+
while evolving_roles_set != reachable_roles
|
36
|
+
reachable_roles = evolving_roles_set.dup
|
37
|
+
policy.can_assign_rules.each do |car|
|
38
|
+
precondition_roles = car.positive_precondition_roles | [car.user_role]
|
39
|
+
if precondition_roles.proper_subset?(reachable_roles)
|
40
|
+
evolving_roles_set << car.target_role
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
reachable_roles
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(policy: Instance).returns Instance }
|
48
|
+
def backward_slicing(policy)
|
49
|
+
relevant_roles = overaproximate_relevant_roles(policy)
|
50
|
+
unused_roles = policy.roles - relevant_roles
|
51
|
+
Instance.new(
|
52
|
+
can_assign_rules: policy.can_assign_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
53
|
+
can_revoke_rules: policy.can_revoke_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
54
|
+
user_to_role: policy.user_to_role,
|
55
|
+
roles: policy.roles - unused_roles,
|
56
|
+
users: policy.users,
|
57
|
+
goal: policy.goal
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(policy: Instance).returns T::Enumerable[Symbol] }
|
62
|
+
def overaproximate_relevant_roles(policy)
|
63
|
+
relevant_roles = T.let(Set.new, T::Set[Symbol])
|
64
|
+
evolving_roles_set = T.let(Set.new([policy.goal]), T::Set[Symbol])
|
65
|
+
while relevant_roles != evolving_roles_set
|
66
|
+
relevant_roles = evolving_roles_set.dup
|
67
|
+
policy.can_assign_rules.each do |car|
|
68
|
+
if relevant_roles.include? car.target_role
|
69
|
+
evolving_roles_set = evolving_roles_set | [car.user_role] | car.positive_precondition_roles | car.negative_precondition_roles
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
relevant_roles
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/arbac_verifier.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# typed: strict
|
2
|
-
require 'arbac_verifier/classes/
|
2
|
+
require 'arbac_verifier/classes/reachability_verifier'
|
data/logo.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arbac_verifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Sello
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-runtime-stub
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: concurrent-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: sorbet
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,13 +104,13 @@ extra_rdoc_files:
|
|
90
104
|
files:
|
91
105
|
- README.md
|
92
106
|
- lib/arbac_verifier.rb
|
93
|
-
- lib/arbac_verifier/classes/
|
94
|
-
- lib/arbac_verifier/classes/
|
95
|
-
- lib/arbac_verifier/classes/rules/
|
96
|
-
- lib/arbac_verifier/classes/rules/
|
107
|
+
- lib/arbac_verifier/classes/instance.rb
|
108
|
+
- lib/arbac_verifier/classes/reachability_verifier.rb
|
109
|
+
- lib/arbac_verifier/classes/rules/can_assign.rb
|
110
|
+
- lib/arbac_verifier/classes/rules/can_revoke.rb
|
97
111
|
- lib/arbac_verifier/classes/user_role.rb
|
98
|
-
- lib/arbac_verifier/
|
99
|
-
-
|
112
|
+
- lib/arbac_verifier/modules/utils.rb
|
113
|
+
- logo.png
|
100
114
|
homepage: https://github.com/stefanosello/arbac_verifier
|
101
115
|
licenses:
|
102
116
|
- Apache-2.0
|
@@ -1,149 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# typed: true
|
3
|
-
require 'sorbet-runtime'
|
4
|
-
require 'set'
|
5
|
-
require 'arbac_verifier/classes/user_role'
|
6
|
-
require 'arbac_verifier/classes/rules/can_assign_rule'
|
7
|
-
require 'arbac_verifier/classes/rules/can_revoke_rule'
|
8
|
-
|
9
|
-
class ArbacInstance
|
10
|
-
extend T::Sig
|
11
|
-
|
12
|
-
sig { returns T::Set[Symbol] }
|
13
|
-
attr_reader :roles
|
14
|
-
|
15
|
-
sig { returns T::Set[String] }
|
16
|
-
attr_reader :users
|
17
|
-
|
18
|
-
sig { returns T::Set[UserRole] }
|
19
|
-
attr_reader :user_to_role
|
20
|
-
|
21
|
-
sig { returns T::Set[CanRevokeRule]}
|
22
|
-
attr_reader :can_revoke_rules
|
23
|
-
|
24
|
-
sig { returns T::Set[CanAssignRule]}
|
25
|
-
attr_reader :can_assign_rules
|
26
|
-
|
27
|
-
sig { returns Symbol }
|
28
|
-
attr_reader :goal
|
29
|
-
|
30
|
-
sig { params(params: T.any(Symbol, T::Set[String], T::Set[Symbol], T::Set[UserRole], T::Set[CanAssignRule], T::Set[CanRevokeRule], String)).void }
|
31
|
-
def initialize(**params)
|
32
|
-
if params[:path].nil?
|
33
|
-
initialize_by_attributes(
|
34
|
-
T.cast(params[:goal], Symbol),
|
35
|
-
T.cast(params[:roles], T::Set[Symbol]),
|
36
|
-
T.cast(params[:users], T::Set[String]),
|
37
|
-
T.cast(params[:user_to_role], T::Set[UserRole]),
|
38
|
-
T.cast(params[:can_assign_rules], T::Set[CanAssignRule]),
|
39
|
-
T.cast(params[:can_revoke_rules], T::Set[CanRevokeRule])
|
40
|
-
)
|
41
|
-
else
|
42
|
-
initialize_by_file_path(T.cast(params[:path], String))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
sig { params(goal: Symbol, roles: T::Set[Symbol], users: T::Set[String], user_to_role: T::Set[UserRole], can_assign_rules: T::Set[CanAssignRule], can_revoke_rules: T::Set[CanRevokeRule]).void }
|
47
|
-
private def initialize_by_attributes(goal, roles, users, user_to_role, can_assign_rules, can_revoke_rules)
|
48
|
-
@goal = goal
|
49
|
-
@roles = roles
|
50
|
-
@users = users
|
51
|
-
@user_to_role = user_to_role
|
52
|
-
@can_assign_rules = can_assign_rules
|
53
|
-
@can_revoke_rules = can_revoke_rules
|
54
|
-
end
|
55
|
-
|
56
|
-
sig { params(path: String).void }
|
57
|
-
private def initialize_by_file_path(path)
|
58
|
-
file = File.open(path)
|
59
|
-
spec_key_to_line_content = get_lines(file)
|
60
|
-
@goal = get_goal(T.must(spec_key_to_line_content[:Goal]))
|
61
|
-
@roles = get_roles(T.must(spec_key_to_line_content[:Roles]))
|
62
|
-
@users = T.must(spec_key_to_line_content[:Users]).to_set
|
63
|
-
@user_to_role = get_user_to_roles(T.must(spec_key_to_line_content[:UA]))
|
64
|
-
@can_assign_rules = get_assign_rules(T.must(spec_key_to_line_content[:CA]))
|
65
|
-
@can_revoke_rules = get_revoke_rules(T.must(spec_key_to_line_content[:CR]))
|
66
|
-
end
|
67
|
-
|
68
|
-
sig { params(file: File).returns T::Hash[Symbol,T::Array[String]]}
|
69
|
-
private def get_lines(file)
|
70
|
-
lines = T.let(file.readlines
|
71
|
-
.map { |l| l.chomp!(" ;\n") }
|
72
|
-
.select { |l| !(l.nil?) }
|
73
|
-
.map { |l| T.must l }, T::Array[String])
|
74
|
-
|
75
|
-
spec_key_to_line_content = lines.map do |l|
|
76
|
-
line_items = T.must l.split(" ")
|
77
|
-
key = T.must line_items.first
|
78
|
-
value = T.must(line_items[1..]).map { |l| T.must l }
|
79
|
-
[key.to_sym, value]
|
80
|
-
end.to_h
|
81
|
-
|
82
|
-
validate_line_keys spec_key_to_line_content
|
83
|
-
|
84
|
-
spec_key_to_line_content
|
85
|
-
end
|
86
|
-
|
87
|
-
sig { params(lines: T::Hash[Symbol,T::Array[String]]).void }
|
88
|
-
private def validate_line_keys(lines)
|
89
|
-
unless (lines.keys - [:Goal,:CA,:CR,:UA,:Users,:Roles]).empty?
|
90
|
-
throw Exception.new("Wrong spec file format.")
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
sig { params(goal_ary: T::Array[String]).returns Symbol }
|
95
|
-
private def get_goal(goal_ary)
|
96
|
-
goal = T.must goal_ary[0]
|
97
|
-
goal.to_sym
|
98
|
-
end
|
99
|
-
|
100
|
-
sig { params(roles_ary: T::Array[String]).returns T::Set[Symbol] }
|
101
|
-
private def get_roles(roles_ary)
|
102
|
-
not_null_roles = T.must roles_ary.reject(&:nil?)
|
103
|
-
not_null_roles.map do |r|
|
104
|
-
role = T.must r
|
105
|
-
role.to_sym
|
106
|
-
end.to_set
|
107
|
-
end
|
108
|
-
|
109
|
-
sig { params(user_to_role: T::Array[String]).returns T::Set[UserRole] }
|
110
|
-
private def get_user_to_roles(user_to_role)
|
111
|
-
user_to_role.map do |item|
|
112
|
-
params = T.must item.slice(1,item.length - 2)
|
113
|
-
params = T.must params.split(",")
|
114
|
-
user = T.must params[0]
|
115
|
-
role = T.must params[1]
|
116
|
-
UserRole.new(user, role.to_sym)
|
117
|
-
end.to_set
|
118
|
-
end
|
119
|
-
|
120
|
-
sig { params(rules: T::Array[String]).returns T::Set[CanRevokeRule] }
|
121
|
-
private def get_revoke_rules(rules)
|
122
|
-
rules.map do |item|
|
123
|
-
params = T.must item.slice(1,item.length - 2)
|
124
|
-
params = T.must params.split(",")
|
125
|
-
revoker_role = T.must params[0]
|
126
|
-
revoked_role = T.must params[1]
|
127
|
-
CanRevokeRule.new(revoker_role.to_sym, revoked_role.to_sym)
|
128
|
-
end.to_set
|
129
|
-
end
|
130
|
-
|
131
|
-
sig { params(rules: T::Array[String]).returns T::Set[CanAssignRule] }
|
132
|
-
private def get_assign_rules(rules)
|
133
|
-
rules.map do |item|
|
134
|
-
params = T.must item.slice(1,item.length - 2)
|
135
|
-
params = T.must params.split(",")
|
136
|
-
assigner_role = T.must params[0]
|
137
|
-
assigned_role = T.must params[2]
|
138
|
-
preconditions_string = T.must params[1]
|
139
|
-
|
140
|
-
roles = T.must preconditions_string.split("&")
|
141
|
-
negatives_string = roles.select{|i| i.start_with? "-"}
|
142
|
-
positives_string = roles - negatives_string
|
143
|
-
|
144
|
-
positives = positives_string.map { |p| p.to_sym }.to_set
|
145
|
-
negatives = negatives_string.map { |n| T.must(n.slice(1, n.length - 1)).to_sym }.to_set
|
146
|
-
CanAssignRule.new(assigner_role.to_sym, positives, negatives, assigned_role.to_sym)
|
147
|
-
end.to_set
|
148
|
-
end
|
149
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
require 'thread'
|
3
|
-
require 'etc'
|
4
|
-
require 'arbac_verifier/classes/arbac_instance'
|
5
|
-
require 'arbac_verifier/modules/arbac_utils_module'
|
6
|
-
require 'arbac_verifier/exceptions/computation_timed_out_exception'
|
7
|
-
|
8
|
-
class ArbacReachabilityVerifier
|
9
|
-
extend T::Sig
|
10
|
-
|
11
|
-
sig { returns ArbacInstance }
|
12
|
-
attr_reader :instance
|
13
|
-
|
14
|
-
sig { params(params: T.any(String, ArbacInstance)).void }
|
15
|
-
def initialize(**params)
|
16
|
-
if params[:instance].nil?
|
17
|
-
path = T.cast(params[:path], String)
|
18
|
-
@instance = ArbacUtilsModule::forward_slicing(ArbacUtilsModule::backward_slicing(ArbacInstance.new(path: path)))
|
19
|
-
else
|
20
|
-
instance = T.cast(params[:instance], ArbacInstance)
|
21
|
-
@instance = instance
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
sig { returns T::Boolean }
|
26
|
-
def verify
|
27
|
-
all_states = T.let(Set.new, T::Set[T::Set[UserRole]])
|
28
|
-
new_states = T.let(Set.new([@instance.user_to_role]), T::Set[T::Set[UserRole]])
|
29
|
-
found = T.let(false, T::Boolean)
|
30
|
-
start = T.let(Time.now, Time)
|
31
|
-
while !found && (new_states - all_states).length > 0
|
32
|
-
if (Time.now - start) > 1500
|
33
|
-
throw ComputationTimedOutException.new
|
34
|
-
end
|
35
|
-
old_states = new_states - all_states
|
36
|
-
all_states += new_states
|
37
|
-
new_states = T.let(Set.new, T::Set[T::Set[UserRole]])
|
38
|
-
old_states.each_slice(Etc.nprocessors) do |old_states_batch|
|
39
|
-
threads = old_states_batch.map do |current_state|
|
40
|
-
Thread.new {
|
41
|
-
thread = Thread.current
|
42
|
-
thread[:new_states] = T.let(Set.new, T::Set[T::Set[UserRole]])
|
43
|
-
@instance.users.each do |subject|
|
44
|
-
@instance.users.each do |object|
|
45
|
-
@instance.can_revoke_rules.each do |rule|
|
46
|
-
if rule.can_apply? current_state, subject, object
|
47
|
-
thread[:new_states] << rule.apply(current_state, object)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
@instance.can_assign_rules.each do |rule|
|
51
|
-
if rule.can_apply? current_state, subject, object
|
52
|
-
new_state = rule.apply(current_state, object)
|
53
|
-
thread[:new_states] << new_state
|
54
|
-
found = new_state.to_a.map(&:role).include?(@instance.goal)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
}
|
60
|
-
end
|
61
|
-
unless found
|
62
|
-
threads.each(&:join)
|
63
|
-
new_states = threads.select{|t| !!t[:new_states]}.map{|t| [*t[:new_states]]}.flatten.to_set
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
found
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# typed: strict
|
3
|
-
require 'sorbet-runtime'
|
4
|
-
|
5
|
-
class CanAssignRule
|
6
|
-
extend T::Sig
|
7
|
-
|
8
|
-
sig { returns Symbol }
|
9
|
-
attr_reader :user_role
|
10
|
-
|
11
|
-
sig { returns T::Set[Symbol] }
|
12
|
-
attr_reader :positive_precondition_roles
|
13
|
-
|
14
|
-
sig { returns T::Set[Symbol] }
|
15
|
-
attr_reader :negative_precondition_roles
|
16
|
-
|
17
|
-
sig { returns Symbol }
|
18
|
-
attr_reader :target_role
|
19
|
-
|
20
|
-
sig do params(
|
21
|
-
user_role: Symbol,
|
22
|
-
positive_precondition_roles: T::Set[Symbol],
|
23
|
-
negative_precondition_roles: T::Set[Symbol],
|
24
|
-
target_role: Symbol).void
|
25
|
-
end
|
26
|
-
def initialize(user_role, positive_precondition_roles, negative_precondition_roles, target_role)
|
27
|
-
@user_role = user_role
|
28
|
-
@positive_precondition_roles = positive_precondition_roles
|
29
|
-
@negative_precondition_roles = negative_precondition_roles
|
30
|
-
@target_role = target_role
|
31
|
-
end
|
32
|
-
|
33
|
-
sig do params(
|
34
|
-
state: T::Set[UserRole],
|
35
|
-
assigner: String,
|
36
|
-
assignee: String).returns T::Boolean
|
37
|
-
end
|
38
|
-
def can_apply?(state, assigner, assignee)
|
39
|
-
assigner_has_rights = state.to_a.any?{ |ur| ur.user == assigner and ur.role == @user_role}
|
40
|
-
assignee_roles = state.select { |ur| ur.user == assignee}.map { |ar| ar.role }.to_set
|
41
|
-
assigner_has_rights and
|
42
|
-
positive_precondition_roles.subset? assignee_roles and
|
43
|
-
!negative_precondition_roles.intersect? assignee_roles
|
44
|
-
end
|
45
|
-
|
46
|
-
sig do params(
|
47
|
-
state: T::Set[UserRole],
|
48
|
-
assignee: String).returns T::Set[UserRole]
|
49
|
-
end
|
50
|
-
def apply(state, assignee)
|
51
|
-
state | [UserRole.new(assignee, @target_role)]
|
52
|
-
end
|
53
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# typed: strict
|
3
|
-
require 'sorbet-runtime'
|
4
|
-
|
5
|
-
class CanRevokeRule
|
6
|
-
extend T::Sig
|
7
|
-
|
8
|
-
sig { returns Symbol }
|
9
|
-
attr_reader :user_role
|
10
|
-
|
11
|
-
sig { returns Symbol }
|
12
|
-
attr_reader :target_role
|
13
|
-
|
14
|
-
sig { params(user_role: Symbol, target_role: Symbol).void }
|
15
|
-
def initialize(user_role, target_role)
|
16
|
-
@target_role = target_role
|
17
|
-
@user_role = user_role
|
18
|
-
end
|
19
|
-
|
20
|
-
sig do params(
|
21
|
-
state: T::Set[UserRole],
|
22
|
-
revoker: String,
|
23
|
-
revokee: String).returns T::Boolean
|
24
|
-
end
|
25
|
-
def can_apply?(state, revoker, revokee)
|
26
|
-
assigner_has_rights = state.to_a.any?{ |ur| ur.user == revoker and ur.role == @user_role}
|
27
|
-
assignee_has_revoking_role = state.to_a.any?{ |ur| ur.user == revokee and ur.role == target_role}
|
28
|
-
assigner_has_rights and assignee_has_revoking_role
|
29
|
-
end
|
30
|
-
|
31
|
-
sig do params(
|
32
|
-
state: T::Set[UserRole],
|
33
|
-
revokee: String).returns T::Set[UserRole]
|
34
|
-
end
|
35
|
-
def apply(state, revokee)
|
36
|
-
new_state = state.dup
|
37
|
-
new_state.delete_if{ |ur| ur.user == revokee and ur.role == @target_role }
|
38
|
-
end
|
39
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
require 'sorbet-runtime'
|
3
|
-
|
4
|
-
module ArbacUtilsModule
|
5
|
-
extend T::Sig
|
6
|
-
|
7
|
-
require 'set'
|
8
|
-
module_function
|
9
|
-
|
10
|
-
sig { params(policy: ArbacInstance).returns ArbacInstance }
|
11
|
-
def forward_slicing(policy)
|
12
|
-
evolving_roles_set = policy.roles & policy.user_to_role.map(&:role)
|
13
|
-
reachable_roles = T.let(Set.new, T::Set[Symbol])
|
14
|
-
while evolving_roles_set != reachable_roles
|
15
|
-
reachable_roles = evolving_roles_set.dup
|
16
|
-
policy.can_assign_rules.each do |car|
|
17
|
-
precondition_roles = car.positive_precondition_roles | [car.user_role]
|
18
|
-
if precondition_roles.proper_subset?(reachable_roles)
|
19
|
-
evolving_roles_set << car.target_role
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
unused_roles = policy.roles - reachable_roles
|
24
|
-
reduced_can_assign_rules = policy.can_assign_rules
|
25
|
-
.to_a
|
26
|
-
.select { |rule| !unused_roles.include?(rule.target_role) || (rule.positive_precondition_roles & unused_roles).empty? }
|
27
|
-
.map { |rule| CanAssignRule.new(rule.user_role, rule.positive_precondition_roles, rule.negative_precondition_roles - unused_roles, rule.target_role )}
|
28
|
-
.to_set
|
29
|
-
reduced_can_revoke_rules = policy.can_revoke_rules
|
30
|
-
.to_a
|
31
|
-
.select { |rule| !unused_roles.include? rule.target_role }
|
32
|
-
.to_set
|
33
|
-
ArbacInstance.new(
|
34
|
-
can_assign_rules: reduced_can_assign_rules,
|
35
|
-
can_revoke_rules: reduced_can_revoke_rules,
|
36
|
-
user_to_role: policy.user_to_role,
|
37
|
-
roles: policy.roles - unused_roles,
|
38
|
-
users: policy.users,
|
39
|
-
goal: policy.goal
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
sig { params(policy: ArbacInstance).returns ArbacInstance }
|
44
|
-
def backward_slicing(policy)
|
45
|
-
evolving_roles_set = T.let(Set.new([policy.goal]), T::Set[Symbol])
|
46
|
-
reachable_roles = T.let(Set.new, T::Set[Symbol])
|
47
|
-
while reachable_roles != evolving_roles_set
|
48
|
-
reachable_roles = evolving_roles_set.dup
|
49
|
-
policy.can_assign_rules.each do |car|
|
50
|
-
if reachable_roles.include? car.target_role
|
51
|
-
evolving_roles_set = evolving_roles_set | [car.user_role] | car.positive_precondition_roles | car.negative_precondition_roles
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
unused_roles = policy.roles - reachable_roles
|
56
|
-
ArbacInstance.new(
|
57
|
-
can_assign_rules: policy.can_assign_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
58
|
-
can_revoke_rules: policy.can_revoke_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
59
|
-
user_to_role: policy.user_to_role,
|
60
|
-
roles: policy.roles - unused_roles,
|
61
|
-
users: policy.users,
|
62
|
-
goal: policy.goal
|
63
|
-
)
|
64
|
-
end
|
65
|
-
end
|