pastore 0.0.1 → 0.0.3
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/lib/pastore/guards/settings.rb +186 -0
- data/lib/pastore/guards.rb +31 -132
- data/lib/pastore/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18748116f3fc80df20d80cb5a47476750ca7e25fe14d249ce90c80e29939f14a
|
4
|
+
data.tar.gz: f96b3c8969cc3b65e9ec3992bb1b20dba39e3fc271b11523c767b2c4c0e1ba12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e5a985b839f0223e0a52b5e1dc61423fa70a407460d64f02d42a7b1d53a9609d0568acdc918804bb994cb1f93bb763fcbedb415be496a5ef57a7006274666c7
|
7
|
+
data.tar.gz: c2c2d1f2509e854c96b4ffb97369f3f31a6b60363b23db317a1d69b48c0d8a92d2f464af60a9e4ad220aa11ab25ab6d032b358876704842ddc27d9dd8042aac5
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -222,8 +222,8 @@ your Rails Controller.
|
|
222
222
|
| `detect_role(&block)` | Allows to specify the logic for user role detection. This block will be automatically called to get current user's role information. |
|
223
223
|
| `permit_role(*roles)` | Specifies the roles that are allowed to access the action that follows. |
|
224
224
|
| `deny_role(*roles)` | Specifies the roles that are not allowed to access the action that follows. |
|
225
|
-
|
|
226
|
-
|
|
225
|
+
| <code>authorize_with(Symbol\|&block)</code> | Allows to specify a custom authorization logic to use for action access check. Accepts a method name or a block. |
|
226
|
+
| <code>skip_guards(*Symbol\|*String, except: *Symbol\|*String)</code> | Allows to disable guards for specific actions. Accepts a list of actions for which to disable guards (e.g. `skip_guards :index, 'show'`), but can be also used to disable guards for all the actions except specified (e.g. `skip_guards except: :index`). |
|
227
227
|
|
228
228
|
## Contributing
|
229
229
|
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pastore
|
4
|
+
module Guards
|
5
|
+
# Implements a structure where to store the settings for the guards.
|
6
|
+
class Settings # rubocop:disable Metrics/ClassLength
|
7
|
+
|
8
|
+
attr_writer :role_detector, :forbidden_cbk
|
9
|
+
attr_reader :strategy
|
10
|
+
|
11
|
+
def initialize(superklass)
|
12
|
+
@super_guards = superklass.pastore_guards if superklass.respond_to?(:pastore_guards)
|
13
|
+
@superclass = superklass
|
14
|
+
reset!
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset!
|
18
|
+
@strategy = :deny
|
19
|
+
@role_detector = nil
|
20
|
+
@forbidden_cbk = nil
|
21
|
+
@actions = {}
|
22
|
+
@buffer = {}
|
23
|
+
@skipped_guards = []
|
24
|
+
@forced_guards = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def role_detector
|
28
|
+
@role_detector || @super_guards&.role_detector
|
29
|
+
end
|
30
|
+
|
31
|
+
def forbidden_cbk
|
32
|
+
@forbidden_cbk || @super_guards&.forbidden_cbk
|
33
|
+
end
|
34
|
+
|
35
|
+
def use_allow_strategy!
|
36
|
+
@strategy = :allow
|
37
|
+
end
|
38
|
+
|
39
|
+
def use_deny_strategy!
|
40
|
+
@strategy = :deny
|
41
|
+
end
|
42
|
+
|
43
|
+
def permit_role(*roles)
|
44
|
+
new_roles = [roles].flatten.compact.uniq.map(&:to_s)
|
45
|
+
conflicts = @buffer.fetch(:denied_roles, []) & new_roles
|
46
|
+
|
47
|
+
unless conflicts.empty?
|
48
|
+
raise Pastore::Guards::RoleConflictError, "Roles conflict: #{conflicts} roles already specified with #deny_role"
|
49
|
+
end
|
50
|
+
|
51
|
+
if @buffer[:authorization_lambda].present?
|
52
|
+
raise Pastore::Guards::RoleConflictError, 'An #authorize_with has already been specified'
|
53
|
+
end
|
54
|
+
|
55
|
+
@buffer[:permitted_roles] = new_roles
|
56
|
+
end
|
57
|
+
|
58
|
+
def deny_role(*roles)
|
59
|
+
new_roles = [roles].flatten.compact.uniq.map(&:to_s)
|
60
|
+
conflicts = @buffer.fetch(:permitted_roles, []) & new_roles
|
61
|
+
|
62
|
+
unless conflicts.empty?
|
63
|
+
raise Pastore::Guards::RoleConflictError, "Roles conflict: #{conflicts} roles already specified with #permit_role"
|
64
|
+
end
|
65
|
+
|
66
|
+
if @buffer[:authorization_lambda].present?
|
67
|
+
raise Pastore::Guards::RoleConflictError, 'An #authorize_with has already been specified'
|
68
|
+
end
|
69
|
+
|
70
|
+
@buffer[:denied_roles] = new_roles
|
71
|
+
end
|
72
|
+
|
73
|
+
def authorize_with(method_name = nil, &block)
|
74
|
+
if @buffer[:permitted_roles].present? || @buffer[:denied_roles].present?
|
75
|
+
raise Pastore::Guards::RoleConflictError, 'A role has already been specified with #permit_role or #deny_role'
|
76
|
+
end
|
77
|
+
|
78
|
+
custom_lambda = method_name.to_sym if method_name.is_a?(Symbol) || method_name.is_a?(String)
|
79
|
+
custom_lambda = block if block_given?
|
80
|
+
|
81
|
+
if custom_lambda.present?
|
82
|
+
@buffer[:authorization_lambda] = custom_lambda
|
83
|
+
else
|
84
|
+
raise ArgumentError, 'A block or a method name must be provided'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def save_guards_for(action_name)
|
89
|
+
return if @buffer.empty?
|
90
|
+
|
91
|
+
name = action_name.to_sym
|
92
|
+
@actions[name] ||= {}
|
93
|
+
|
94
|
+
save_permitted_roles!(name)
|
95
|
+
save_denied_roles!(name)
|
96
|
+
save_authorization_lambda!(name)
|
97
|
+
|
98
|
+
reset_buffer!
|
99
|
+
end
|
100
|
+
|
101
|
+
def reset_buffer!
|
102
|
+
@buffer = {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def skip_guards_for(*actions)
|
106
|
+
@skipped_guards = [actions].flatten.compact.map(&:to_sym)
|
107
|
+
end
|
108
|
+
|
109
|
+
def force_guards_for(*actions)
|
110
|
+
@forced_guards = [actions].flatten.compact.map(&:to_sym)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the current role for the controller.
|
114
|
+
def current_role(controller)
|
115
|
+
return nil if role_detector.blank?
|
116
|
+
|
117
|
+
controller.instance_exec(&role_detector)&.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
def access_granted?(controller, action_name) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
121
|
+
# Get setting for the current action.
|
122
|
+
action = @actions[action_name.to_sym]
|
123
|
+
|
124
|
+
return true if skip_guards?(action_name)
|
125
|
+
|
126
|
+
result = authorize_with_lambda(controller, action)
|
127
|
+
return result unless result.nil?
|
128
|
+
|
129
|
+
role = current_role(controller)
|
130
|
+
|
131
|
+
return false if action&.dig(:denied_roles)&.include?(role)
|
132
|
+
|
133
|
+
@strategy == :allow || (@strategy == :deny && action&.dig(:permitted_roles)&.include?(role)) || false
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def save_permitted_roles!(action_name)
|
139
|
+
return if @buffer[:permitted_roles].blank?
|
140
|
+
|
141
|
+
@actions[action_name][:permitted_roles] = @buffer[:permitted_roles]
|
142
|
+
end
|
143
|
+
|
144
|
+
def save_denied_roles!(action_name)
|
145
|
+
return if @buffer[:denied_roles].blank?
|
146
|
+
|
147
|
+
@actions[action_name][:denied_roles] = @buffer[:denied_roles]
|
148
|
+
end
|
149
|
+
|
150
|
+
def save_authorization_lambda!(action_name)
|
151
|
+
return if @buffer[:authorization_lambda].blank?
|
152
|
+
|
153
|
+
@actions[action_name][:authorization_lambda] = @buffer[:authorization_lambda]
|
154
|
+
end
|
155
|
+
|
156
|
+
def skip_guards?(action_name)
|
157
|
+
# If current action is listed in `:except` field of `skip_guards`, then we have to run guards (return false).
|
158
|
+
return false if @forced_guards&.include?(action_name.to_sym)
|
159
|
+
|
160
|
+
# If `skip_guards` has specified an `:except` field, then we can skip guards, because current action has
|
161
|
+
# implicitely been marked as "to skip guards".
|
162
|
+
return true if @forced_guards.present?
|
163
|
+
|
164
|
+
# If `skip_guards` don't have the any `except` field, just check if current actions is listed in
|
165
|
+
# `skip_guards`.
|
166
|
+
return true if @skipped_guards&.include?(action_name.to_sym)
|
167
|
+
|
168
|
+
# Current action isn't listed in `skip_guards`, so we have to run guards (return false).
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
def authorize_with_lambda(controller, action)
|
173
|
+
# When an authorization lambda is defined, it has the priority over the other guards.
|
174
|
+
authorization_lambda = action&.dig(:authorization_lambda)
|
175
|
+
if authorization_lambda.present?
|
176
|
+
result = controller.instance_eval(&authorization_lambda) if authorization_lambda.is_a?(Proc)
|
177
|
+
result = controller.instance_eval(authorization_lambda.to_s) if authorization_lambda.is_a?(Symbol)
|
178
|
+
|
179
|
+
return result
|
180
|
+
end
|
181
|
+
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/pastore/guards.rb
CHANGED
@@ -1,182 +1,81 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_support/concern'
|
4
|
+
require_relative 'guards/settings'
|
4
5
|
|
5
6
|
module Pastore
|
6
7
|
# Implements the features for Rails controller access guards.
|
7
|
-
module Guards
|
8
|
+
module Guards
|
8
9
|
extend ActiveSupport::Concern
|
9
10
|
|
11
|
+
class RoleConflictError < StandardError; end
|
12
|
+
|
10
13
|
included do
|
11
|
-
before_action
|
14
|
+
before_action do
|
15
|
+
guards = self.class.pastore_guards
|
16
|
+
next if guards.access_granted?(self, action_name)
|
17
|
+
|
18
|
+
if guards.forbidden_cbk.present?
|
19
|
+
instance_eval(&guards.forbidden_cbk)
|
20
|
+
response.status = :forbidden
|
21
|
+
else
|
22
|
+
render json: { message: 'Forbidden' }, status: :forbidden
|
23
|
+
end
|
24
|
+
end
|
12
25
|
end
|
13
26
|
|
14
27
|
class_methods do # rubocop:disable Metrics/BlockLength
|
15
|
-
attr_accessor :
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
attr_accessor :_pastore_guards
|
29
|
+
|
30
|
+
def pastore_guards
|
31
|
+
self._pastore_guards ||= Pastore::Guards::Settings.new(superclass)
|
32
|
+
end
|
19
33
|
|
20
34
|
# Sets the logic to use for current role detection.
|
21
35
|
def detect_role(&block)
|
22
|
-
|
36
|
+
pastore_guards.role_detector = block
|
23
37
|
end
|
24
38
|
|
25
39
|
# Specifies a custom callback to be called when access is forbidden.
|
26
40
|
def forbidden(&block)
|
27
|
-
|
41
|
+
pastore_guards.forbidden_cbk = block
|
28
42
|
end
|
29
43
|
|
30
44
|
# Sets the default strategy to "deny".
|
31
45
|
def use_deny_strategy!
|
32
|
-
|
46
|
+
pastore_guards.use_deny_strategy!
|
33
47
|
end
|
34
48
|
|
35
49
|
# Sets the default strategy to "allow".
|
36
50
|
def use_allow_strategy!
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns the default strategy.
|
41
|
-
def pastore_default_strategy
|
42
|
-
_default_strategy || :deny
|
51
|
+
pastore_guards.use_allow_strategy!
|
43
52
|
end
|
44
53
|
|
45
54
|
def skip_guards(*actions, except: [])
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
def actions_with_active_guards
|
51
|
-
_actions_with_active_guards || []
|
52
|
-
end
|
53
|
-
|
54
|
-
def actions_with_skipped_guards
|
55
|
-
_actions_with_skipped_guards || []
|
55
|
+
pastore_guards.skip_guards_for(*actions)
|
56
|
+
pastore_guards.force_guards_for(*except)
|
56
57
|
end
|
57
58
|
|
58
59
|
# Specify the list of roles allowed to access the action.
|
59
60
|
def permit_role(*roles)
|
60
|
-
|
61
|
+
pastore_guards.permit_role(*roles)
|
61
62
|
end
|
62
63
|
|
63
64
|
# Specify the list of roles denied to access the action.
|
64
65
|
def deny_role(*roles)
|
65
|
-
|
66
|
+
pastore_guards.deny_role(*roles)
|
66
67
|
end
|
67
68
|
|
68
69
|
# Specify a custom lambda to be called to authorize the action.
|
69
70
|
def authorize_with(method_name = nil, &block)
|
70
|
-
|
71
|
-
custom_lambda = block if block_given?
|
72
|
-
|
73
|
-
if custom_lambda.present?
|
74
|
-
self._action_authorization_lambda = custom_lambda
|
75
|
-
else
|
76
|
-
raise ArgumentError, 'A block or a method name must be provided'
|
77
|
-
end
|
71
|
+
pastore_guards.authorize_with(method_name, &block)
|
78
72
|
end
|
79
73
|
|
80
74
|
# Save the configurations of the action when the action is defined.
|
81
|
-
def method_added(name, *args)
|
82
|
-
|
83
|
-
self._controller_allowed_roles ||= {}
|
84
|
-
self._controller_allowed_roles[name] = _action_permitted_roles
|
85
|
-
self._action_permitted_roles = nil
|
86
|
-
end
|
87
|
-
|
88
|
-
unless _action_denied_roles.blank?
|
89
|
-
self._controller_denied_roles ||= {}
|
90
|
-
self._controller_denied_roles[name] = _action_denied_roles
|
91
|
-
self._action_denied_roles = nil
|
92
|
-
end
|
93
|
-
|
94
|
-
unless _action_authorization_lambda.blank?
|
95
|
-
self._controller_authorization_lambdas ||= {}
|
96
|
-
self._controller_authorization_lambdas[name] = _action_authorization_lambda
|
97
|
-
self._action_authorization_lambda = nil
|
98
|
-
end
|
99
|
-
|
75
|
+
def method_added(name, *args)
|
76
|
+
pastore_guards.save_guards_for(name)
|
100
77
|
super
|
101
78
|
end
|
102
79
|
end
|
103
|
-
|
104
|
-
protected
|
105
|
-
|
106
|
-
# Returns the current role detected by the role detector logic.
|
107
|
-
def pastore_current_role
|
108
|
-
self.class._role_detector&.call&.to_s
|
109
|
-
end
|
110
|
-
|
111
|
-
# Returns the list of roles allowed to access current action.
|
112
|
-
def pastore_allowed_roles
|
113
|
-
self.class._controller_allowed_roles&.dig(action_name.to_sym) || []
|
114
|
-
end
|
115
|
-
|
116
|
-
# Returns the list of roles denied to access current action.
|
117
|
-
def pastore_denied_roles
|
118
|
-
self.class._controller_denied_roles&.dig(action_name.to_sym) || []
|
119
|
-
end
|
120
|
-
|
121
|
-
# Returns the custom lambda to be called to authorize the action.
|
122
|
-
def pastore_authorization_lambda
|
123
|
-
self.class._controller_authorization_lambdas&.dig(action_name.to_sym)
|
124
|
-
end
|
125
|
-
|
126
|
-
def skip_pastore_guards?
|
127
|
-
active_guards = self.class._actions_with_active_guards
|
128
|
-
|
129
|
-
# If current action is listed in `:except` field of `skip_guards`, then we have to run guards (return false).
|
130
|
-
return false if active_guards&.include?(action_name.to_sym)
|
131
|
-
|
132
|
-
# If `skip_guards` has specified an `:except` field, then we can skip guards, because current action has
|
133
|
-
# implicitely been marked as "to skip guards".
|
134
|
-
return true if active_guards.present?
|
135
|
-
|
136
|
-
# If `skip_guards` don't have the any `except` field, just check if current actions is listed in
|
137
|
-
# `skip_guards`.
|
138
|
-
return true if self.class._actions_with_skipped_guards&.include?(action_name.to_sym)
|
139
|
-
|
140
|
-
# Current action isn't listed in `skip_guards`, so we have to run guards (return false).
|
141
|
-
false
|
142
|
-
end
|
143
|
-
|
144
|
-
# Performs the access check for current action.
|
145
|
-
def pastore_check_access # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
|
146
|
-
return if skip_pastore_guards?
|
147
|
-
|
148
|
-
if pastore_authorization_lambda.present?
|
149
|
-
authorized = instance_eval(&pastore_authorization_lambda) if pastore_authorization_lambda.is_a?(Proc)
|
150
|
-
authorized = send(pastore_authorization_lambda) if pastore_authorization_lambda.is_a?(Symbol)
|
151
|
-
|
152
|
-
return if authorized
|
153
|
-
|
154
|
-
return pastore_deny_access!
|
155
|
-
end
|
156
|
-
|
157
|
-
case self.class.pastore_default_strategy
|
158
|
-
when :deny then check_access_with_deny_strategy
|
159
|
-
when :allow then check_access_with_allow_strategy
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def check_access_with_deny_strategy
|
164
|
-
return pastore_deny_access! unless pastore_allowed_roles.include?(pastore_current_role)
|
165
|
-
end
|
166
|
-
|
167
|
-
def check_access_with_allow_strategy
|
168
|
-
return pastore_deny_access! if pastore_denied_roles.include?(pastore_current_role)
|
169
|
-
end
|
170
|
-
|
171
|
-
def pastore_deny_access!
|
172
|
-
callback = self.class._forbidden_callback
|
173
|
-
|
174
|
-
if callback
|
175
|
-
instance_eval(&callback)
|
176
|
-
response.status = :forbidden
|
177
|
-
else
|
178
|
-
render json: { message: 'Forbidden' }, status: :forbidden
|
179
|
-
end
|
180
|
-
end
|
181
80
|
end
|
182
81
|
end
|
data/lib/pastore/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pastore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Groza Sergiu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- Rakefile
|
52
52
|
- lib/pastore.rb
|
53
53
|
- lib/pastore/guards.rb
|
54
|
+
- lib/pastore/guards/settings.rb
|
54
55
|
- lib/pastore/params_validators.rb
|
55
56
|
- lib/pastore/version.rb
|
56
57
|
homepage: https://github.com/demetra-it/pastore
|