arbac_verifier 1.0.2 → 1.1.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: 1e6677babd9700f22f8a74f28c699d7c94d833aecce94a077ff497a4f8f93cbf
4
- data.tar.gz: bfbdf60e6169c04006cc21fbde36a740d940ca904e08560759823cd76c9cf30d
3
+ metadata.gz: 16f5236fed8bc1a233deb5cf6b2c866704c2ef658dff494fbb2f636c96174cbe
4
+ data.tar.gz: e14caa0d96905d68a57cec19ca55c44dd6467268ed5da9e6d1950ed91b3a5ac8
5
5
  SHA512:
6
- metadata.gz: 5c6bc1f6256a7c0e17f804153c0d657f706c0a9e531df983e54792473c510636641a1dfe8e295cb44d3500f9ddca45900bd32b90ed9cbf38781dd3444993646d
7
- data.tar.gz: 1f8365ff6a3b127cfa620ffe37831f5a04b1b3d98646d93b3458a0dd6d73ebb381d7a229db8fc263d9bf1b3ce43304aa1d462abc12fa34ac9075e05e7d9764f6
6
+ metadata.gz: 8faf2cb1888330e0d0559f340423f3d07e972c1539b561556d661edbf3a1c3475cb7665e0b603934b0d289f8e90c691d39a711941f8f8885db9d5ccc42f09027
7
+ data.tar.gz: f85fa7100e6376127296e106ec2d09b53c785eadfa873589907197489563c815a08904e05ae6cc376ebc79a821e430d6f166b3b3795193a190fc43cce68b7645
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ ![logo.png](logo.png)
2
+
3
+ [![codecov](https://codecov.io/github/stefanosello/arbac_verifier/branch/main/graph/badge.svg?token=VXWHKJUJR2)](https://codecov.io/github/stefanosello/arbac_verifier)
4
+ [![Ruby Gem](https://github.com/stefanosello/arbac_verifier/actions/workflows/gem-push.yml/badge.svg?branch=main)](https://github.com/stefanosello/arbac_verifier/actions/workflows/gem-push.yml)
5
+ [![Gem Version](https://badge.fury.io/rb/arbac_verifier.svg)](https://badge.fury.io/rb/arbac_verifier)
6
+ [![Open Source? Yes!](https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github)](https://github.com/Naereen/badges/)
7
+
8
+
9
+ **ARBAC Verifier** is a Ruby gem designed to facilitate the modeling and verification of Administrative Role-Based Access Control (ARBAC) policies. With this tool, you can efficiently model ARBAC policies and perform verification tasks to determine if a specific role (`Goal`) can be achieved starting from a given set of states (user-to-role assignments).
10
+
11
+ This gem is grounded in comprehensive theoretical foundations, which you can explore in detail through the [official security course slides](https://secgroup.dais.unive.it/wp-content/uploads/2020/04/arbac.pdf) provided by [Ca' Foscari University](https://www.unive.it/pag/13526) of Venice.
12
+
13
+ ## Installation
14
+ The `arbac_verifier` gem can be installed from [rubygems.org](https://rubygems.org/gems/arbac_verifier) from command line:
15
+ ```{bash}
16
+ gem install arbac_verifier
17
+ ```
18
+ or by adding the following line to your `Gemfile` project:
19
+ ```{ruby}
20
+ gem 'arbac_verifier', '~> 1.0', '>= 1.0.1'
21
+ ```
22
+
23
+ ## ARBAC definition file
24
+ An ARBAC (Attribute-Based Role-Based Access Control) policy definition comprises four key components:
25
+ - **Users**: A set of individuals who are part of the system under analysis.
26
+ - **Roles**: A set of roles that can be assigned to or removed from users.
27
+ - **Can-Assign Rules**: These rules specify which roles can be assigned to users. Each rule includes:
28
+ - The role that has the authority to make the assignment.
29
+ - The role to be assigned.
30
+ - Positive preconditions: Specific roles that the user must already possess to be eligible for the new role.
31
+ - Negative preconditions: Specific roles that the user must not possess to be eligible for the new role.
32
+ - **Can-Revoke Rules**: These rules specify which roles can be revoked from users. Each rule includes:
33
+ - The role that has the authority to revoke.
34
+ - The role to be revoked.
35
+
36
+ This structure ensures that role assignments and revocations are controlled and based on the current state of the user's roles.
37
+ In order to represent a policy based on this definition, we can use `arbac` files, which should follow this format:
38
+ ```
39
+ Roles Teacher Student TA ;
40
+ Users stefano alice bob ;
41
+ UA <stefano,Teacher> <alice,TA> ;
42
+ CR <Teacher,Student> <Teacher,TA> ;
43
+ CA <Teacher,-Teacher&-TA,Student> <Teacher,-Student,TA> <Teacher,TA&-Student,Teacher> ;
44
+ Goal Student ;
45
+ ```
46
+ - Each line starts with an *header* that explains which information will be represented
47
+ - `Roles` and `Users` are straight forward
48
+ - `UA` are initial User Assignments, i.e. user-role assignments, where each item is a pair of `<user,role>`
49
+ - `CR` are Can-Revoke rules, where each item is a pair of `<revoker role, revokable role>`
50
+ - `CA` are Can-Assign rules, where each item is a tern of `<assigner role, <positive1&positive2&-negative1&-negative2>, assignable role>`
51
+ - `Goal` is not an ARBAC property: it is the target role for which the reachability should be verified
52
+ - Each line ends with a `;`
53
+ - Items of each line are space-separated
54
+
55
+ ## Usage
56
+ Once installed, the gem can be used to manage different tasks related to arbac policies.
57
+ ### Create ARBAC instance
58
+ ARBAC instances can be created by providing the path of an `.arbac` definition file or passing explicit instance attributes.
59
+ ```{Ruby}
60
+ require 'arbac_verifier'
61
+ require 'set
62
+
63
+ # Create new Arbac instance from .arbac file
64
+ policy0 = ARBACVerifier::Instance.new(path: 'policy0.arbac')
65
+
66
+ # Create new Arbac instance passing single attributes
67
+ policy1 = ARBACVerifier::Instance.new(
68
+ goal: :Student,
69
+ roles: [:Teacher, :Student, :TA].to_set,
70
+ users: ["stefano", "alice", "bob"].to_set,
71
+ user_to_role: [ARBACVerifier::UserRole.new("stefano", :Teacher), ARBACVerifier::UserRole.new("alice", :TA)].to_set,
72
+ can_assign_rules: [
73
+ ARBACVerifier::Rules::CanAssign.new(:Teacher, [].to_set, [:Teacher, :TA].to_set, :Student),
74
+ ARBACVerifier::Rules::CanAssign.new(:Teacher, [].to_set, [:Student].to_set, :TA),
75
+ ARBACVerifier::Rules::CanAssign.new(:Teacher, [:TA].to_set, [:Student].to_set, :Teacher)
76
+ ].to_set,
77
+ can_revoke_rules: [
78
+ ARBACVerifier::Rules::CanRevoke.new(:Teacher, :Student),
79
+ ARBACVerifier::Rules::CanRevoke.new(:Teacher, :TA)
80
+ ].to_set
81
+ )
82
+ ```
83
+ ### Instance pruning
84
+ Once the problem instance has been defined, the gem provides two simplification algorithms that can be used to reduce the size of the reachability problem.
85
+ These algorithms do not modify the original policy and return a new simplified policy.
86
+ ```{Ruby}
87
+ require 'arbac_verifier'
88
+
89
+ include ARBACVerifier::Utils
90
+
91
+ # apply backward slicing
92
+ policy0bs = backward_slicing(policy0)
93
+
94
+ # apply forward slicing
95
+ policy0fs = forward_slicing(policy0)
96
+ ```
97
+ ### Role reachability solution
98
+ A Role Reachability Problem solution can be computed using the `ArbacReachabilityVerifier` class.
99
+ ```{Ruby}
100
+ require 'arbac_verifier'
101
+
102
+ # Creare new reachability verifier instance starting from an .arbac file
103
+ verifier0 = ARBACVerifier::ReachabilityVerifier.new(path: 'policy0.arbac')
104
+
105
+ # or from an already created ArbacInstance
106
+ verifier1 = ARBACVerifier::ReachabilityVerifier.new(instance: policy1)
107
+
108
+ # and then compute reachability
109
+ verifier0.verify # => true
110
+ ```
111
+ **NB:** when a verifier instance is created starting from an `.arbac` file, backward and forward slicing are applied to the parsed policy.
112
+ ### Logging
113
+ By default, `ARBACVerifier::ReachabilityVerifier` logs context info to `$stdout`. Custom loggers can be set using `ARBACVerifier::ReachabilityVerifier#set_logger(logger)`.
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+ require 'sorbet-runtime'
4
+ require 'set'
5
+ require 'arbac_verifier/classes/user_role'
6
+ require 'arbac_verifier/classes/rules/can_assign'
7
+ require 'arbac_verifier/classes/rules/can_revoke'
8
+
9
+ module ARBACVerifier
10
+ class Instance
11
+ extend T::Sig
12
+
13
+ sig { returns T::Set[Symbol] }
14
+ attr_reader :roles
15
+
16
+ sig { returns T::Set[String] }
17
+ attr_reader :users
18
+
19
+ sig { returns T::Set[UserRole] }
20
+ attr_reader :user_to_role
21
+
22
+ sig { returns T::Set[Rules::CanRevoke]}
23
+ attr_reader :can_revoke_rules
24
+
25
+ sig { returns T::Set[Rules::CanAssign]}
26
+ attr_reader :can_assign_rules
27
+
28
+ sig { returns Symbol }
29
+ attr_reader :goal
30
+
31
+ sig { params(params: T.any(Symbol, T::Set[String], T::Set[Symbol], T::Set[UserRole], T::Set[Rules::CanAssign], T::Set[Rules::CanRevoke], String)).void }
32
+ def initialize(**params)
33
+ if params[:path].nil?
34
+ initialize_by_attributes(
35
+ T.cast(params[:goal], Symbol),
36
+ T.cast(params[:roles], T::Set[Symbol]),
37
+ T.cast(params[:users], T::Set[String]),
38
+ T.cast(params[:user_to_role], T::Set[UserRole]),
39
+ T.cast(params[:can_assign_rules], T::Set[Rules::CanAssign]),
40
+ T.cast(params[:can_revoke_rules], T::Set[Rules::CanRevoke])
41
+ )
42
+ else
43
+ initialize_by_file_path(T.cast(params[:path], String))
44
+ end
45
+ end
46
+
47
+ sig { params(goal: Symbol, roles: T::Set[Symbol], users: T::Set[String], user_to_role: T::Set[UserRole], can_assign_rules: T::Set[Rules::CanAssign], can_revoke_rules: T::Set[Rules::CanRevoke]).void }
48
+ private def initialize_by_attributes(goal, roles, users, user_to_role, can_assign_rules, can_revoke_rules)
49
+ @goal = goal
50
+ @roles = roles
51
+ @users = users
52
+ @user_to_role = user_to_role
53
+ @can_assign_rules = can_assign_rules
54
+ @can_revoke_rules = can_revoke_rules
55
+ end
56
+
57
+ sig { params(path: String).void }
58
+ private def initialize_by_file_path(path)
59
+ file = File.open(path)
60
+ spec_key_to_line_content = get_lines(file)
61
+ @goal = get_goal(T.must(spec_key_to_line_content[:Goal]))
62
+ @roles = get_roles(T.must(spec_key_to_line_content[:Roles]))
63
+ @users = T.must(spec_key_to_line_content[:Users]).to_set
64
+ @user_to_role = get_user_to_roles(T.must(spec_key_to_line_content[:UA]))
65
+ @can_assign_rules = get_assign_rules(T.must(spec_key_to_line_content[:CA]))
66
+ @can_revoke_rules = get_revoke_rules(T.must(spec_key_to_line_content[:CR]))
67
+ end
68
+
69
+ sig { params(file: File).returns T::Hash[Symbol,T::Array[String]]}
70
+ private def get_lines(file)
71
+ lines = T.let(file.readlines
72
+ .map { |l| l.chomp!(" ;\n") }
73
+ .select { |l| !(l.nil?) }
74
+ .map { |l| T.must l }, T::Array[String])
75
+
76
+ spec_key_to_line_content = lines.map do |l|
77
+ line_items = T.must l.split(" ")
78
+ key = T.must line_items.first
79
+ value = T.must(line_items[1..]).map { |l| T.must l }
80
+ [key.to_sym, value]
81
+ end.to_h
82
+
83
+ validate_line_keys spec_key_to_line_content
84
+
85
+ spec_key_to_line_content
86
+ end
87
+
88
+ sig { params(lines: T::Hash[Symbol,T::Array[String]]).void }
89
+ private def validate_line_keys(lines)
90
+ unless (lines.keys - [:Goal,:CA,:CR,:UA,:Users,:Roles]).empty?
91
+ throw Exception.new("Wrong spec file format.")
92
+ end
93
+ end
94
+
95
+ sig { params(goal_ary: T::Array[String]).returns Symbol }
96
+ private def get_goal(goal_ary)
97
+ goal = T.must goal_ary[0]
98
+ goal.to_sym
99
+ end
100
+
101
+ sig { params(roles_ary: T::Array[String]).returns T::Set[Symbol] }
102
+ private def get_roles(roles_ary)
103
+ not_null_roles = T.must roles_ary.reject(&:nil?)
104
+ not_null_roles.map do |r|
105
+ role = T.must r
106
+ role.to_sym
107
+ end.to_set
108
+ end
109
+
110
+ sig { params(user_to_role: T::Array[String]).returns T::Set[UserRole] }
111
+ private def get_user_to_roles(user_to_role)
112
+ user_to_role.map do |item|
113
+ params = T.must item.slice(1,item.length - 2)
114
+ params = T.must params.split(",")
115
+ user = T.must params[0]
116
+ role = T.must params[1]
117
+ UserRole.new(user, role.to_sym)
118
+ end.to_set
119
+ end
120
+
121
+ sig { params(rules: T::Array[String]).returns T::Set[Rules::CanRevoke] }
122
+ private def get_revoke_rules(rules)
123
+ rules.map do |item|
124
+ params = T.must item.slice(1,item.length - 2)
125
+ params = T.must params.split(",")
126
+ revoker_role = T.must params[0]
127
+ revoked_role = T.must params[1]
128
+ Rules::CanRevoke.new(revoker_role.to_sym, revoked_role.to_sym)
129
+ end.to_set
130
+ end
131
+
132
+ sig { params(rules: T::Array[String]).returns T::Set[Rules::CanAssign] }
133
+ private def get_assign_rules(rules)
134
+ rules.map do |item|
135
+ params = T.must item.slice(1,item.length - 2)
136
+ params = T.must params.split(",")
137
+ assigner_role = T.must params[0]
138
+ assigned_role = T.must params[2]
139
+ preconditions_string = T.must params[1]
140
+
141
+ roles = T.must preconditions_string.split("&")
142
+ negatives_string = roles.select{|i| i.start_with? "-"}
143
+ positives_string = roles - negatives_string
144
+
145
+ positives = positives_string.map { |p| p.to_sym }.to_set
146
+ negatives = negatives_string.map { |n| T.must(n.slice(1, n.length - 1)).to_sym }.to_set
147
+ Rules::CanAssign.new(assigner_role.to_sym, positives, negatives, assigned_role.to_sym)
148
+ end.to_set
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,154 @@
1
+ # typed: true
2
+ require 'etc'
3
+ require 'concurrent'
4
+ require 'logger'
5
+ require 'arbac_verifier/classes/instance'
6
+ require 'arbac_verifier/modules/utils'
7
+
8
+ module ARBACVerifier
9
+ class ReachabilityVerifier
10
+ extend T::Sig
11
+
12
+ include Utils
13
+
14
+ sig { returns Instance }
15
+ attr_reader :instance
16
+
17
+ sig { returns Logger }
18
+ def self.logger
19
+ @@logger ||= Logger.new($stdout).tap do |log|
20
+ log.progname = self.name
21
+ end
22
+ end
23
+
24
+ sig { params(logger: T.nilable(Logger)).returns T.nilable(Logger) }
25
+ def self.set_logger(logger)
26
+ @@logger = logger
27
+ end
28
+
29
+ sig { params(params: T.any(String, Instance)).void }
30
+ def initialize(**params)
31
+ if params[:instance].nil?
32
+ path = T.cast(params[:path], String)
33
+ logger.info("Initializing reachability problem for policy from file #{path}...")
34
+ instance = T.let(Instance.new(path: path), Instance)
35
+ logger.info("*** Initial instance info ***")
36
+ log_complexity(instance)
37
+ @instance = forward_slicing(backward_slicing(instance))
38
+ logger.info("*** Post pruning instance info ***")
39
+ log_complexity(@instance)
40
+ else
41
+ instance = T.cast(params[:instance], Instance)
42
+ logger.info("Initializing reachability problem for policy #{instance.hash}...")
43
+ logger.info("*** Initial instance info ***")
44
+ log_complexity(instance)
45
+ @instance = forward_slicing(backward_slicing(instance))
46
+ logger.info("*** Post pruning instance info ***")
47
+ log_complexity(@instance)
48
+ end
49
+ end
50
+
51
+ sig { returns T::Boolean }
52
+ def verify
53
+ all_states = {}
54
+ initial_state = @instance.user_to_role
55
+ new_states = { initial_state => true }
56
+ found = Concurrent::AtomicBoolean.new(false)
57
+
58
+ users = @instance.users.to_a
59
+ user_pairs = users.product(users)
60
+
61
+ num_cpus = Concurrent.processor_count
62
+ pool = Concurrent::ThreadPoolExecutor.new(
63
+ min_threads: num_cpus,
64
+ max_threads: num_cpus,
65
+ max_queue: num_cpus * 2,
66
+ fallback_policy: :caller_runs
67
+ )
68
+
69
+ until found.true? || new_states.empty?
70
+ all_states.merge!(new_states)
71
+ current_states = new_states.keys
72
+ new_states.clear
73
+
74
+ futures = current_states.flat_map do |current_state|
75
+ user_pairs.map do |subject, object|
76
+ Concurrent::Future.execute(executor: pool) do
77
+ new_local_states = []
78
+ perform_assignments(subject, object, new_local_states, all_states, current_state, found)
79
+ perform_revocations(subject, object, new_local_states, all_states, current_state)
80
+ new_local_states
81
+ end
82
+ end
83
+ end
84
+
85
+ futures.each do |future|
86
+ future.value.each { |state| new_states[state] = true }
87
+ end
88
+ break if found.true?
89
+ end
90
+
91
+ pool.shutdown
92
+ pool.wait_for_termination
93
+
94
+ found.true?
95
+ end
96
+
97
+ sig do
98
+ params(
99
+ subject: String,
100
+ object: String,
101
+ new_states: T::Array[Symbol],
102
+ all_states: T::Hash[T::Set[UserRole], T::Boolean],
103
+ current_state: T::Set[UserRole],
104
+ found: Concurrent::AtomicBoolean
105
+ ).void
106
+ end
107
+ private def perform_assignments(subject, object, new_states, all_states, current_state, found)
108
+ @instance.can_assign_rules.each do |rule|
109
+ if rule.can_apply?(current_state, subject, object)
110
+ new_state = rule.apply(current_state, object)
111
+ if new_state.any? { |ur| ur.role == @instance.goal }
112
+ found.make_true
113
+ break
114
+ end
115
+ new_states << new_state unless all_states.include?(new_state)
116
+ end
117
+ end
118
+ end
119
+
120
+ sig do
121
+ params(
122
+ subject: String,
123
+ object: String,
124
+ new_states: T::Array[Symbol],
125
+ all_states: T::Hash[T::Set[UserRole], T::Boolean],
126
+ current_state: T::Set[UserRole]
127
+ ).void
128
+ end
129
+ private def perform_revocations(subject, object, new_states, all_states, current_state)
130
+ @instance.can_revoke_rules.each do |rule|
131
+ if rule.can_apply?(current_state, subject, object)
132
+ new_state = rule.apply(current_state, object)
133
+ new_states << new_state unless all_states.include?(new_state)
134
+ end
135
+ end
136
+ end
137
+
138
+ sig { returns Logger }
139
+ private def logger
140
+ self.class.logger
141
+ end
142
+
143
+ sig { params(instance: Instance).void }
144
+ private def log_complexity(instance)
145
+ n_users, n_roles, n_can_assign, n_can_revoke = instance.users.size, instance.roles.size, instance.can_assign_rules.size, instance.can_revoke_rules.size
146
+ logger.info("# users => #{n_users}")
147
+ logger.info("# roles => #{n_roles}")
148
+ logger.info("# can_assign rules => #{n_can_assign}")
149
+ logger.info("# can_revoke rules => #{n_can_revoke}")
150
+ logger.info("# states: #{2**(n_users*n_roles)}")
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+ require 'sorbet-runtime'
4
+
5
+ module ARBACVerifier
6
+ module Rules
7
+ class CanAssign
8
+ extend T::Sig
9
+
10
+ sig { returns Symbol }
11
+ attr_reader :user_role
12
+
13
+ sig { returns T::Set[Symbol] }
14
+ attr_reader :positive_precondition_roles
15
+
16
+ sig { returns T::Set[Symbol] }
17
+ attr_reader :negative_precondition_roles
18
+
19
+ sig { returns Symbol }
20
+ attr_reader :target_role
21
+
22
+ sig do params(
23
+ user_role: Symbol,
24
+ positive_precondition_roles: T::Set[Symbol],
25
+ negative_precondition_roles: T::Set[Symbol],
26
+ target_role: Symbol).void
27
+ end
28
+ def initialize(user_role, positive_precondition_roles, negative_precondition_roles, target_role)
29
+ @user_role = user_role
30
+ @positive_precondition_roles = positive_precondition_roles
31
+ @negative_precondition_roles = negative_precondition_roles
32
+ @target_role = target_role
33
+ end
34
+
35
+ sig do params(
36
+ state: T::Set[UserRole],
37
+ assigner: String,
38
+ assignee: String).returns T::Boolean
39
+ end
40
+ def can_apply?(state, assigner, assignee)
41
+ assigner_has_rights = state.to_a.any?{ |ur| ur.user == assigner and ur.role == @user_role}
42
+ assignee_roles = state.select { |ur| ur.user == assignee}.map { |ar| ar.role }.to_set
43
+ assigner_has_rights and
44
+ positive_precondition_roles.subset? assignee_roles and
45
+ !negative_precondition_roles.intersect? assignee_roles
46
+ end
47
+
48
+ sig do params(
49
+ state: T::Set[UserRole],
50
+ assignee: String).returns T::Set[UserRole]
51
+ end
52
+ def apply(state, assignee)
53
+ state | [UserRole.new(assignee, @target_role)]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+ require 'sorbet-runtime'
4
+
5
+ module ARBACVerifier
6
+ module Rules
7
+ class CanRevoke
8
+ extend T::Sig
9
+
10
+ sig { returns Symbol }
11
+ attr_reader :user_role
12
+
13
+ sig { returns Symbol }
14
+ attr_reader :target_role
15
+
16
+ sig { params(user_role: Symbol, target_role: Symbol).void }
17
+ def initialize(user_role, target_role)
18
+ @target_role = target_role
19
+ @user_role = user_role
20
+ end
21
+
22
+ sig do params(
23
+ state: T::Set[UserRole],
24
+ revoker: String,
25
+ revokee: String).returns T::Boolean
26
+ end
27
+ def can_apply?(state, revoker, revokee)
28
+ assigner_has_rights = state.to_a.any?{ |ur| ur.user == revoker and ur.role == @user_role}
29
+ assignee_has_revoking_role = state.to_a.any?{ |ur| ur.user == revokee and ur.role == target_role}
30
+ assigner_has_rights and assignee_has_revoking_role
31
+ end
32
+
33
+ sig do params(
34
+ state: T::Set[UserRole],
35
+ revokee: String).returns T::Set[UserRole]
36
+ end
37
+ def apply(state, revokee)
38
+ new_state = state.dup
39
+ new_state.delete_if{ |ur| ur.user == revokee and ur.role == @target_role }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -2,31 +2,33 @@
2
2
  # typed: true
3
3
  require 'sorbet-runtime'
4
4
 
5
- class UserRole
6
- extend T::Sig
5
+ module ARBACVerifier
6
+ class UserRole
7
+ extend T::Sig
7
8
 
8
- sig { returns String }
9
- attr_reader :user
9
+ sig { returns String }
10
+ attr_reader :user
10
11
 
11
- sig { returns Symbol }
12
- attr_reader :role
12
+ sig { returns Symbol }
13
+ attr_reader :role
13
14
 
14
- sig { params(user: String, role: Symbol).void }
15
- def initialize(user, role)
16
- @user = user
17
- @role = role
18
- end
15
+ sig { params(user: String, role: Symbol).void }
16
+ def initialize(user, role)
17
+ @user = user
18
+ @role = role
19
+ end
19
20
 
20
- # overrides
21
- def ==(other)
22
- other.is_a?(UserRole) && self.user == other.user && self.role == other.role
23
- end
21
+ # overrides
22
+ def ==(other)
23
+ other.is_a?(UserRole) && self.user == other.user && self.role == other.role
24
+ end
24
25
 
25
- def eql?(other)
26
- self == other
27
- end
26
+ def eql?(other)
27
+ self == other
28
+ end
28
29
 
29
- def hash
30
- [user, role].hash
30
+ def hash
31
+ [user, role].hash
32
+ end
31
33
  end
32
34
  end
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ require 'sorbet-runtime'
3
+ require 'set'
4
+
5
+ module ARBACVerifier
6
+ module Utils
7
+ extend T::Sig
8
+
9
+ sig { params(policy: Instance).returns Instance }
10
+ def forward_slicing(policy)
11
+ reachable_roles = overaproximate_reachable_roles(policy)
12
+ unused_roles = policy.roles - reachable_roles
13
+ reduced_can_assign_rules = policy.can_assign_rules.to_a
14
+ .select { |rule| not(unused_roles.include?(rule.target_role)) && (rule.positive_precondition_roles & unused_roles).empty? }
15
+ .map { |rule| Rules::CanAssign.new(rule.user_role, rule.positive_precondition_roles, rule.negative_precondition_roles - unused_roles, rule.target_role )}
16
+ .to_set
17
+ reduced_can_revoke_rules = policy.can_revoke_rules.to_a
18
+ .select { |rule| not(unused_roles.include? rule.target_role) }
19
+ .to_set
20
+ new_instance = Instance.new(
21
+ can_assign_rules: reduced_can_assign_rules,
22
+ can_revoke_rules: reduced_can_revoke_rules,
23
+ user_to_role: policy.user_to_role,
24
+ roles: policy.roles - unused_roles,
25
+ users: policy.users,
26
+ goal: policy.goal
27
+ )
28
+ new_instance
29
+ end
30
+
31
+ sig { params(policy: Instance).returns T::Enumerable[Symbol] }
32
+ def overaproximate_reachable_roles(policy)
33
+ reachable_roles = T.let(Set.new, T::Set[Symbol])
34
+ evolving_roles_set = policy.user_to_role.map(&:role).to_set
35
+ while evolving_roles_set != reachable_roles
36
+ reachable_roles = evolving_roles_set.dup
37
+ policy.can_assign_rules.each do |car|
38
+ precondition_roles = car.positive_precondition_roles | [car.user_role]
39
+ if precondition_roles.proper_subset?(reachable_roles)
40
+ evolving_roles_set << car.target_role
41
+ end
42
+ end
43
+ end
44
+ reachable_roles
45
+ end
46
+
47
+ sig { params(policy: Instance).returns Instance }
48
+ def backward_slicing(policy)
49
+ relevant_roles = overaproximate_relevant_roles(policy)
50
+ unused_roles = policy.roles - relevant_roles
51
+ Instance.new(
52
+ can_assign_rules: policy.can_assign_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
53
+ can_revoke_rules: policy.can_revoke_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
54
+ user_to_role: policy.user_to_role,
55
+ roles: policy.roles - unused_roles,
56
+ users: policy.users,
57
+ goal: policy.goal
58
+ )
59
+ end
60
+
61
+ sig { params(policy: Instance).returns T::Enumerable[Symbol] }
62
+ def overaproximate_relevant_roles(policy)
63
+ relevant_roles = T.let(Set.new, T::Set[Symbol])
64
+ evolving_roles_set = T.let(Set.new([policy.goal]), T::Set[Symbol])
65
+ while relevant_roles != evolving_roles_set
66
+ relevant_roles = evolving_roles_set.dup
67
+ policy.can_assign_rules.each do |car|
68
+ if relevant_roles.include? car.target_role
69
+ evolving_roles_set = evolving_roles_set | [car.user_role] | car.positive_precondition_roles | car.negative_precondition_roles
70
+ end
71
+ end
72
+ end
73
+ relevant_roles
74
+ end
75
+ end
76
+ end
@@ -1,2 +1,2 @@
1
1
  # typed: strict
2
- require 'arbac_verifier/classes/arbac_reachability_verifier'
2
+ require 'arbac_verifier/classes/reachability_verifier'
data/logo.png ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arbac_verifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefano Sello
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-21 00:00:00.000000000 Z
11
+ date: 2024-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime-stub
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: sorbet
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,89 +94,24 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.12'
83
- description: " ![logo.png](https://github.com/stefanosello/arbac_verifier/raw/main/logo.png)\n\n
84
- \ [![codecov](https://codecov.io/github/stefanosello/arbac_verifier/branch/development/graph/badge.svg?token=VXWHKJUJR2)](https://codecov.io/github/stefanosello/arbac_verifier)\n
85
- \ [![Ruby Gem](https://github.com/stefanosello/arbac_verifier/actions/workflows/gem-push.yml/badge.svg?branch=development)](https://github.com/stefanosello/arbac_verifier/actions/workflows/gem-push.yml)\n
86
- \ [![Gem Version](https://badge.fury.io/rb/arbac_verifier.svg)](https://badge.fury.io/rb/arbac_verifier)\n
87
- \ [![Open Source? Yes!](https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github)](https://github.com/Naereen/badges/)\n
88
- \ \n \n **ARBAC Verifier** is a Ruby gem designed to facilitate the modeling
89
- and verification of Administrative Role-Based Access Control (ARBAC) policies. With
90
- this tool, you can efficiently model ARBAC policies and perform verification tasks
91
- to determine if a specific role (`Goal`) can be achieved starting from a given set
92
- of states (user-to-role assignments).\n \n This gem is grounded in comprehensive
93
- theoretical foundations, which you can explore in detail through the [official security
94
- course slides](https://secgroup.dais.unive.it/wp-content/uploads/2020/04/arbac.pdf)
95
- provided by [Ca' Foscari University](https://www.unive.it/pag/13526) of Venice.
96
- \n \n ## Installation\n The `arbac_verifier` gem can be installed from
97
- [rubygems.org](https://rubygems.org/gems/arbac_verifier) from command line: \n ```{bash}\n
98
- \ gem install arbac_verifier\n ```\n or by adding the following line to
99
- your `Gemfile` project:\n ```{ruby}\n gem 'arbac_verifier', '~> 1.0', '>=
100
- 1.0.1'\n ```\n \n ## ARBAC definition file\n An ARBAC (Attribute-Based
101
- Role-Based Access Control) policy definition comprises four key components:\n -
102
- **Users**: A set of individuals who are part of the system under analysis.\n -
103
- **Roles**: A set of roles that can be assigned to or removed from users.\n -
104
- **Can-Assign Rules**: These rules specify which roles can be assigned to users.
105
- Each rule includes:\n - The role that has the authority to make the assignment.\n
106
- \ - The role to be assigned.\n - Positive preconditions: Specific roles
107
- that the user must already possess to be eligible for the new role.\n - Negative
108
- preconditions: Specific roles that the user must not possess to be eligible for
109
- the new role.\n - **Can-Revoke Rules**: These rules specify which roles can be
110
- revoked from users. Each rule includes:\n - The role that has the authority
111
- to revoke.\n - The role to be revoked. \n \n This structure ensures that
112
- role assignments and revocations are controlled and based on the current state of
113
- the user's roles.\n In order to represent a policy based on this definition,
114
- we can use `arbac` files, which should follow this format:\n ```\n Roles Teacher
115
- Student TA ;\n Users stefano alice bob ;\n UA <stefano,Teacher> <alice,TA>
116
- ;\n CR <Teacher,Student> <Teacher,TA> ;\n CA <Teacher,-Teacher&-TA,Student>
117
- <Teacher,-Student,TA> <Teacher,TA&-Student,Teacher> ;\n Goal Student ;\n ```
118
- \n - Each line starts with an *header* that explains which information will be
119
- represented\n - `Roles` and `Users` are straight forward\n - `UA` are
120
- initial User Assignments, i.e. user-role assignments, where each item is a pair
121
- of `<user,role>`\n - `CR` are Can-Revoke rules, where each item is a pair of
122
- `<revoker role, revokable role>`\n - `CA` are Can-Assign rules, where each
123
- item is a tern of `<assigner role, <positive1&positive2&-negative1&-negative2>,
124
- assignable role>`\n - `Goal` is not an ARBAC property: it is the target role
125
- for which the reachability should be verified\n - Each line ends with a `;`\n
126
- \ - Items of each line are space-separated\n \n ## Usage\n Once installed,
127
- the gem can be used to manage different tasks related to arbac policies.\n ```{Ruby}\n
128
- \ require 'arbac_verifier'\n require 'set\n \n # Create new Arbac instance
129
- from .arbac file\n policy0 = ArbacInstance.new(path: 'policy0.arbac')\n \n
130
- \ # Create new Arbac instance passing single attributes\n policy1 = ArbacInstance.new(\n
131
- \ goal: :Student,\n roles: [:Teacher, :Student, :TA].to_set,\n users:
132
- [\"stefano\", \"alice\", \"bob\"].to_set,\n user_to_role: [UserRole.new(\"stefano\",
133
- :Teacher), UserRole.new(\"alice\", :TA)].to_set,\n can_assign_rules: [\n CanAssignRule.new(:Teacher,
134
- [].to_set, [:Teacher, :TA].to_set, :Student),\n CanAssignRule.new(:Teacher,
135
- [].to_set, [:Student].to_set, :TA),\n CanAssignRule.new(:Teacher,
136
- [:TA].to_set, [:Student].to_set, :Teacher)\n ].to_set,\n
137
- \ can_revoke_rules: [CanRevokeRule.new(:Teacher, :Student), CanRevokeRule.new(:Teacher,
138
- :TA)].to_set\n )\n ```\n \n Once the problem instance has been defined,
139
- the gem provides two simplification algorithms that can be used to reduce the size
140
- of the reachability problem.\n These algorithms do not modify the original policy
141
- and return a new simplified policy.\n ```{Ruby}\n require 'arbac_verifier'\n
142
- \ \n # apply backward slicing\n policy0bs = ArbacUtilsModule::backward_slicing(policy0)\n
143
- \ policy0fs = ArbacUtilsModule::forward_slicing(policy0)\n ```\n A Role
144
- Reachability Problem solution can be computed using the `ArbacReachabilityVerifier`
145
- class.\n ```{Ruby}\n require 'arbac_verifier'\n \n # Creare new reachability
146
- verifier instance starting from an .arbac file\n verifier0 = ArbacReachabilityVerifier.new(path:
147
- 'policy0.arbac')\n \n # or from an already created ArbacInstance\n verifier1
148
- = ArbacReachabilityVerifier.new(instance: policy1)\n \n # and then compute
149
- reachability\n verifier0.verify # => true\n ```\n **NB:** when a verifier
150
- instance is created starting from an `.arbac` file, backward and forward slicing
151
- are applied to the parsed policy.\n"
97
+ description: " A way to solve simple ARBAC role reachability problems, given an
98
+ .arbac definition file or a pre-built problem instance."
152
99
  email: sellostefano@gmail.com
153
100
  executables: []
154
101
  extensions: []
155
- extra_rdoc_files: []
102
+ extra_rdoc_files:
103
+ - README.md
156
104
  files:
105
+ - README.md
157
106
  - lib/arbac_verifier.rb
158
- - lib/arbac_verifier/classes/arbac_instance.rb
159
- - lib/arbac_verifier/classes/arbac_reachability_verifier.rb
160
- - lib/arbac_verifier/classes/rules/can_assign_rule.rb
161
- - lib/arbac_verifier/classes/rules/can_revoke_rule.rb
107
+ - lib/arbac_verifier/classes/instance.rb
108
+ - lib/arbac_verifier/classes/reachability_verifier.rb
109
+ - lib/arbac_verifier/classes/rules/can_assign.rb
110
+ - lib/arbac_verifier/classes/rules/can_revoke.rb
162
111
  - lib/arbac_verifier/classes/user_role.rb
163
- - lib/arbac_verifier/exceptions/computation_timed_out_exception.rb
164
- - lib/arbac_verifier/modules/arbac_utils_module.rb
165
- homepage: https://rubygems.org/gems/arbac_verifier
112
+ - lib/arbac_verifier/modules/utils.rb
113
+ - logo.png
114
+ homepage: https://github.com/stefanosello/arbac_verifier
166
115
  licenses:
167
116
  - Apache-2.0
168
117
  metadata: {}
@@ -174,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
123
  requirements:
175
124
  - - ">="
176
125
  - !ruby/object:Gem::Version
177
- version: '0'
126
+ version: 3.0.0
178
127
  required_rubygems_version: !ruby/object:Gem::Requirement
179
128
  requirements:
180
129
  - - ">="
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: true
3
- require 'sorbet-runtime'
4
- require 'set'
5
- require 'arbac_verifier/classes/user_role'
6
- require 'arbac_verifier/classes/rules/can_assign_rule'
7
- require 'arbac_verifier/classes/rules/can_revoke_rule'
8
-
9
- class ArbacInstance
10
- extend T::Sig
11
-
12
- sig { returns T::Set[Symbol] }
13
- attr_reader :roles
14
-
15
- sig { returns T::Set[String] }
16
- attr_reader :users
17
-
18
- sig { returns T::Set[UserRole] }
19
- attr_reader :user_to_role
20
-
21
- sig { returns T::Set[CanRevokeRule]}
22
- attr_reader :can_revoke_rules
23
-
24
- sig { returns T::Set[CanAssignRule]}
25
- attr_reader :can_assign_rules
26
-
27
- sig { returns Symbol }
28
- attr_reader :goal
29
-
30
- sig { params(params: T.any(Symbol, T::Set[String], T::Set[Symbol], T::Set[UserRole], T::Set[CanAssignRule], T::Set[CanRevokeRule], String)).void }
31
- def initialize(**params)
32
- if params[:path].nil?
33
- initialize_by_attributes(
34
- T.cast(params[:goal], Symbol),
35
- T.cast(params[:roles], T::Set[Symbol]),
36
- T.cast(params[:users], T::Set[String]),
37
- T.cast(params[:user_to_role], T::Set[UserRole]),
38
- T.cast(params[:can_assign_rules], T::Set[CanAssignRule]),
39
- T.cast(params[:can_revoke_rules], T::Set[CanRevokeRule])
40
- )
41
- else
42
- initialize_by_file_path(T.cast(params[:path], String))
43
- end
44
- end
45
-
46
- sig { params(goal: Symbol, roles: T::Set[Symbol], users: T::Set[String], user_to_role: T::Set[UserRole], can_assign_rules: T::Set[CanAssignRule], can_revoke_rules: T::Set[CanRevokeRule]).void }
47
- private def initialize_by_attributes(goal, roles, users, user_to_role, can_assign_rules, can_revoke_rules)
48
- @goal = goal
49
- @roles = roles
50
- @users = users
51
- @user_to_role = user_to_role
52
- @can_assign_rules = can_assign_rules
53
- @can_revoke_rules = can_revoke_rules
54
- end
55
-
56
- sig { params(path: String).void }
57
- private def initialize_by_file_path(path)
58
- file = File.open(path)
59
- spec_key_to_line_content = get_lines(file)
60
- @goal = get_goal(T.must(spec_key_to_line_content[:Goal]))
61
- @roles = get_roles(T.must(spec_key_to_line_content[:Roles]))
62
- @users = T.must(spec_key_to_line_content[:Users]).to_set
63
- @user_to_role = get_user_to_roles(T.must(spec_key_to_line_content[:UA]))
64
- @can_assign_rules = get_assign_rules(T.must(spec_key_to_line_content[:CA]))
65
- @can_revoke_rules = get_revoke_rules(T.must(spec_key_to_line_content[:CR]))
66
- end
67
-
68
- sig { params(file: File).returns T::Hash[Symbol,T::Array[String]]}
69
- private def get_lines(file)
70
- lines = T.let(file.readlines
71
- .map { |l| l.chomp!(" ;\n") }
72
- .select { |l| !(l.nil?) }
73
- .map { |l| T.must l }, T::Array[String])
74
-
75
- spec_key_to_line_content = lines.map do |l|
76
- line_items = T.must l.split(" ")
77
- key = T.must line_items.first
78
- value = T.must(line_items[1..]).map { |l| T.must l }
79
- [key.to_sym, value]
80
- end.to_h
81
-
82
- validate_line_keys spec_key_to_line_content
83
-
84
- spec_key_to_line_content
85
- end
86
-
87
- sig { params(lines: T::Hash[Symbol,T::Array[String]]).void }
88
- private def validate_line_keys(lines)
89
- unless (lines.keys - [:Goal,:CA,:CR,:UA,:Users,:Roles]).empty?
90
- throw Exception.new("Wrong spec file format.")
91
- end
92
- end
93
-
94
- sig { params(goal_ary: T::Array[String]).returns Symbol }
95
- private def get_goal(goal_ary)
96
- goal = T.must goal_ary[0]
97
- goal.to_sym
98
- end
99
-
100
- sig { params(roles_ary: T::Array[String]).returns T::Set[Symbol] }
101
- private def get_roles(roles_ary)
102
- not_null_roles = T.must roles_ary.reject(&:nil?)
103
- not_null_roles.map do |r|
104
- role = T.must r
105
- role.to_sym
106
- end.to_set
107
- end
108
-
109
- sig { params(user_to_role: T::Array[String]).returns T::Set[UserRole] }
110
- private def get_user_to_roles(user_to_role)
111
- user_to_role.map do |item|
112
- params = T.must item.slice(1,item.length - 2)
113
- params = T.must params.split(",")
114
- user = T.must params[0]
115
- role = T.must params[1]
116
- UserRole.new(user, role.to_sym)
117
- end.to_set
118
- end
119
-
120
- sig { params(rules: T::Array[String]).returns T::Set[CanRevokeRule] }
121
- private def get_revoke_rules(rules)
122
- rules.map do |item|
123
- params = T.must item.slice(1,item.length - 2)
124
- params = T.must params.split(",")
125
- revoker_role = T.must params[0]
126
- revoked_role = T.must params[1]
127
- CanRevokeRule.new(revoker_role.to_sym, revoked_role.to_sym)
128
- end.to_set
129
- end
130
-
131
- sig { params(rules: T::Array[String]).returns T::Set[CanAssignRule] }
132
- private def get_assign_rules(rules)
133
- rules.map do |item|
134
- params = T.must item.slice(1,item.length - 2)
135
- params = T.must params.split(",")
136
- assigner_role = T.must params[0]
137
- assigned_role = T.must params[2]
138
- preconditions_string = T.must params[1]
139
-
140
- roles = T.must preconditions_string.split("&")
141
- negatives_string = roles.select{|i| i.start_with? "-"}
142
- positives_string = roles - negatives_string
143
-
144
- positives = positives_string.map { |p| p.to_sym }.to_set
145
- negatives = negatives_string.map { |n| T.must(n.slice(1, n.length - 1)).to_sym }.to_set
146
- CanAssignRule.new(assigner_role.to_sym, positives, negatives, assigned_role.to_sym)
147
- end.to_set
148
- end
149
- end
@@ -1,70 +0,0 @@
1
- # typed: true
2
- require 'thread'
3
- require 'etc'
4
- require 'arbac_verifier/classes/arbac_instance'
5
- require 'arbac_verifier/modules/arbac_utils_module'
6
- require 'arbac_verifier/exceptions/computation_timed_out_exception'
7
-
8
- class ArbacReachabilityVerifier
9
- extend T::Sig
10
-
11
- sig { returns ArbacInstance }
12
- attr_reader :instance
13
-
14
- sig { params(params: T.any(String, ArbacInstance)).void }
15
- def initialize(**params)
16
- if params[:instance].nil?
17
- path = T.cast(params[:path], String)
18
- @instance = ArbacUtilsModule::forward_slicing(ArbacUtilsModule::backward_slicing(ArbacInstance.new(path: path)))
19
- else
20
- instance = T.cast(params[:instance], ArbacInstance)
21
- @instance = instance
22
- end
23
- end
24
-
25
- sig { returns T::Boolean }
26
- def verify
27
- all_states = T.let(Set.new, T::Set[T::Set[UserRole]])
28
- new_states = T.let(Set.new([@instance.user_to_role]), T::Set[T::Set[UserRole]])
29
- found = T.let(false, T::Boolean)
30
- start = T.let(Time.now, Time)
31
- while !found && (new_states - all_states).length > 0
32
- if (Time.now - start) > 1500
33
- throw ComputationTimedOutException.new
34
- end
35
- old_states = new_states - all_states
36
- all_states += new_states
37
- new_states = T.let(Set.new, T::Set[T::Set[UserRole]])
38
- old_states.each_slice(Etc.nprocessors) do |old_states_batch|
39
- threads = old_states_batch.map do |current_state|
40
- Thread.new {
41
- thread = Thread.current
42
- thread[:new_states] = T.let(Set.new, T::Set[T::Set[UserRole]])
43
- @instance.users.each do |subject|
44
- @instance.users.each do |object|
45
- @instance.can_revoke_rules.each do |rule|
46
- if rule.can_apply? current_state, subject, object
47
- thread[:new_states] << rule.apply(current_state, object)
48
- end
49
- end
50
- @instance.can_assign_rules.each do |rule|
51
- if rule.can_apply? current_state, subject, object
52
- new_state = rule.apply(current_state, object)
53
- thread[:new_states] << new_state
54
- found = new_state.to_a.map(&:role).include?(@instance.goal)
55
- end
56
- end
57
- end
58
- end
59
- }
60
- end
61
- unless found
62
- threads.each(&:join)
63
- new_states = threads.select{|t| !!t[:new_states]}.map{|t| [*t[:new_states]]}.flatten.to_set
64
- end
65
- end
66
- end
67
- found
68
- end
69
-
70
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: strict
3
- require 'sorbet-runtime'
4
-
5
- class CanAssignRule
6
- extend T::Sig
7
-
8
- sig { returns Symbol }
9
- attr_reader :user_role
10
-
11
- sig { returns T::Set[Symbol] }
12
- attr_reader :positive_precondition_roles
13
-
14
- sig { returns T::Set[Symbol] }
15
- attr_reader :negative_precondition_roles
16
-
17
- sig { returns Symbol }
18
- attr_reader :target_role
19
-
20
- sig do params(
21
- user_role: Symbol,
22
- positive_precondition_roles: T::Set[Symbol],
23
- negative_precondition_roles: T::Set[Symbol],
24
- target_role: Symbol).void
25
- end
26
- def initialize(user_role, positive_precondition_roles, negative_precondition_roles, target_role)
27
- @user_role = user_role
28
- @positive_precondition_roles = positive_precondition_roles
29
- @negative_precondition_roles = negative_precondition_roles
30
- @target_role = target_role
31
- end
32
-
33
- sig do params(
34
- state: T::Set[UserRole],
35
- assigner: String,
36
- assignee: String).returns T::Boolean
37
- end
38
- def can_apply?(state, assigner, assignee)
39
- assigner_has_rights = state.to_a.any?{ |ur| ur.user == assigner and ur.role == @user_role}
40
- assignee_roles = state.select { |ur| ur.user == assignee}.map { |ar| ar.role }.to_set
41
- assigner_has_rights and
42
- positive_precondition_roles.subset? assignee_roles and
43
- !negative_precondition_roles.intersect? assignee_roles
44
- end
45
-
46
- sig do params(
47
- state: T::Set[UserRole],
48
- assignee: String).returns T::Set[UserRole]
49
- end
50
- def apply(state, assignee)
51
- state | [UserRole.new(assignee, @target_role)]
52
- end
53
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: strict
3
- require 'sorbet-runtime'
4
-
5
- class CanRevokeRule
6
- extend T::Sig
7
-
8
- sig { returns Symbol }
9
- attr_reader :user_role
10
-
11
- sig { returns Symbol }
12
- attr_reader :target_role
13
-
14
- sig { params(user_role: Symbol, target_role: Symbol).void }
15
- def initialize(user_role, target_role)
16
- @target_role = target_role
17
- @user_role = user_role
18
- end
19
-
20
- sig do params(
21
- state: T::Set[UserRole],
22
- revoker: String,
23
- revokee: String).returns T::Boolean
24
- end
25
- def can_apply?(state, revoker, revokee)
26
- assigner_has_rights = state.to_a.any?{ |ur| ur.user == revoker and ur.role == @user_role}
27
- assignee_has_revoking_role = state.to_a.any?{ |ur| ur.user == revokee and ur.role == target_role}
28
- assigner_has_rights and assignee_has_revoking_role
29
- end
30
-
31
- sig do params(
32
- state: T::Set[UserRole],
33
- revokee: String).returns T::Set[UserRole]
34
- end
35
- def apply(state, revokee)
36
- new_state = state.dup
37
- new_state.delete_if{ |ur| ur.user == revokee and ur.role == @target_role }
38
- end
39
- end
@@ -1,4 +0,0 @@
1
- # typed: strict
2
- # Public: Specific exception to throw when a certain computation time exceeds a predefined limit
3
- class ComputationTimedOutException < StandardError
4
- end
@@ -1,65 +0,0 @@
1
- # typed: strict
2
- require 'sorbet-runtime'
3
-
4
- module ArbacUtilsModule
5
- extend T::Sig
6
-
7
- require 'set'
8
- module_function
9
-
10
- sig { params(policy: ArbacInstance).returns ArbacInstance }
11
- def forward_slicing(policy)
12
- evolving_roles_set = policy.roles & policy.user_to_role.map(&:role)
13
- reachable_roles = T.let(Set.new, T::Set[Symbol])
14
- while evolving_roles_set != reachable_roles
15
- reachable_roles = evolving_roles_set.dup
16
- policy.can_assign_rules.each do |car|
17
- precondition_roles = car.positive_precondition_roles | [car.user_role]
18
- if precondition_roles.proper_subset?(reachable_roles)
19
- evolving_roles_set << car.target_role
20
- end
21
- end
22
- end
23
- unused_roles = policy.roles - reachable_roles
24
- reduced_can_assign_rules = policy.can_assign_rules
25
- .to_a
26
- .select { |rule| !unused_roles.include?(rule.target_role) || (rule.positive_precondition_roles & unused_roles).empty? }
27
- .map { |rule| CanAssignRule.new(rule.user_role, rule.positive_precondition_roles, rule.negative_precondition_roles - unused_roles, rule.target_role )}
28
- .to_set
29
- reduced_can_revoke_rules = policy.can_revoke_rules
30
- .to_a
31
- .select { |rule| !unused_roles.include? rule.target_role }
32
- .to_set
33
- ArbacInstance.new(
34
- can_assign_rules: reduced_can_assign_rules,
35
- can_revoke_rules: reduced_can_revoke_rules,
36
- user_to_role: policy.user_to_role,
37
- roles: policy.roles - unused_roles,
38
- users: policy.users,
39
- goal: policy.goal
40
- )
41
- end
42
-
43
- sig { params(policy: ArbacInstance).returns ArbacInstance }
44
- def backward_slicing(policy)
45
- evolving_roles_set = T.let(Set.new([policy.goal]), T::Set[Symbol])
46
- reachable_roles = T.let(Set.new, T::Set[Symbol])
47
- while reachable_roles != evolving_roles_set
48
- reachable_roles = evolving_roles_set.dup
49
- policy.can_assign_rules.each do |car|
50
- if reachable_roles.include? car.target_role
51
- evolving_roles_set = evolving_roles_set | [car.user_role] | car.positive_precondition_roles | car.negative_precondition_roles
52
- end
53
- end
54
- end
55
- unused_roles = policy.roles - reachable_roles
56
- ArbacInstance.new(
57
- can_assign_rules: policy.can_assign_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
58
- can_revoke_rules: policy.can_revoke_rules.to_a.select { |rule| !unused_roles.include? rule.target_role }.to_set,
59
- user_to_role: policy.user_to_role,
60
- roles: policy.roles - unused_roles,
61
- users: policy.users,
62
- goal: policy.goal
63
- )
64
- end
65
- end