arbac_verifier 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|