arbac_verifier 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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