authorizy 0.2.0 → 0.4.0

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: faaf887b6dd9e95abc874df1b5ac534de5dc2dbebda5b2d887472a14a582fa9f
4
- data.tar.gz: 156619514026194dc25299d689aec65ef0cce3ee5500c79090677286cb59dd61
3
+ metadata.gz: d5aa4cb2402214093d2887dcd0760f1087586e28d558c52bfcb4999ff18f72cc
4
+ data.tar.gz: e189e2a283e745c6cb37ddab2683b362d86a4355d776866f031d4cc2c2913079
5
5
  SHA512:
6
- metadata.gz: 9d3e8128355ad5fc09b3bfd8f6719dc8b5ae1a11470de49979e8877f48c2f87752a7990549dc921758499585df4a6615f3941f91863afa65eb25520290577f81
7
- data.tar.gz: 49f47d3b8d83d810a419139fb8c9419b51939bf817c6ec617200382e9951376c2ff545a669548d9f3ad6b44270facb849073ad80b5efa6f9348376b56c448df7
6
+ metadata.gz: bc846ae164fabea516698ef5ddfaec65618a30f2949ec59cc8d93a7b380f240a1a1e25afd53ea6cdfb4f0db7b43cb5a1b26fdd8f87220ef47f9de0d8064b4508
7
+ data.tar.gz: 24e3e907dc28f062932bcc41387f5075ca3865b3406dc80cef0da09439c9db47a52d9c1b3d6ef1871138292f888e43608711e0fc3167a02da5e0d439fcc83091
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # v0.4.0
2
+
3
+ ## Fixes
4
+
5
+ - Returns `403` status code, to represent recognized but not authorized, instead `401`;
6
+
7
+ ## Features
8
+
9
+ - Added `denied` callback allowing a custom acess denied treatment;
10
+
11
+ # v0.3.0
12
+
13
+ ## Features
14
+
15
+ - Added options `field` to customize how the authorizy field is fetched;
16
+
17
+ # v0.2.2
18
+
19
+ ## Fixes
20
+
21
+ - When Cop returns anything different from `true` it is converted to `false`;
22
+
23
+ # v0.2.1
24
+
25
+ ## Fixes
26
+
27
+ - Returns `401` status code when user has no authorization on a XHR request;
28
+
1
29
  # v0.2.0
2
30
 
3
31
  ## Break Changes
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(self), 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: 422) 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.0'
4
+ VERSION = '0.4.0'
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,49 @@
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
+ )
30
+ end
31
+
32
+ it 'renders' do
33
+ allow(context).to receive(:redirect_to)
34
+
35
+ config.denied.call(context)
36
+
37
+ expect(context).to have_received(:redirect_to).with('/', info: 'Action denied for users#index')
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'with custom denied callback' do
43
+ it 'calls the callback' do
44
+ config.denied = ->(context) { context[:key] }
45
+
46
+ expect(config.denied.call(key: :value)).to eq(:value)
47
+ end
48
+ end
49
+ 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(422)
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpec::Matchers, '#be_authorized' do
4
+ it 'pending' do
5
+ matcher = be_authorized('controller', 'action', params: { params: true }, session: { session: true })
6
+
7
+ expect(matcher.description).to eq %(
8
+ be authorized "controller", "action", and {:params=>{:params=>true}, :session=>{:session=>true}}
9
+ ).squish
10
+ end
11
+ end
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.0
4
+ version: 0.4.0
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
@@ -173,6 +175,7 @@ files:
173
175
  - spec/authorizy/expander/expand_spec.rb
174
176
  - spec/authorizy/extension/authorizy_question_spec.rb
175
177
  - spec/authorizy/extension/authorizy_spec.rb
178
+ - spec/authorizy/rspec_spec.rb
176
179
  - spec/common_helper.rb
177
180
  - spec/spec_helper.rb
178
181
  - spec/support/application.rb
@@ -215,7 +218,9 @@ test_files:
215
218
  - spec/authorizy/config/aliases_spec.rb
216
219
  - spec/authorizy/config/cop_spec.rb
217
220
  - spec/authorizy/config/current_user_spec.rb
221
+ - spec/authorizy/config/denied_spec.rb
218
222
  - spec/authorizy/config/dependencies_spec.rb
223
+ - spec/authorizy/config/field_spec.rb
219
224
  - spec/authorizy/config/initialize_spec.rb
220
225
  - spec/authorizy/config/redirect_url_spec.rb
221
226
  - spec/authorizy/cop/controller_spec.rb
@@ -225,6 +230,7 @@ test_files:
225
230
  - spec/authorizy/expander/expand_spec.rb
226
231
  - spec/authorizy/extension/authorizy_question_spec.rb
227
232
  - spec/authorizy/extension/authorizy_spec.rb
233
+ - spec/authorizy/rspec_spec.rb
228
234
  - spec/common_helper.rb
229
235
  - spec/spec_helper.rb
230
236
  - spec/support/application.rb