pastore 0.0.1 → 0.0.2

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: 410e528dd9b199e672139ce5bca88f06a25a3c68d0d81955f1ffedf2d57f8d8e
4
+ data.tar.gz: 0e9e886326196f6f115b5827906310d75896a08cec53ec34fbb62a0627ee8107
5
5
  SHA512:
6
- metadata.gz: bc3fb06cbbc9ae2321a486dea657f2e6645ca3e3ceea3e72d36e5cb6733ef16741097fb29e669d4fbc0e64ef072b5f6ac3edc530a3b5abf0048ad93934d5c926
7
- data.tar.gz: 8f4d326809b4ed7495a964da19de29a75818a70882c447cef7f202526c764b720bb396f931d879b505fcf49bd7cf85b4d17eee8f3970a915b60d92098c60d8fb
6
+ metadata.gz: 2e8eae1e20fdc6429b8094f0f17c974f156d8394af38f62b3a8fc22406208a43ab278704d887f26243cbed4f6e53da972bc0ae7d2474bb7c90895fc52d724836
7
+ data.tar.gz: 83140c5b356109e2340bdfe1b161d8d77522463861d71da8deb299d2fb2742892f9b4abad5d96c24c777c485de802aaeae87d0dc329bde3f31d91a18dbc1e3ea
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.2)
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,175 @@
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
+ attr_accessor :role_detector, :forbidden_cbk
8
+ attr_reader :strategy
9
+
10
+ def initialize
11
+ reset!
12
+ end
13
+
14
+ def reset!
15
+ @strategy = :deny
16
+ @role_detector = nil
17
+ @forbidden_cbk = nil
18
+ @actions = {}
19
+ @buffer = {}
20
+ @skipped_guards = []
21
+ @forced_guards = []
22
+ end
23
+
24
+ def use_allow_strategy!
25
+ @strategy = :allow
26
+ end
27
+
28
+ def use_deny_strategy!
29
+ @strategy = :deny
30
+ end
31
+
32
+ def permit_role(*roles)
33
+ new_roles = [roles].flatten.compact.uniq.map(&:to_s)
34
+ conflicts = @buffer.fetch(:denied_roles, []) & new_roles
35
+
36
+ unless conflicts.empty?
37
+ raise Pastore::Guards::RoleConflictError, "Roles conflict: #{conflicts} roles already specified with #deny_role"
38
+ end
39
+
40
+ if @buffer[:authorization_lambda].present?
41
+ raise Pastore::Guards::RoleConflictError, 'An #authorize_with has already been specified'
42
+ end
43
+
44
+ @buffer[:permitted_roles] = new_roles
45
+ end
46
+
47
+ def deny_role(*roles)
48
+ new_roles = [roles].flatten.compact.uniq.map(&:to_s)
49
+ conflicts = @buffer.fetch(:permitted_roles, []) & new_roles
50
+
51
+ unless conflicts.empty?
52
+ raise Pastore::Guards::RoleConflictError, "Roles conflict: #{conflicts} roles already specified with #permit_role"
53
+ end
54
+
55
+ if @buffer[:authorization_lambda].present?
56
+ raise Pastore::Guards::RoleConflictError, 'An #authorize_with has already been specified'
57
+ end
58
+
59
+ @buffer[:denied_roles] = new_roles
60
+ end
61
+
62
+ def authorize_with(method_name = nil, &block)
63
+ if @buffer[:permitted_roles].present? || @buffer[:denied_roles].present?
64
+ raise Pastore::Guards::RoleConflictError, 'A role has already been specified with #permit_role or #deny_role'
65
+ end
66
+
67
+ custom_lambda = method_name.to_sym if method_name.is_a?(Symbol) || method_name.is_a?(String)
68
+ custom_lambda = block if block_given?
69
+
70
+ if custom_lambda.present?
71
+ @buffer[:authorization_lambda] = custom_lambda
72
+ else
73
+ raise ArgumentError, 'A block or a method name must be provided'
74
+ end
75
+ end
76
+
77
+ def save_guards_for(action_name)
78
+ return if @buffer.empty?
79
+
80
+ name = action_name.to_sym
81
+ @actions[name] ||= {}
82
+
83
+ save_permitted_roles!(name)
84
+ save_denied_roles!(name)
85
+ save_authorization_lambda!(name)
86
+
87
+ reset_buffer!
88
+ end
89
+
90
+ def reset_buffer!
91
+ @buffer = {}
92
+ end
93
+
94
+ def skip_guards_for(*actions)
95
+ @skipped_guards = [actions].flatten.compact.map(&:to_sym)
96
+ end
97
+
98
+ def force_guards_for(*actions)
99
+ @forced_guards = [actions].flatten.compact.map(&:to_sym)
100
+ end
101
+
102
+ # Returns the current role for the controller.
103
+ def current_role(controller)
104
+ return nil if @role_detector.blank?
105
+
106
+ controller.instance_exec(&@role_detector)&.to_s
107
+ end
108
+
109
+ def access_granted?(controller, action_name) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
+ # Get setting for the current action.
111
+ action = @actions[action_name.to_sym]
112
+
113
+ return true if skip_guards?(action_name)
114
+
115
+ result = authorize_with_lambda(controller, action)
116
+ return result unless result.nil?
117
+
118
+ role = current_role(controller)
119
+
120
+ return false if action&.dig(:denied_roles)&.include?(role)
121
+
122
+ @strategy == :allow || (@strategy == :deny && action&.dig(:permitted_roles)&.include?(role)) || false
123
+ end
124
+
125
+ private
126
+
127
+ def save_permitted_roles!(action_name)
128
+ return if @buffer[:permitted_roles].blank?
129
+
130
+ @actions[action_name][:permitted_roles] = @buffer[:permitted_roles]
131
+ end
132
+
133
+ def save_denied_roles!(action_name)
134
+ return if @buffer[:denied_roles].blank?
135
+
136
+ @actions[action_name][:denied_roles] = @buffer[:denied_roles]
137
+ end
138
+
139
+ def save_authorization_lambda!(action_name)
140
+ return if @buffer[:authorization_lambda].blank?
141
+
142
+ @actions[action_name][:authorization_lambda] = @buffer[:authorization_lambda]
143
+ end
144
+
145
+ def skip_guards?(action_name)
146
+ # If current action is listed in `:except` field of `skip_guards`, then we have to run guards (return false).
147
+ return false if @forced_guards&.include?(action_name.to_sym)
148
+
149
+ # If `skip_guards` has specified an `:except` field, then we can skip guards, because current action has
150
+ # implicitely been marked as "to skip guards".
151
+ return true if @forced_guards.present?
152
+
153
+ # If `skip_guards` don't have the any `except` field, just check if current actions is listed in
154
+ # `skip_guards`.
155
+ return true if @skipped_guards&.include?(action_name.to_sym)
156
+
157
+ # Current action isn't listed in `skip_guards`, so we have to run guards (return false).
158
+ false
159
+ end
160
+
161
+ def authorize_with_lambda(controller, action)
162
+ # When an authorization lambda is defined, it has the priority over the other guards.
163
+ authorization_lambda = action&.dig(:authorization_lambda)
164
+ if authorization_lambda.present?
165
+ result = controller.instance_eval(&authorization_lambda) if authorization_lambda.is_a?(Proc)
166
+ result = controller.instance_eval(authorization_lambda.to_s) if authorization_lambda.is_a?(Symbol)
167
+
168
+ return result
169
+ end
170
+
171
+ nil
172
+ end
173
+ end
174
+ end
175
+ 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
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.2'
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.2
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-01 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