arbac_verifier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 500b55bd4414f98384d079f4e4f76c301c7d0cd55f965012541743b86915947c
|
4
|
+
data.tar.gz: 1c88a9633b75f79aab289b41f64531467fe525829fe9788bdb7146d03aec89ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f86f2e29bc343f4c77f32295dd2f29071f92d1280fc341945b08209d8acc4aa443c73c09b490310d4785dbd4fb71c1fab2c22278c2bc2bb81d2dfb28fde1bc12
|
7
|
+
data.tar.gz: 6fb714760c3471dd9d40f7caf5fb9b0b4dfb839aba873e9f8c743d74cbf370ac9edc94f327e36106508c14a50bb4e343657f3524712ee47c1fb64f3cc3f8e17a
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# :markup: TomDoc
|
2
|
+
require 'thread'
|
3
|
+
require 'etc'
|
4
|
+
|
5
|
+
# Public: Representation of an ARBAC role reachability problem
|
6
|
+
class ArbacInstance
|
7
|
+
require_relative './../modules/arbac_module.rb'
|
8
|
+
require_relative './../exceptions/computation_timed_out_exception.rb'
|
9
|
+
|
10
|
+
# Public: Gets/Sets the Hash value of @instance
|
11
|
+
attr_accessor :instance
|
12
|
+
|
13
|
+
# Public: Initializes @instance with the parsed hash value of the role reachability problem
|
14
|
+
#
|
15
|
+
# path - the String representation of the file path to parse in order to obtain the Hash representation of the role reachability problem
|
16
|
+
#
|
17
|
+
# *NOTE*: @instance will be a Hash made as follows:
|
18
|
+
# :Roles - set of strings, the available roles in the policy
|
19
|
+
# :Users - set of strings, the users present in the policy
|
20
|
+
# :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
|
21
|
+
# :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
|
22
|
+
# :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,
|
23
|
+
# 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
|
24
|
+
# :Goal - string, representing the role object of the reachability analysis for the given policy
|
25
|
+
#
|
26
|
+
def initialize(path)
|
27
|
+
@instance = ArbacModule::forward_slicing(ArbacModule::backward_slicing(ArbacModule::parse_arbac_file(path)))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Computes the solution of the role reachability problem for the active instance.
|
31
|
+
# *How does this method works?* It takes the initial state (association user-role) and computes all the possible states applying the "assign" and "revoke" rules.
|
32
|
+
# Then it iterates over the new computed states (those different from the initial state) computing each time all possible derivates.
|
33
|
+
# The method terminates when its execution exceeds 15oo seconds, when there are no new states to inpect or when one of the computed states contains the role to reach.
|
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
|
82
|
+
end
|
83
|
+
found
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,179 @@
|
|
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
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'arbac_verifier/classes/arbac_instance'
|
data/lib/main.rb
ADDED
@@ -0,0 +1,26 @@
|
|
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)
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arbac_verifier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefano Sello
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
description: A simple solutor for role reachability problem instances expressed in
|
28
|
+
ARBAC format.
|
29
|
+
email: sellostefano@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/arbac_verifier.rb
|
35
|
+
- lib/arbac_verifier/classes/arbac_instance.rb
|
36
|
+
- lib/arbac_verifier/exceptions/computation_timed_out_exception.rb
|
37
|
+
- lib/arbac_verifier/modules/arbac_module.rb
|
38
|
+
- lib/main.rb
|
39
|
+
homepage: https://rubygems.org/gems/arbac_verifier
|
40
|
+
licenses:
|
41
|
+
- Apache-2.0
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubygems_version: 3.3.7
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: ARBAC role reachability problem solutor
|
62
|
+
test_files: []
|