arbac_verifier 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/arbac_verifier/classes/arbac_instance.rb +141 -78
- data/lib/arbac_verifier/classes/arbac_reachability_verifier.rb +69 -0
- data/lib/arbac_verifier/classes/rules/can_assign_rule.rb +53 -0
- data/lib/arbac_verifier/classes/rules/can_revoke_rule.rb +39 -0
- data/lib/arbac_verifier/classes/user_role.rb +32 -0
- data/lib/arbac_verifier/modules/arbac_utils_module.rb +66 -0
- data/lib/arbac_verifier.rb +1 -1
- metadata +68 -9
- data/lib/arbac_verifier/modules/arbac_module.rb +0 -179
- data/lib/main.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1b80a72041dc3311907f2b2b8a0c08c4c33cbf6efefa066b2c5c8af89909597
|
4
|
+
data.tar.gz: 2177787d1bc90d1e7f847188e8ba0ac1a0efa502f9cec9c5e908facbd458e7c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b38d72d7e6aa90123c8daf27e21bdaa78853949db8ef7ca4ebf37d20561a4c7c72e0a826cff3f017badaa447c9b955846690446cee5f76849173ce7ffa2ebc7f
|
7
|
+
data.tar.gz: ec9849fb5a809eb80ebff238d54d56ecb1942a0797656f7201b269e0fa9e5b3a13e6787cd48433c33e777a5dce8dec3bce28461415c1ea50eec7095e61cc8592
|
@@ -1,86 +1,149 @@
|
|
1
|
-
# :
|
2
|
-
|
3
|
-
require '
|
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'
|
4
8
|
|
5
|
-
# Public: Representation of an ARBAC role reachability problem
|
6
9
|
class ArbacInstance
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
+
unless params[:path].nil?
|
33
|
+
initialize_by_file_path(T.cast(params[:path], String))
|
34
|
+
else
|
35
|
+
initialize_by_attributes(
|
36
|
+
T.cast(params[:goal], Symbol),
|
37
|
+
T.cast(params[:roles], T::Set[Symbol]),
|
38
|
+
T.cast(params[:users], T::Set[String]),
|
39
|
+
T.cast(params[:user_to_role], T::Set[UserRole]),
|
40
|
+
T.cast(params[:can_assign_rules], T::Set[CanAssignRule]),
|
41
|
+
T.cast(params[:can_revoke_rules], T::Set[CanRevokeRule])
|
42
|
+
)
|
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
|
28
85
|
end
|
29
86
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
# *NOTE* This method makes use of threads parallelize the computation of the derivates of a set of states: one thread for each state for which there is the need to compute the derivates.
|
36
|
-
# When a state contains the target, its thread kills all other threads and the method returns true.
|
37
|
-
#
|
38
|
-
# Returns true if the instance is satisfied, false otherwise
|
39
|
-
#
|
40
|
-
# Examples
|
41
|
-
#
|
42
|
-
# self.compute_reachability
|
43
|
-
# # => 0
|
44
|
-
#
|
45
|
-
def compute_reachability
|
46
|
-
all_states = Set.new
|
47
|
-
new_states = Set.new [@instance[:UA]]
|
48
|
-
found = false
|
49
|
-
start = Time.now
|
50
|
-
while !found && (new_states - all_states).length > 0
|
51
|
-
if (Time.now - start) > 1500
|
52
|
-
throw ComputationTimedOutException.new
|
53
|
-
end
|
54
|
-
old_states = new_states - all_states
|
55
|
-
all_states += new_states
|
56
|
-
new_states = Set.new
|
57
|
-
old_states.each_slice(Etc.nprocessors) do |old_states_batch|
|
58
|
-
threads = old_states_batch.map do |current_state|
|
59
|
-
Thread.new {
|
60
|
-
thread = Thread.current
|
61
|
-
thread[:new_states] = Set.new
|
62
|
-
@instance[:Users].each do |user|
|
63
|
-
@instance[:CR].each do |revocation|
|
64
|
-
thread[:new_states] << apply_role_revocation(current_state, user, revocation)
|
65
|
-
end
|
66
|
-
@instance[:CA].each do |assignment|
|
67
|
-
s = apply_role_assignment(current_state, user, assignment)
|
68
|
-
thread[:new_states] << s
|
69
|
-
if s.find{|i| i.last == @instance[:Goal]}
|
70
|
-
found = true
|
71
|
-
threads.each { |t| Thread.kill t }
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
}
|
76
|
-
end
|
77
|
-
unless found
|
78
|
-
threads.each(&:join)
|
79
|
-
new_states = threads.select{|t| !!t[:new_states]}.map{|t| [*t[:new_states]]}.flatten.to_set
|
80
|
-
end
|
81
|
-
end
|
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.")
|
82
91
|
end
|
83
|
-
found
|
84
92
|
end
|
85
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
|
86
149
|
end
|
@@ -0,0 +1,69 @@
|
|
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(args: T.any(String, ArbacInstance)).void }
|
15
|
+
def initialize(**args)
|
16
|
+
if !(args[:instance].nil?)
|
17
|
+
@instance = T.let(T.cast(args[:instance], ArbacInstance), ArbacInstance)
|
18
|
+
else
|
19
|
+
path = T.cast(args[:path], String)
|
20
|
+
@instance = ArbacUtilsModule::forward_slicing(ArbacUtilsModule::backward_slicing(ArbacInstance.new(path: path)))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { returns T::Boolean }
|
25
|
+
def verify
|
26
|
+
all_states = T.let(Set.new, T::Set[T::Set[UserRole]])
|
27
|
+
new_states = T.let(Set.new([@instance.user_to_role]), T::Set[T::Set[UserRole]])
|
28
|
+
found = T.let(false, T::Boolean)
|
29
|
+
start = T.let(Time.now, Time)
|
30
|
+
while !found && (new_states - all_states).length > 0
|
31
|
+
if (Time.now - start) > 1500
|
32
|
+
throw ComputationTimedOutException.new
|
33
|
+
end
|
34
|
+
old_states = new_states - all_states
|
35
|
+
all_states += new_states
|
36
|
+
new_states = T.let(Set.new, T::Set[T::Set[UserRole]])
|
37
|
+
old_states.each_slice(Etc.nprocessors) do |old_states_batch|
|
38
|
+
threads = old_states_batch.map do |current_state|
|
39
|
+
Thread.new {
|
40
|
+
thread = Thread.current
|
41
|
+
thread[:new_states] = T.let(Set.new, T::Set[T::Set[UserRole]])
|
42
|
+
@instance.users.each do |subject|
|
43
|
+
@instance.users.each do |object|
|
44
|
+
@instance.can_revoke_rules.each do |rule|
|
45
|
+
if rule.can_apply? current_state, subject, object
|
46
|
+
thread[:new_states] << rule.apply(current_state, object)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@instance.can_assign_rules.each do |rule|
|
50
|
+
if rule.can_apply? current_state, subject, object
|
51
|
+
new_state = rule.apply(current_state, object)
|
52
|
+
thread[:new_states] << new_state
|
53
|
+
found = new_state.to_a.map(&:role).include?(@instance.goal)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
unless found
|
61
|
+
threads.each(&:join)
|
62
|
+
new_states = threads.select{|t| !!t[:new_states]}.map{|t| [*t[:new_states]]}.flatten.to_set
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
found
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
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
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
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
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
class UserRole
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns String }
|
9
|
+
attr_reader :user
|
10
|
+
|
11
|
+
sig { returns Symbol }
|
12
|
+
attr_reader :role
|
13
|
+
|
14
|
+
sig { params(user: String, role: Symbol).void }
|
15
|
+
def initialize(user, role)
|
16
|
+
@user = user
|
17
|
+
@role = role
|
18
|
+
end
|
19
|
+
|
20
|
+
# overrides
|
21
|
+
def ==(other)
|
22
|
+
other.is_a?(UserRole) && self.user == other.user && self.role == other.role
|
23
|
+
end
|
24
|
+
|
25
|
+
def eql?(other)
|
26
|
+
self == other
|
27
|
+
end
|
28
|
+
|
29
|
+
def hash
|
30
|
+
[user, role].hash
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
|
4
|
+
# Collection of utilities to manipulate .arbac files (defining an ARBAC role reachability problem) to parse and eventually solve the problem
|
5
|
+
module ArbacUtilsModule
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
require 'set'
|
9
|
+
module_function
|
10
|
+
|
11
|
+
sig { params(policy: ArbacInstance).returns ArbacInstance }
|
12
|
+
def forward_slicing(policy)
|
13
|
+
evolving_roles_set = policy.roles & policy.user_to_role.map(&:role)
|
14
|
+
reachable_roles = T.let(Set.new, T::Set[Symbol])
|
15
|
+
while evolving_roles_set != reachable_roles
|
16
|
+
reachable_roles = evolving_roles_set.dup
|
17
|
+
policy.can_assign_rules.each do |car|
|
18
|
+
precondition_roles = car.positive_precondition_roles | [car.user_role]
|
19
|
+
if precondition_roles.proper_subset?(reachable_roles)
|
20
|
+
evolving_roles_set << car.target_role
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
unused_roles = policy.roles - reachable_roles
|
25
|
+
reduced_can_assign_rules = policy.can_assign_rules
|
26
|
+
.to_a
|
27
|
+
.select { |rule| !unused_roles.include?(rule.target_role) || (rule.positive_precondition_roles & unused_roles).empty? }
|
28
|
+
.map { |rule| CanAssignRule.new(rule.user_role, rule.positive_precondition_roles, rule.negative_precondition_roles - unused_roles, rule.target_role )}
|
29
|
+
.to_set
|
30
|
+
reduced_can_revoke_rules = policy.can_revoke_rules
|
31
|
+
.to_a
|
32
|
+
.select { |rule| !unused_roles.include? rule.target_role }
|
33
|
+
.to_set
|
34
|
+
ArbacInstance.new(
|
35
|
+
can_assign_rules: reduced_can_assign_rules,
|
36
|
+
can_revoke_rules: reduced_can_revoke_rules,
|
37
|
+
user_to_role: policy.user_to_role,
|
38
|
+
roles: policy.roles - unused_roles,
|
39
|
+
users: policy.users,
|
40
|
+
goal: policy.goal
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(policy: ArbacInstance).returns ArbacInstance }
|
45
|
+
def backward_slicing(policy)
|
46
|
+
evolving_roles_set = T.let(Set.new([policy.goal]), T::Set[Symbol])
|
47
|
+
reachable_roles = T.let(Set.new, T::Set[Symbol])
|
48
|
+
while reachable_roles != evolving_roles_set
|
49
|
+
reachable_roles = evolving_roles_set.dup
|
50
|
+
policy.can_assign_rules.each do |car|
|
51
|
+
if reachable_roles.include? car.target_role
|
52
|
+
evolving_roles_set = evolving_roles_set | [car.user_role] | car.positive_precondition_roles | car.negative_precondition_roles
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
unused_roles = policy.roles - reachable_roles
|
57
|
+
ArbacInstance.new(
|
58
|
+
can_assign_rules: policy.can_assign_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
59
|
+
can_revoke_rules: policy.can_revoke_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
|
60
|
+
user_to_role: policy.user_to_role,
|
61
|
+
roles: policy.roles - unused_roles,
|
62
|
+
users: policy.users,
|
63
|
+
goal: policy.goal
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
data/lib/arbac_verifier.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require 'arbac_verifier/classes/
|
1
|
+
require 'arbac_verifier/classes/arbac_reachability_verifier'
|
metadata
CHANGED
@@ -1,29 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arbac_verifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Sello
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sorbet-runtime-stub
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sorbet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sorbet-runtime
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tapioca
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.15'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.15'
|
13
69
|
- !ruby/object:Gem::Dependency
|
14
70
|
name: rspec
|
15
71
|
requirement: !ruby/object:Gem::Requirement
|
16
72
|
requirements:
|
17
73
|
- - "~>"
|
18
74
|
- !ruby/object:Gem::Version
|
19
|
-
version: '3'
|
75
|
+
version: '3.12'
|
20
76
|
type: :development
|
21
77
|
prerelease: false
|
22
78
|
version_requirements: !ruby/object:Gem::Requirement
|
23
79
|
requirements:
|
24
80
|
- - "~>"
|
25
81
|
- !ruby/object:Gem::Version
|
26
|
-
version: '3'
|
82
|
+
version: '3.12'
|
27
83
|
description: A simple solutor for role reachability problem instances expressed in
|
28
84
|
ARBAC format.
|
29
85
|
email: sellostefano@gmail.com
|
@@ -33,14 +89,17 @@ extra_rdoc_files: []
|
|
33
89
|
files:
|
34
90
|
- lib/arbac_verifier.rb
|
35
91
|
- lib/arbac_verifier/classes/arbac_instance.rb
|
92
|
+
- lib/arbac_verifier/classes/arbac_reachability_verifier.rb
|
93
|
+
- lib/arbac_verifier/classes/rules/can_assign_rule.rb
|
94
|
+
- lib/arbac_verifier/classes/rules/can_revoke_rule.rb
|
95
|
+
- lib/arbac_verifier/classes/user_role.rb
|
36
96
|
- lib/arbac_verifier/exceptions/computation_timed_out_exception.rb
|
37
|
-
- lib/arbac_verifier/modules/
|
38
|
-
- lib/main.rb
|
97
|
+
- lib/arbac_verifier/modules/arbac_utils_module.rb
|
39
98
|
homepage: https://rubygems.org/gems/arbac_verifier
|
40
99
|
licenses:
|
41
100
|
- Apache-2.0
|
42
101
|
metadata: {}
|
43
|
-
post_install_message:
|
102
|
+
post_install_message:
|
44
103
|
rdoc_options: []
|
45
104
|
require_paths:
|
46
105
|
- lib
|
@@ -56,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
115
|
version: '0'
|
57
116
|
requirements: []
|
58
117
|
rubygems_version: 3.3.7
|
59
|
-
signing_key:
|
118
|
+
signing_key:
|
60
119
|
specification_version: 4
|
61
120
|
summary: ARBAC role reachability problem solutor
|
62
121
|
test_files: []
|
@@ -1,179 +0,0 @@
|
|
1
|
-
# :markup: TomDoc
|
2
|
-
|
3
|
-
# Collection of utilities to manipulate .arbac files (defining an ARBAC role reachability problem) to parse and eventually solve the problem
|
4
|
-
module ArbacModule
|
5
|
-
require 'set'
|
6
|
-
module_function
|
7
|
-
|
8
|
-
# Public: Parse a given and properly formatted arbac file into an easily usable hash
|
9
|
-
#
|
10
|
-
# path - The absolute path of the file that needs to be parsed
|
11
|
-
#
|
12
|
-
# Returns an hash representing the policy in the following format:
|
13
|
-
# :Roles - set of strings, the available roles in the policy
|
14
|
-
# :Users - set of strings, the users present in the policy
|
15
|
-
# :UA - set of arrays of (2) strings, the first string of each inner array represents the user, the second the role that the user has
|
16
|
-
# :UR - set of arrays of (2) strings, the first string of each inner array represents the role in power of revoke, the second the role to be revoked
|
17
|
-
# :CA - set of arrays of (3) mixed where the first element is a string representing the role in power of assign; the second element is an array of two sets of strings,
|
18
|
-
# representing respectively the positive preconditions and the negative ones needed to apply the assignment; the third element is a string representing the role to be assigned
|
19
|
-
# :Goal - string, representing the role object of the reachability analysis for the given policy
|
20
|
-
#
|
21
|
-
# Examples
|
22
|
-
#
|
23
|
-
# parse_arbac_file("/Users/stefanosello/Documents/scuola/ARBAC_challenge/policies/policy0.arbac")
|
24
|
-
# # => {:Roles=>["Teacher", "Student", "TA"], :Users=>["stefano", "alice", "bob"], :UA=>[["stefano", "Teacher"], ["alice", "TA"]], :CR=>[["Teacher", "Student"], ["Teacher", "TA"]], :CA=>[["Teacher", [[], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Student"]], "TA"], ["Teacher", [["TA"], ["Student"]], "Teacher"]], :Goal=>"Student"}
|
25
|
-
#
|
26
|
-
def parse_arbac_file(path)
|
27
|
-
file = File.open(path)
|
28
|
-
lines = file.readlines.map{|l| l.chomp!(" ;\n")}.select{|l| !(l.nil?)}
|
29
|
-
result = Hash.new
|
30
|
-
lines.each do |line|
|
31
|
-
row = line.split(" ")
|
32
|
-
key = row[0].to_sym
|
33
|
-
result[key] = Set.new row.slice(1,row.length)
|
34
|
-
if key === :Goal
|
35
|
-
result[key] = result[key].first
|
36
|
-
elsif [:UA, :CR, :CA].include? key
|
37
|
-
result[key] = result[key].map{|item| item.slice(1,item.length - 2).split(",")}.to_set
|
38
|
-
if key === :CA
|
39
|
-
result[key].each do |entry|
|
40
|
-
items = entry[1].split("&")
|
41
|
-
negatives = items.select{|i| i.start_with? "-"}
|
42
|
-
positives = items - negatives
|
43
|
-
entry[1] = [positives.to_set, negatives.map{|i| i.slice(1, i.length - 1)}.to_set]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
result
|
49
|
-
end
|
50
|
-
|
51
|
-
# Public: Compute the forward slicing algorithm within a given policy in order to make the latter smaller and easier to analyze
|
52
|
-
#
|
53
|
-
# original_policy - The policy object, expressed as an hash structured in the same way of the return value of parse_arbac_file(string)
|
54
|
-
#
|
55
|
-
# Returns the simplified policy
|
56
|
-
#
|
57
|
-
# Examples
|
58
|
-
#
|
59
|
-
# forward_slicing({:Roles=>["Teacher", "Student", "TA", "Admin"], :Users=>["stefano", "alice", "bob"], :UA=>[["stefano", "Teacher"], ["alice", "TA"]], :CR=>[["Teacher", "Student"], ["Teacher", "TA"]], :CA=>[["Teacher", [["Admin"], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Student"]], "TA"], ["Teacher", [["TA"], ["Student"]], "Teacher"]], :Goal=>"Student"})
|
60
|
-
# # => {:Roles=>["Teacher", "Student", "TA"], :Users=>["stefano", "alice", "bob"], :UA=>[["stefano", "Teacher"], ["alice", "TA"]], :CR=>[["Teacher", "Student"], ["Teacher", "TA"]], :CA=>[["Teacher", [[], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Student"]], "TA"], ["Teacher", [["TA"], ["Student"]], "Teacher"]], :Goal=>"Student"}
|
61
|
-
#
|
62
|
-
def forward_slicing(original_policy)
|
63
|
-
policy = original_policy.dup
|
64
|
-
s = policy[:Roles].select {|r| policy[:UA].map{ |ua| ua[1] }.include?(r)}.to_set
|
65
|
-
s_old = Set.new
|
66
|
-
while s != s_old
|
67
|
-
s_old = s.dup
|
68
|
-
policy[:CA].each do |ca|
|
69
|
-
check_set = (ca[1].first + [ca.first]).to_set
|
70
|
-
if check_set.proper_subset?(s_old)
|
71
|
-
s << ca[2]
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
unused_roles = policy[:Roles] - s
|
76
|
-
policy[:CA] = policy[:CA].select {|ca| !(unused_roles.include?(ca[2]) || (ca[1].first - unused_roles).length < ca[1].first.length)}.to_set
|
77
|
-
policy[:CR] = policy[:CR].select {|cr| !(unused_roles.include?(cr[1]))}.to_set
|
78
|
-
policy[:CA] = policy[:CA].map do |ca|
|
79
|
-
ca[1][1] = ca[1][1] - unused_roles
|
80
|
-
ca
|
81
|
-
end.to_set
|
82
|
-
policy[:Roles] -= unused_roles
|
83
|
-
policy
|
84
|
-
end
|
85
|
-
|
86
|
-
# Public: Compute the backward slicing algorithm within a given policy in order to make the latter smaller and easier to analyze
|
87
|
-
#
|
88
|
-
# original_policy - The policy object, expressed as an hash structured in the same way of the return value of parse_arbac_file(string)
|
89
|
-
#
|
90
|
-
# Returns the simplified policy
|
91
|
-
#
|
92
|
-
# Examples
|
93
|
-
#
|
94
|
-
# backward_slicing({:Roles=>["Teacher", "Student", "TA", "Admin"], :Users=>["stefano", "alice", "bob"], :UA=>[["stefano", "Teacher"], ["alice", "TA"]], :CR=>[["Teacher", "Student"], ["Teacher", "TA"]], :CA=>[["Teacher", [["Admin"], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Student"]], "TA"], ["Teacher", [["TA"], ["Student"]], "Teacher"]], :Goal=>"Student"})
|
95
|
-
# # => {:Roles=>["Teacher", "Student", "TA"], :Users=>["stefano", "alice", "bob"], :UA=>[["stefano", "Teacher"], ["alice", "TA"]], :CR=>[["Teacher", "Student"], ["Teacher", "TA"]], :CA=>[["Teacher", [[], ["Teacher", "TA"]], "Student"], ["Teacher", [[], ["Student"]], "TA"], ["Teacher", [["TA"], ["Student"]], "Teacher"]], :Goal=>"Student"}
|
96
|
-
#
|
97
|
-
def backward_slicing(original_policy)
|
98
|
-
policy = original_policy.dup
|
99
|
-
s = Set.new [policy[:Goal]]
|
100
|
-
s_old = Set.new
|
101
|
-
while s != s_old
|
102
|
-
s_old = s.dup
|
103
|
-
policy[:CA].each do |ca|
|
104
|
-
if s_old.include? ca.last
|
105
|
-
s += (ca[1][0] + ca[1][1] + [ca.first])
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
unused_roles = original_policy[:Roles] - s
|
110
|
-
policy[:CA] = policy[:CA].select{ |ca| !(unused_roles.include?(ca[2])) }.to_set
|
111
|
-
policy[:CR] = policy[:CR].select{ |cr| !(unused_roles.include?(cr[1])) }.to_set
|
112
|
-
policy[:Roles] -= unused_roles
|
113
|
-
policy
|
114
|
-
end
|
115
|
-
|
116
|
-
# Internal: Given a current state, a target user and an assignment rule, computes the rule application result state
|
117
|
-
#
|
118
|
-
# state - The initial state in which the transition should be applied, represented as a set of arrays [<user>,<role>]
|
119
|
-
# target - The string representation of the user to whom assign the new role
|
120
|
-
# assignment_rule - The assignment rule, espressed as [agent_role,[ [ positive_precondition,... ], [ negative_precondition,... ] ],new_role]
|
121
|
-
#
|
122
|
-
# Returns the state, represented in the same way of the state parameter, resulted from the application of the assignment.
|
123
|
-
#
|
124
|
-
# *Note*: if the rule cannot be applied because either there are no users in the initial state with the agent role, the target is not present in the initial state or the preconditions on the target are not satisfied, then the method returns the initial state itself
|
125
|
-
#
|
126
|
-
def apply_role_assignment(state, target, assignment_rule)
|
127
|
-
agent = assignment_rule.first
|
128
|
-
if !(state.map(&:first).include? target)
|
129
|
-
# The target user is not in the current state
|
130
|
-
state
|
131
|
-
elsif !(state.map(&:last).include? agent)
|
132
|
-
# There is no user in the current state with the agent role
|
133
|
-
state
|
134
|
-
else
|
135
|
-
positive_preconditions_hold = true
|
136
|
-
negative_preconditions_hold = true
|
137
|
-
assignment_rule[1].first.each do |role|
|
138
|
-
positive_preconditions_hold &&= (state.include? [target,role])
|
139
|
-
end
|
140
|
-
assignment_rule[1].last.each do |role|
|
141
|
-
negative_preconditions_hold &&= !(state.include? [target,role])
|
142
|
-
end
|
143
|
-
if positive_preconditions_hold && negative_preconditions_hold
|
144
|
-
new_state = state.dup
|
145
|
-
new_state << [target,assignment_rule.last]
|
146
|
-
new_state
|
147
|
-
else
|
148
|
-
# preconditions don't hold
|
149
|
-
state
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Internal: Given a current state, a target user and an revocation rule, computes the rule application result state
|
155
|
-
#
|
156
|
-
# state - The initial state in which the transition should be applied, represented as a set of arrays [<user>,<role>]
|
157
|
-
# target - The string representation of the user to whom revoke the role
|
158
|
-
# assignment_rule - The revocation rule, espressed as [<agent_role>,<role_to_revoke>]
|
159
|
-
#
|
160
|
-
# Returns the state, expressed as the state parameter, resulted from the application of the revocation.
|
161
|
-
#
|
162
|
-
# *Note*: if the rule cannot be applied because either there are no users in the initial state with the agent role or the association <target user,role to be revoked> is not present in the initial state, then the method returns the initial state itself
|
163
|
-
#
|
164
|
-
def apply_role_revocation(state, target, revocation_rule)
|
165
|
-
agent = revocation_rule.first
|
166
|
-
if !(state.include? [target,revocation_rule.last])
|
167
|
-
# The association <target user,role to be revoked> is not in the current state
|
168
|
-
state
|
169
|
-
elsif !(state.map(&:last).include? agent)
|
170
|
-
# There is no user in the current state with the agent role
|
171
|
-
state
|
172
|
-
else
|
173
|
-
new_state = state.dup
|
174
|
-
new_state.delete [target,revocation_rule.last]
|
175
|
-
new_state
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
data/lib/main.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
# :markup: TomDoc
|
3
|
-
|
4
|
-
def main(arguments) # :nodoc:
|
5
|
-
require_relative './classes/arbac_instance.rb'
|
6
|
-
|
7
|
-
if arguments.length < 1
|
8
|
-
puts "Wrong number of arguments.\nUsage: verifier.rb <arbac_file.arbac> ..."
|
9
|
-
end
|
10
|
-
|
11
|
-
arguments.each do |arg|
|
12
|
-
puts "-------------"
|
13
|
-
puts "START #{arg}"
|
14
|
-
arbac = ArbacInstance.new arg
|
15
|
-
begin
|
16
|
-
result = arbac.compute_reachability
|
17
|
-
puts "END #{arg} with #{result ? 1 : 0}"
|
18
|
-
rescue ComputationTimedOutException => _
|
19
|
-
puts "#{arg} COMPUTATION TIMED OUT"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
exit 0
|
23
|
-
end
|
24
|
-
|
25
|
-
# Start the verifier
|
26
|
-
main(ARGV)
|