pastore 0.0.1 → 0.0.3

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: 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