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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 500b55bd4414f98384d079f4e4f76c301c7d0cd55f965012541743b86915947c
4
- data.tar.gz: 1c88a9633b75f79aab289b41f64531467fe525829fe9788bdb7146d03aec89ba
3
+ metadata.gz: f1b80a72041dc3311907f2b2b8a0c08c4c33cbf6efefa066b2c5c8af89909597
4
+ data.tar.gz: 2177787d1bc90d1e7f847188e8ba0ac1a0efa502f9cec9c5e908facbd458e7c5
5
5
  SHA512:
6
- metadata.gz: f86f2e29bc343f4c77f32295dd2f29071f92d1280fc341945b08209d8acc4aa443c73c09b490310d4785dbd4fb71c1fab2c22278c2bc2bb81d2dfb28fde1bc12
7
- data.tar.gz: 6fb714760c3471dd9d40f7caf5fb9b0b4dfb839aba873e9f8c743d74cbf370ac9edc94f327e36106508c14a50bb4e343657f3524712ee47c1fb64f3cc3f8e17a
6
+ metadata.gz: b38d72d7e6aa90123c8daf27e21bdaa78853949db8ef7ca4ebf37d20561a4c7c72e0a826cff3f017badaa447c9b955846690446cee5f76849173ce7ffa2ebc7f
7
+ data.tar.gz: ec9849fb5a809eb80ebff238d54d56ecb1942a0797656f7201b269e0fa9e5b3a13e6787cd48433c33e777a5dce8dec3bce28461415c1ea50eec7095e61cc8592
@@ -1,86 +1,149 @@
1
- # :markup: TomDoc
2
- require 'thread'
3
- require 'etc'
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
- 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)))
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
- # 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
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
@@ -1 +1 @@
1
- require 'arbac_verifier/classes/arbac_instance'
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: 0.1.0
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: 2023-02-17 00:00:00.000000000 Z
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/arbac_module.rb
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)