authorizy 0.2.1 → 0.4.1

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: d3025788b5adf8a466ed73bfc45f709612ad1b05de0e5e887a84c67c31cd5074
4
- data.tar.gz: 354c47240e842ecdd4afa112e95e02ef094f94dfcd17e9431d7dbe97ee580283
3
+ metadata.gz: adf5a52d89eabb0dd6503d0f96fb44ae9d4268213a6da8b91fb758805db97371
4
+ data.tar.gz: 01bcedd187623c4364c38d0c4f53b5a12ee1877f4426df44b21cc3b7446f6b5d
5
5
  SHA512:
6
- metadata.gz: a8a73b41a1b8ea247cd59114d9ddf857f7727efa4c780fd5b6fe17a8805aa86528061e7fa49ec2f32c9fe4a80bb0d05a70341d8176af302cb6e31da4bdce9403
7
- data.tar.gz: d9c32ae7a0b4a81742d3caa9879c69b7949cc86ecf104b4e79f7e642fa7b92efb5b4047993a38f2136e8b226dd5119046f5e5faed85134e5f249513c3fed72dc
6
+ metadata.gz: 0623f322536c3a6de17848f3f6b3642d70041384e220d0417d028a045d59970c97dfa84e659dd6a95795fe30f2ed09bb7d6203cf0bc7b600fbea0e53ff63bec5
7
+ data.tar.gz: 4e4862f37eb92ef4c0eb247ecd0fa0abe0652d6d750b282bb20908fadaf3dbaf54738ed51167cfa5784e1d158a441d349c75f9a460981c800def31df30b1d002
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # v0.4.1
2
+
3
+ ## Fixes
4
+
5
+ - `redirect_url` was receiving the `denied` context instead of the controller's context;
6
+
7
+ # v0.4.0
8
+
9
+ ## Fixes
10
+
11
+ - Returns `403` status code, to represent recognized but not authorized, instead `401`;
12
+
13
+ ## Features
14
+
15
+ - Added `denied` callback allowing a custom acess denied treatment;
16
+
17
+ # v0.3.0
18
+
19
+ ## Features
20
+
21
+ - Added options `field` to customize how the authorizy field is fetched;
22
+
23
+ # v0.2.2
24
+
25
+ ## Fixes
26
+
27
+ - When Cop returns anything different from `true` it is converted to `false`;
28
+
1
29
  # v0.2.1
2
30
 
3
31
  ## Fixes
data/README.md CHANGED
@@ -8,10 +8,6 @@
8
8
 
9
9
  A JSON based Authorization.
10
10
 
11
- ##### Why not [cancancan](https://github.com/CanCanCommunity/cancancan)?
12
-
13
- I have been working with cancan/cancancan for years. Since the beginning with [database access](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Abilities-in-Database.md). After a while, I realised I built a couple of abstractions around `ability` class and suddenly migrated to JSON for better performance. As I need a full role admin I decided to start to extract this logic to a gem.
14
-
15
11
  ## Install
16
12
 
17
13
  Add the following code on your `Gemfile` and run `bundle install`:
@@ -84,25 +80,6 @@ Authorizy.configure do |config|
84
80
  end
85
81
  ```
86
82
 
87
- ### Dependencies
88
-
89
- You can allow access to one or more controllers and actions based on your permissions. It'll consider not only the `action`, like [aliases](#aliases) but the controller either.
90
-
91
- ```ruby
92
- Authorizy.configure do |config|
93
- config.dependencies = {
94
- payments: {
95
- index: [
96
- ['system/users', :index],
97
- ['system/enrollments', :index],
98
- ]
99
- }
100
- }
101
- end
102
- ```
103
-
104
- So now if a have the permission `payments#index` I'll receive more two permissions: `users#index` and `enrollments#index`.
105
-
106
83
  ### Cop
107
84
 
108
85
  Sometimes we need to allow access in runtime because the permission will depend on the request data and/or some dynamic logic. For this you can create a *Cop* class, that inherits from `Authorizy::BaseCop`, to allow it based on logic. It works like a [Interceptor](https://en.wikipedia.org/wiki/Interceptor_pattern).
@@ -161,6 +138,43 @@ Authorizy.configure do |config|
161
138
  end
162
139
  ```
163
140
 
141
+ ### Denied
142
+
143
+ When some access is denied, by default, Authorizy checks if it is a XHR request or not and then redirect or serializes a message with status code `403`. You can rescue it by yourself:
144
+
145
+ ```ruby
146
+ config.denied = ->(context) { context.redirect_to(subscription_path, info: 'Subscription expired!') }
147
+ ```
148
+
149
+ ### Dependencies
150
+
151
+ You can allow access to one or more controllers and actions based on your permissions. It'll consider not only the `action`, like [aliases](#aliases) but the controller either.
152
+
153
+ ```ruby
154
+ Authorizy.configure do |config|
155
+ config.dependencies = {
156
+ payments: {
157
+ index: [
158
+ ['system/users', :index],
159
+ ['system/enrollments', :index],
160
+ ]
161
+ }
162
+ }
163
+ end
164
+ ```
165
+
166
+ So now if a have the permission `payments#index` I'll receive more two permissions: `users#index` and `enrollments#index`.
167
+
168
+ ### Field
169
+
170
+ By default the permissions are located inside the field called `authorizy` in the configured `current_user`. You can change how this field is fetched:
171
+
172
+ ```ruby
173
+ Authorizy.configure do |config|
174
+ @field = ->(current_user) { current_user.profile.authorizy }
175
+ end
176
+ ```
177
+
164
178
  ### Redirect URL
165
179
 
166
180
  When authorization fails and the request is not a XHR request a redirect happens to `/` path. You can change it:
@@ -2,13 +2,23 @@
2
2
 
3
3
  module Authorizy
4
4
  class Config
5
- attr_accessor :aliases, :dependencies, :cop, :current_user, :redirect_url
5
+ attr_accessor :aliases, :cop, :current_user, :denied, :dependencies, :field, :redirect_url
6
6
 
7
7
  def initialize
8
8
  @aliases = {}
9
9
  @cop = Authorizy::BaseCop
10
10
  @current_user = ->(context) { context.respond_to?(:current_user) ? context.current_user : nil }
11
+
12
+ @denied = lambda { |context|
13
+ info = I18n.t('authorizy.denied', controller: context.params[:controller], action: context.params[:action])
14
+
15
+ return context.render(json: { message: info }, status: 403) if context.request.xhr?
16
+
17
+ context.redirect_to(redirect_url.call(context), info: info)
18
+ }
19
+
11
20
  @dependencies = {}
21
+ @field = ->(current_user) { current_user.respond_to?(:authorizy) ? current_user.authorizy : {} }
12
22
  @redirect_url = ->(context) { context.respond_to?(:root_url) ? context.root_url : '/' }
13
23
  end
14
24
  end
@@ -16,7 +16,7 @@ module Authorizy
16
16
  session_permissions.any? { |tuple| route_match?(tuple) } ||
17
17
  user_permissions.any? { |tuple| route_match?(tuple) }
18
18
 
19
- return @cop.public_send(cop_controller) if @cop.respond_to?(cop_controller)
19
+ return @cop.public_send(cop_controller) == true if @cop.respond_to?(cop_controller)
20
20
 
21
21
  false
22
22
  end
@@ -50,7 +50,7 @@ module Authorizy
50
50
  end
51
51
 
52
52
  def user_permissions
53
- expand(@user.authorizy.try(:[], 'permissions'))
53
+ expand(Authorizy.config.field.call(@user).try(:[], 'permissions'))
54
54
  end
55
55
  end
56
56
  end
@@ -8,38 +8,26 @@ module Authorizy
8
8
  helper_method(:authorizy?)
9
9
 
10
10
  def authorizy
11
- return if authorizy_core.new(authorizy_user, params, session, cop: authorizy_cop).access?
11
+ return if Authorizy::Core.new(authorizy_user, params, session, cop: authorizy_cop).access?
12
12
 
13
- info = I18n.t('authorizy.denied', controller: params[:controller], action: params[:action])
14
-
15
- return render(json: { message: info }, status: 401) if request.xhr?
16
-
17
- redirect_to authorizy_config.redirect_url.call(self), info: info
13
+ Authorizy.config.denied.call(self)
18
14
  end
19
15
 
20
16
  def authorizy?(controller, action)
21
17
  params['controller'] = controller
22
18
  params['action'] = action
23
19
 
24
- authorizy_core.new(authorizy_user, params, session, cop: authorizy_cop).access?
20
+ Authorizy::Core.new(authorizy_user, params, session, cop: authorizy_cop).access?
25
21
  end
26
22
 
27
23
  private
28
24
 
29
- def authorizy_core
30
- Authorizy::Core
31
- end
32
-
33
25
  def authorizy_user
34
- authorizy_config.current_user.call(self)
35
- end
36
-
37
- def authorizy_config
38
- Authorizy.config
26
+ Authorizy.config.current_user.call(self)
39
27
  end
40
28
 
41
29
  def authorizy_cop
42
- authorizy_config.cop.new(authorizy_user, params, session)
30
+ Authorizy.config.cop.new(authorizy_user, params, session)
43
31
  end
44
32
  end
45
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Authorizy
4
- VERSION = '0.2.1'
4
+ VERSION = '0.4.1'
5
5
  end
@@ -9,14 +9,28 @@ Authorizy.configure do |config|
9
9
  # https://github.com/wbotelhos/authorizy#cop
10
10
  # config.cop = Authorizy::BaseCop
11
11
 
12
- # The current user from we fetch the permissions
12
+ # The current user from where we fetch the permissions
13
13
  # https://github.com/wbotelhos/authorizy#current-user
14
14
  # config.current_user = -> (context) { context.respond_to?(:current_user) ? context.current_user : nil }
15
15
 
16
+ # Callback called when access is denied
17
+ # https://github.com/wbotelhos/authorizy#denied
18
+ # config.denied = lambda { |context|
19
+ # info = I18n.t('authorizy.denied', controller: context.params[:controller], action: context.params[:action])
20
+
21
+ # return context.render(json: { message: info }, status: 403) if context.request.xhr?
22
+
23
+ # context.redirect_to(redirect_url.call(self), info: info)
24
+ # }
25
+
16
26
  # Inherited permissions from some other permission the user already has
17
27
  # https://github.com/wbotelhos/authorizy#dependencies
18
28
  # config.dependencies = {}
19
29
 
30
+ # Field used to fetch the Authorizy permissions
31
+ # https://github.com/wbotelhos/authorizy#field
32
+ # config.field = ->(current_user) { current_user.respond_to?(:authorizy) ? current_user.authorizy : {} }
33
+
20
34
  # URL to be redirect when user has no permission to access some resource
21
35
  # https://github.com/wbotelhos/authorizy#dependencies
22
36
  # config.redirect_url = -> (context) { context.respond_to?(:root_url) ? context.root_url : '/' }
@@ -15,9 +15,7 @@ RSpec.describe Authorizy::Config, '#current_user' do
15
15
  context 'when context does not respond to current_user' do
16
16
  let!(:context) { 'context' }
17
17
 
18
- it 'returns nil' do
19
- expect(config.current_user.call(context)).to be(nil)
20
- end
18
+ it { expect(config.current_user.call(context)).to be(nil) }
21
19
  end
22
20
  end
23
21
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#denied' do
4
+ let!(:config) { described_class.new }
5
+
6
+ context 'with default denied callback' do
7
+ context 'when is a xhr request' do
8
+ let!(:context) do
9
+ double('context',
10
+ params: { controller: 'users', action: 'index' },
11
+ request: OpenStruct.new(xhr?: true)
12
+ )
13
+ end
14
+
15
+ it 'renders' do
16
+ allow(context).to receive(:render)
17
+
18
+ config.denied.call(context)
19
+
20
+ expect(context).to have_received(:render).with(json: { message: 'Action denied for users#index' }, status: 403)
21
+ end
22
+ end
23
+
24
+ context 'when is not a xhr request' do
25
+ let!(:context) do
26
+ double('context',
27
+ params: { controller: 'users', action: 'index' },
28
+ request: OpenStruct.new(xhr?: false),
29
+ root_url: 'root_url'
30
+ )
31
+ end
32
+
33
+ it 'redirects' do
34
+ allow(context).to receive(:redirect_to)
35
+ allow(context).to receive(:respond_to?).with(:root_url).and_return(true)
36
+
37
+ config.denied.call(context)
38
+
39
+ expect(context).to have_received(:redirect_to).with('root_url', info: 'Action denied for users#index')
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'with custom denied callback' do
45
+ it 'calls the callback' do
46
+ config.denied = ->(context) { context[:key] }
47
+
48
+ expect(config.denied.call(key: :value)).to eq(:value)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#field' do
4
+ let!(:config) { described_class.new }
5
+
6
+ context 'when uses default value' do
7
+ context 'when current_user responds to authorizy' do
8
+ let!(:current_user) { OpenStruct.new(authorizy: { permissions: [%i[users index]] }) }
9
+
10
+ it 'is called' do
11
+ expect(config.field.call(current_user)).to eq(permissions: [%i[users index]])
12
+ end
13
+ end
14
+
15
+ context 'when current_user does not respond to field' do
16
+ let!(:current_user) { nil }
17
+
18
+ it { expect(config.field.call(current_user)).to eq({}) }
19
+ end
20
+ end
21
+
22
+ context 'when uses custom value' do
23
+ it 'executes what you want' do
24
+ config.field = ->(current_user) { current_user[:value] }
25
+
26
+ expect(config.field.call({ value: 'value' })).to eq('value')
27
+ end
28
+ end
29
+ end
@@ -23,9 +23,9 @@ RSpec.describe Authorizy::Config, '#redirect_url' do
23
23
 
24
24
  context 'when uses custom value' do
25
25
  it 'executes what you want' do
26
- config.redirect_url = ->(context) { context[:value] }
26
+ config.redirect_url = ->(context) { context[:key] }
27
27
 
28
- expect(config.redirect_url.call({ value: 'value' })).to eq('value')
28
+ expect(config.redirect_url.call({ key: :value })).to eq(:value)
29
29
  end
30
30
  end
31
31
  end
@@ -85,6 +85,76 @@ RSpec.describe Authorizy::Core, '#access?' do
85
85
  expect(described_class.new(current_user, params, session, cop: cop).access?).to be(true)
86
86
  end
87
87
  end
88
+
89
+ context 'when cop return nil' do
90
+ let!(:cop) do
91
+ Class.new(Authorizy::BaseCop) do
92
+ def access?
93
+ false
94
+ end
95
+
96
+ def admin__controller
97
+ nil
98
+ end
99
+ end.new(current_user, params, session)
100
+ end
101
+
102
+ it 'is converted to false' do
103
+ expect(described_class.new(current_user, params, session, cop: cop).access?).to be(false)
104
+ end
105
+ end
106
+
107
+ context 'when cop return empty' do
108
+ let!(:cop) do
109
+ Class.new(Authorizy::BaseCop) do
110
+ def access?
111
+ false
112
+ end
113
+
114
+ def admin__controller
115
+ ''
116
+ end
117
+ end.new(current_user, params, session)
118
+ end
119
+
120
+ it 'is converted to false' do
121
+ expect(described_class.new(current_user, params, session, cop: cop).access?).to be(false)
122
+ end
123
+ end
124
+
125
+ context 'when cop return nothing' do
126
+ let!(:cop) do
127
+ Class.new(Authorizy::BaseCop) do
128
+ def access?
129
+ false
130
+ end
131
+
132
+ def admin__controller; end
133
+ end.new(current_user, params, session)
134
+ end
135
+
136
+ it 'is converted to false' do
137
+ expect(described_class.new(current_user, params, session, cop: cop).access?).to be(false)
138
+ end
139
+ end
140
+
141
+ context 'when cop return true as string' do
142
+ let!(:cop) do
143
+ Class.new(Authorizy::BaseCop) do
144
+ def access?
145
+ false
146
+ end
147
+
148
+ def admin__controller
149
+ 'true'
150
+ end
151
+ end.new(current_user, params, session)
152
+ end
153
+
154
+ it 'is converted to false' do
155
+ expect(described_class.new(current_user, params, session, cop: cop).access?).to be(false)
156
+ end
157
+ end
88
158
  end
89
159
 
90
160
  context 'when user has the controller permission but not action' do
@@ -3,18 +3,15 @@
3
3
  require 'support/controllers/dummy_controller'
4
4
 
5
5
  RSpec.describe DummyController, '#authorizy', type: :controller do
6
- let!(:config) { Authorizy.config }
7
6
  let!(:parameters) { ActionController::Parameters.new(key: 'value', controller: 'dummy', action: 'action') }
8
7
  let!(:user) { nil }
9
8
 
10
- before { allow(Authorizy).to receive(:config).and_return(config) }
11
-
12
9
  context 'when user has access' do
13
10
  let!(:authorizy_core) { instance_double('Authorizy::Core', access?: true) }
14
11
 
15
12
  before do
16
13
  allow(Authorizy::Core).to receive(:new)
17
- .with(user, parameters, session, cop: config.cop)
14
+ .with(user, parameters, session, cop: Authorizy.config.cop)
18
15
  .and_return(authorizy_core)
19
16
  end
20
17
 
@@ -42,27 +39,16 @@ RSpec.describe DummyController, '#authorizy', type: :controller do
42
39
 
43
40
  before do
44
41
  allow(Authorizy::Core).to receive(:new)
45
- .with(user, parameters, session, cop: config.cop)
42
+ .with(user, parameters, session, cop: Authorizy.config.cop)
46
43
  .and_return(authorizy_core)
47
44
  end
48
45
 
49
- context 'when is a xhr request' do
50
- it 'receives the default values and denied the access' do
51
- get :action, xhr: true, params: { key: 'value' }
46
+ it 'calls denied callback' do
47
+ allow(Authorizy.config.denied).to receive(:call)
52
48
 
53
- expect(response.body).to eq('{"message":"Action denied for dummy#action"}')
54
- expect(response.status).to be(401)
55
- end
56
- end
49
+ get :action, xhr: true, params: { key: 'value' }
57
50
 
58
- context 'when is a html request' do
59
- it 'receives the default values and do not denied the access' do
60
- get :action, params: { key: 'value' }
61
-
62
- expect(response).to redirect_to '/'
63
-
64
- # expect(flash[:info]).to eq('Action denied for dummy#action') # TODO: get flash message
65
- end
51
+ expect(Authorizy.config.denied).to have_received(:call).with(subject)
66
52
  end
67
53
  end
68
54
  end
@@ -9,23 +9,3 @@ RSpec.describe RSpec::Matchers, '#be_authorized' do
9
9
  ).squish
10
10
  end
11
11
  end
12
-
13
- # matcher.actual
14
- # matcher.actual_formatted
15
- # matcher.and
16
- # matcher.description
17
- # matcher.diffable?
18
- # matcher.does_not_match?
19
- # matcher.expected
20
- # matcher.expected_formatted
21
- # matcher.expects_call_stack_jump?
22
- # matcher.failure_message
23
- # matcher.failure_message_when_negated
24
- # matcher.match_unless_raises
25
- # matcher.matcher_name
26
- # matcher.matcher_name=
27
- # matcher.matches?
28
- # matcher.or
29
- # matcher.present_ivars
30
- # matcher.rescued_exception
31
- # matcher.supports_block_expectations?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authorizy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Washington Botelho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-05 00:00:00.000000000 Z
11
+ date: 2021-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -163,7 +163,9 @@ files:
163
163
  - spec/authorizy/config/aliases_spec.rb
164
164
  - spec/authorizy/config/cop_spec.rb
165
165
  - spec/authorizy/config/current_user_spec.rb
166
+ - spec/authorizy/config/denied_spec.rb
166
167
  - spec/authorizy/config/dependencies_spec.rb
168
+ - spec/authorizy/config/field_spec.rb
167
169
  - spec/authorizy/config/initialize_spec.rb
168
170
  - spec/authorizy/config/redirect_url_spec.rb
169
171
  - spec/authorizy/cop/controller_spec.rb
@@ -216,7 +218,9 @@ test_files:
216
218
  - spec/authorizy/config/aliases_spec.rb
217
219
  - spec/authorizy/config/cop_spec.rb
218
220
  - spec/authorizy/config/current_user_spec.rb
221
+ - spec/authorizy/config/denied_spec.rb
219
222
  - spec/authorizy/config/dependencies_spec.rb
223
+ - spec/authorizy/config/field_spec.rb
220
224
  - spec/authorizy/config/initialize_spec.rb
221
225
  - spec/authorizy/config/redirect_url_spec.rb
222
226
  - spec/authorizy/cop/controller_spec.rb