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,3 @@
1
+ # Public: Specific exception to throw when a certain computation time exceeds a predefined limit
2
+ class ComputationTimedOutException < StandardError
3
+ 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: []