authorizy 0.2.1 → 0.4.1

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