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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44d3e6e0a841c09767bef483f9c53a6c1e11f8a1603671865635a50eb139d0cd
4
- data.tar.gz: 246bba109256e0bb1e67c0c5cc518640e99ddd2a0ca01a403a4008fd04c53298
3
+ metadata.gz: 18748116f3fc80df20d80cb5a47476750ca7e25fe14d249ce90c80e29939f14a
4
+ data.tar.gz: f96b3c8969cc3b65e9ec3992bb1b20dba39e3fc271b11523c767b2c4c0e1ba12
5
5
  SHA512:
6
- metadata.gz: bc3fb06cbbc9ae2321a486dea657f2e6645ca3e3ceea3e72d36e5cb6733ef16741097fb29e669d4fbc0e64ef072b5f6ac3edc530a3b5abf0048ad93934d5c926
7
- data.tar.gz: 8f4d326809b4ed7495a964da19de29a75818a70882c447cef7f202526c764b720bb396f931d879b505fcf49bd7cf85b4d17eee8f3970a915b60d92098c60d8fb
6
+ metadata.gz: 5e5a985b839f0223e0a52b5e1dc61423fa70a407460d64f02d42a7b1d53a9609d0568acdc918804bb994cb1f93bb763fcbedb415be496a5ef57a7006274666c7
7
+ data.tar.gz: c2c2d1f2509e854c96b4ffb97369f3f31a6b60363b23db317a1d69b48c0d8a92d2f464af60a9e4ad220aa11ab25ab6d032b358876704842ddc27d9dd8042aac5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pastore (0.0.1)
4
+ pastore (0.0.3)
5
5
  rails (>= 4.0)
6
6
 
7
7
  GEM
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
- | `authorize_with(Symbol|&block)` | Allows to specify a custom authorization logic to use for action access check. Accepts a method name or a block. |
226
- | `skip_guards(*Symbol|*String, except: *Symbol|*String)` | 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`). |
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
@@ -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 # rubocop:disable Metrics/ModuleLength
8
+ module Guards
8
9
  extend ActiveSupport::Concern
9
10
 
11
+ class RoleConflictError < StandardError; end
12
+
10
13
  included do
11
- before_action :pastore_check_access
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 :_role_detector, :_default_strategy, :_action_permitted_roles, :_controller_allowed_roles,
16
- :_forbidden_callback, :_action_authorization_lambda, :_controller_authorization_lambdas,
17
- :_action_denied_roles, :_controller_denied_roles, :_actions_with_skipped_guards,
18
- :_actions_with_active_guards
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
- self._role_detector = block
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
- self._forbidden_callback = block
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
- self._default_strategy = :deny
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
- self._default_strategy = :allow
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
- self._actions_with_active_guards = [except].flatten.compact.map(&:to_sym)
47
- self._actions_with_skipped_guards = actions.flatten.compact.map(&:to_sym)
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
- self._action_permitted_roles = [roles].flatten.compact.uniq.map(&:to_s)
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
- self._action_denied_roles = [roles].flatten.compact.uniq.map(&:to_s)
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
- custom_lambda = method_name.to_sym if method_name.is_a?(Symbol) || method_name.is_a?(String)
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) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
82
- unless _action_permitted_roles.blank?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pastore
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.3'
5
5
  end
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.1
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-01-31 00:00:00.000000000 Z
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