authorizy 0.1.0 → 0.3.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: d9be49c0763122d6a892671c240a9211720a664a62d66ee457c31203a436c0f8
4
- data.tar.gz: 8c6806a8c63b06c3f750cba5ed61ce512e8520b79d20273711cfdac1b1345188
3
+ metadata.gz: 1bdbf8fe26ec2fa456858b922f21efa63830f329018f07d6c2bed196d7e5cd8a
4
+ data.tar.gz: 49ad3862405c7707a3ab83c458e8c950d4fcf8b3e6488d7edc6fa6416e912cd9
5
5
  SHA512:
6
- metadata.gz: 4aecc0fc9dfb238e9a0b4948bf49e424ae3bdd1ce9d8b5942c8ae66605c259b0f2f80b0800dcf6c383248a76a6a47ead49f42ff4f746523875eb8fb8e3c2a628
7
- data.tar.gz: 62386459e46f79d0d17a21e4229fd69c4915eb762f115adf2a76323590f0c471ef325bfa9f8c44ab6283a20e32dd196692da4958fd9a1f436255baafa83d1248
6
+ metadata.gz: 88568952618d7984a5ec9b548a74adaa64a263d4bfecdae71ec5e28225df8a5a93d5febf6aedb5a27a87f22ced4c066da8b330a5e8c96bc77abc7cea71cbde20
7
+ data.tar.gz: 2be439b99e310b42fca0122ba1a2c7bdf79d932a38be47e09722f6308763f73f25f76ad3481874ac0f7bd3b3e6ea7f33728c1605d372aba71a15c6de8143ffc7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ # v0.3.0
2
+
3
+ ## Features
4
+
5
+ - Added options `field` to customize how the authorizy field is fetched;
6
+
7
+ # v0.2.2
8
+
9
+ ## Fixes
10
+
11
+ - When Cop returns anything different from `true` it is converted to `false`;
12
+
13
+ # v0.2.1
14
+
15
+ ## Fixes
16
+
17
+ - Returns `401` status code when user has no authorization on a XHR request;
18
+
19
+ # v0.2.0
20
+
21
+ ## Break Changes
22
+
23
+ - The permissions format now is:
24
+
25
+ ```
26
+ {
27
+ permissions: [
28
+ ['controller', 'action'],
29
+ ['controller2', 'action2'],
30
+ ]
31
+ }
32
+ ```
33
+
34
+ ## Fixes
35
+
36
+ - Calls the `Authorizy::BaseCop#access?` as the first check intercepting all requests;
37
+
38
+ ## Features
39
+
40
+ - Added RSpec matcher to make the test easier;
41
+
1
42
  # v0.1.0
2
43
 
3
44
  ## Features
data/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  [![CI](https://github.com/wbotelhos/authorizy/workflows/CI/badge.svg)](https://github.com/wbotelhos/authorizy/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/authorizy.svg)](https://badge.fury.io/rb/authorizy)
5
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/f312587b4f126bb13e85/maintainability)](https://codeclimate.com/github/wbotelhos/authorizy/maintainability)
6
- [![Coverage](https://codecov.io/gh/wbotelhos/blogy/branch/master/graph/badge.svg?token=PENDING)](https://codecov.io/gh/wbotelhos/authorizy)
7
- [![Sponsor](https://img.shields.io/badge/donate-%3C3-brightgreen.svg)](https://www.patreon.com/wbotelhos)
6
+ [![Coverage](https://codecov.io/gh/wbotelhos/authorizy/branch/main/graph/badge.svg)](https://codecov.io/gh/wbotelhos/authorizy)
7
+ [![Sponsor](https://img.shields.io/badge/sponsor-%3C3-green)](https://www.patreon.com/wbotelhos)
8
8
 
9
9
  A JSON based Authorization.
10
10
 
@@ -23,7 +23,7 @@ gem 'authorizy'
23
23
  Run the following task to create Authorizy migration and initialize.
24
24
 
25
25
  ```sh
26
- rails g rating:install
26
+ rails g authorizy:install
27
27
  ```
28
28
 
29
29
  Then execute the migration to adds the column `authorizy` to your `users` table.
@@ -55,8 +55,8 @@ The column `authorizy` is a JSON column that has a key called `permission` with
55
55
  ```ruby
56
56
  {
57
57
  permissions: [
58
- { controller: :user, action: :create },
59
- { controller: :user, action: :update },
58
+ [users, :create],
59
+ [users, :update],
60
60
  }
61
61
  }
62
62
  ```
@@ -84,28 +84,9 @@ Authorizy.configure do |config|
84
84
  end
85
85
  ```
86
86
 
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
- { controller: :users, action: :index },
97
- { controller: :enrollments, action: :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
87
  ### Cop
107
88
 
108
- 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, the inherit from `Authorizy::BaseCop`, to allow it based on logic. It works like a [Interceptor](https://en.wikipedia.org/wiki/Interceptor_pattern).
89
+ 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).
109
90
 
110
91
  First, you need to configure your cop:
111
92
 
@@ -130,6 +111,7 @@ end
130
111
  ```
131
112
 
132
113
  As you can see, you have access to a couple of variables: `action`, `controller`, `current_user`, `params`, and `session`.
114
+ When you return `false`, the authorization will be denied, when you return `true` your access will be allowed.
133
115
 
134
116
  If your controller has a namespace, just use `__` to separate the modules name:
135
117
 
@@ -140,13 +122,52 @@ class AuthorizyCop < Authorizy::BaseCop
140
122
  end
141
123
  ```
142
124
 
125
+ If you want to intercept all request as the first Authorizy check, you can override the `access?` method:
126
+
127
+ ```ruby
128
+ class AuthorizyCop < Authorizy::BaseCop
129
+ def access?
130
+ return true if current_user.admin?
131
+ end
132
+ end
133
+ ```
134
+
143
135
  ### Current User
144
136
 
145
137
  By default Authorizy fetch the current user from the variable `current_user`. You have a config, that receives the controller context, where you can change it:
146
138
 
147
139
  ```ruby
148
140
  Authorizy.configure do |config|
149
- config.current_user -> (context) { context.current_person }
141
+ config.current_user = -> (context) { context.current_person }
142
+ end
143
+ ```
144
+
145
+ ### Dependencies
146
+
147
+ 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.
148
+
149
+ ```ruby
150
+ Authorizy.configure do |config|
151
+ config.dependencies = {
152
+ payments: {
153
+ index: [
154
+ ['system/users', :index],
155
+ ['system/enrollments', :index],
156
+ ]
157
+ }
158
+ }
159
+ end
160
+ ```
161
+
162
+ So now if a have the permission `payments#index` I'll receive more two permissions: `users#index` and `enrollments#index`.
163
+
164
+ ### Field
165
+
166
+ By default the permissions are located inside the field called `authorizy` in the configured `current_user`. You can change how this field is fetched:
167
+
168
+ ```ruby
169
+ Authorizy.configure do |config|
170
+ @field = ->(current_user) { current_user.profile.authorizy }
150
171
  end
151
172
  ```
152
173
 
@@ -156,7 +177,7 @@ When authorization fails and the request is not a XHR request a redirect happens
156
177
 
157
178
  ```ruby
158
179
  Authorizy.configure do |config|
159
- config.redirect_url -> (context) { context.new_session_url }
180
+ config.redirect_url = -> (context) { context.new_session_url }
160
181
  end
161
182
  ```
162
183
 
@@ -188,3 +209,74 @@ Using on jBuilder view:
188
209
  ```ruby
189
210
  json.create_link new_users_url if authorizy?(:users, :create)
190
211
  ```
212
+
213
+ # Specs
214
+
215
+ To test some routes you'll need to give or not permission to the user, for that you have to ways, where the first is give permission to the user via session:
216
+
217
+ ```ruby
218
+ before do
219
+ sign_in(current_user)
220
+
221
+ session[:permissions] = [[:users, :create]]
222
+ end
223
+ ```
224
+
225
+ Or you can put the permission directly in the current user:
226
+
227
+ ```ruby
228
+ before do
229
+ sign_in(current_user)
230
+
231
+ current_user.update(permissions: [[:users, :create]])
232
+ end
233
+ ```
234
+
235
+ ## Checks
236
+
237
+ We have a couple of check, here is the order:
238
+
239
+ 1. `Authorizy::BaseCop#access?`;
240
+ 2. `session[:permissions]`;
241
+ 3. `current_user.authorizy['permissions']`;
242
+ 4. `Authorizy::BaseCop#controller_name`;
243
+
244
+ ## Performance
245
+
246
+ If you have few permissions, you can save the permissions in the session and avoid hit database many times, but if you have a couple of them, maybe it's a good idea save it in some place like [Redis](https://redis.io).
247
+
248
+ ## Management
249
+
250
+ It's a good idea you keep your permissions in the database, so the customer can change it dynamic. You can load all permissions when the user is logged and cache it later. For cache expiration, you can trigger a refresh everytime that the permissions change.
251
+
252
+ ## Database Structure
253
+
254
+ Inside database you can use the following relation to dynamicly change your permissions:
255
+
256
+ ```ruby
257
+ plans -> plans_permissions <- permissions
258
+ |
259
+ v
260
+ role_plan_permissions
261
+ ^
262
+ |
263
+ roles
264
+ ```
265
+
266
+ ## RSpec
267
+
268
+ You can test you app passing through all authorizy layers:
269
+
270
+ ```ruby
271
+ user = User.create!(permission: { permissions: [[:users, :create]] })
272
+
273
+ expect(user).to be_authorized(:users, :create)
274
+ ```
275
+
276
+ Or make sure the user does not have access:
277
+
278
+ ```ruby
279
+ user = User.create!(permission: {})
280
+
281
+ expect(user).not_to be_authorized(:users, :create)
282
+ ```
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Authorizy
4
4
  class BaseCop
5
- def initialize(current_user, params, session, controller, action)
6
- @action = action
7
- @controller = controller
5
+ def initialize(current_user, params, session)
6
+ @action = params[:action].to_s
7
+ @controller = params[:controller].to_s
8
8
  @current_user = current_user
9
9
  @params = params
10
10
  @session = session
@@ -2,14 +2,15 @@
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, :dependencies, :field, :redirect_url
6
6
 
7
7
  def initialize
8
8
  @aliases = {}
9
9
  @cop = Authorizy::BaseCop
10
- @current_user = -> (context) { context.respond_to?(:current_user) ? context.current_user : nil }
10
+ @current_user = ->(context) { context.respond_to?(:current_user) ? context.current_user : nil }
11
11
  @dependencies = {}
12
- @redirect_url = -> (context) { context.respond_to?(:root_url) ? context.root_url : '/' }
12
+ @field = ->(current_user) { current_user.respond_to?(:authorizy) ? current_user.authorizy : {} }
13
+ @redirect_url = ->(context) { context.respond_to?(:root_url) ? context.root_url : '/' }
13
14
  end
14
15
  end
15
16
  end
@@ -2,42 +2,55 @@
2
2
 
3
3
  module Authorizy
4
4
  class Core
5
- def initialize(current_user, params, session, controller: params['controller'], action: params['action'])
6
- @action = action.to_s
7
- @controller = controller.to_s
8
- @current_user = current_user
9
- @params = params
10
- @session = session
5
+ def initialize(user, params, session, cop:)
6
+ @cop = cop
7
+ @params = params
8
+ @session = session
9
+ @user = user
11
10
  end
12
11
 
13
12
  def access?
14
- return false if @current_user.blank?
13
+ return false if @user.blank?
15
14
 
16
- granted = permissions.any? do |item|
17
- data = item.stringify_keys
15
+ return true if @cop.access? ||
16
+ session_permissions.any? { |tuple| route_match?(tuple) } ||
17
+ user_permissions.any? { |tuple| route_match?(tuple) }
18
18
 
19
- data['controller'].to_s == @controller && data['action'].to_s == @action
20
- end
19
+ return @cop.public_send(cop_controller) == true if @cop.respond_to?(cop_controller)
21
20
 
22
- return true if granted
23
-
24
- cop.respond_to?(cop_controller) && cop.public_send(cop_controller)
21
+ false
25
22
  end
26
23
 
27
24
  private
28
25
 
29
- def cop
30
- Authorizy.config.cop.new(@current_user, @params, @session, @controller, @action)
26
+ def action
27
+ @params[:action].to_s
28
+ end
29
+
30
+ def controller
31
+ @params[:controller].to_s
31
32
  end
32
33
 
33
34
  def cop_controller
34
- @controller.sub('/', '__')
35
+ controller.sub('/', '__')
36
+ end
37
+
38
+ def expand(permissions)
39
+ return [] if permissions.blank?
40
+
41
+ Authorizy::Expander.new.expand(permissions)
42
+ end
43
+
44
+ def session_permissions
45
+ expand(@session[:permissions])
46
+ end
47
+
48
+ def route_match?(tuple)
49
+ tuple[0] == controller && tuple[1] == action
35
50
  end
36
51
 
37
- def permissions
38
- Authorizy::Expander.new.expand(
39
- [@session['permissions']].flatten.compact.presence || @current_user.authorizy.try(:[], 'permissions')
40
- )
52
+ def user_permissions
53
+ expand(Authorizy.config.field.call(@user).try(:[], 'permissions'))
41
54
  end
42
55
  end
43
56
  end
@@ -8,24 +8,27 @@ module Authorizy
8
8
  result = {}
9
9
 
10
10
  permissions.each do |permission|
11
- item = permission.stringify_keys.transform_values(&:to_s)
11
+ controller = permission[0].to_s
12
+ action = permission[1].to_s
12
13
 
13
- result[key_for(item)] = item
14
+ result["#{controller}##{action}"] = [controller, action]
14
15
 
15
- if (items = controller_dependency(item))
16
- items.each { |data| result[key_for(data)] = data }
16
+ if (items = controller_dependency(controller, action))
17
+ items.each do |controller_item, action_item|
18
+ result["#{controller_item}##{action_item}"] = [controller_item, action_item]
19
+ end
17
20
  end
18
21
 
19
- actions = [default_aliases[item['action']]].flatten.compact
22
+ actions = [default_aliases[action]].flatten.compact
20
23
 
21
24
  next if actions.blank?
22
25
 
23
- actions.each do |action|
24
- result[key_for(item, action: action)] = { 'action' => action.to_s, 'controller' => item['controller'].to_s }
26
+ actions.each do |action_item|
27
+ result["#{controller}##{action_item}"] = [controller, action_item.to_s]
25
28
  end
26
29
  end
27
30
 
28
- result.values
31
+ result.values # TODO: garantir o uniq
29
32
  end
30
33
 
31
34
  private
@@ -34,11 +37,11 @@ module Authorizy
34
37
  Authorizy.config.aliases.stringify_keys
35
38
  end
36
39
 
37
- def controller_dependency(item)
38
- return if (actions = dependencies[item['controller']]).blank?
39
- return if (permissions = actions[item['action']]).blank?
40
+ def controller_dependency(controller, action)
41
+ return if (actions = dependencies[controller]).blank?
42
+ return if (permissions = actions[action]).blank?
40
43
 
41
- permissions.map { |permission| permission.transform_values(&:to_s) }
44
+ permissions.map { |c, a| [c.to_s, a.to_s] }
42
45
  end
43
46
 
44
47
  def default_aliases
@@ -53,9 +56,5 @@ module Authorizy
53
56
  def dependencies
54
57
  Authorizy.config.dependencies.deep_stringify_keys
55
58
  end
56
-
57
- def key_for(item, action: nil)
58
- "#{item['controller']}##{action || item['action']}"
59
- end
60
59
  end
61
60
  end
@@ -8,17 +8,20 @@ module Authorizy
8
8
  helper_method(:authorizy?)
9
9
 
10
10
  def authorizy
11
- return if Authorizy::Core.new(authorizy_user, params, session).access?
11
+ return if Authorizy::Core.new(authorizy_user, params, session, cop: authorizy_cop).access?
12
12
 
13
- info = I18n.t('authorizy.denied', action: params[:action], controller: params[:controller])
13
+ info = I18n.t('authorizy.denied', controller: params[:controller], action: params[:action])
14
14
 
15
- return render(json: { message: info }, status: 422) if request.xhr?
15
+ return render(json: { message: info }, status: 401) if request.xhr?
16
16
 
17
17
  redirect_to Authorizy.config.redirect_url.call(self), info: info
18
18
  end
19
19
 
20
20
  def authorizy?(controller, action)
21
- Authorizy::Core.new(authorizy_user, params, session, action: action, controller: controller).access?
21
+ params['controller'] = controller
22
+ params['action'] = action
23
+
24
+ Authorizy::Core.new(authorizy_user, params, session, cop: authorizy_cop).access?
22
25
  end
23
26
 
24
27
  private
@@ -26,6 +29,10 @@ module Authorizy
26
29
  def authorizy_user
27
30
  Authorizy.config.current_user.call(self)
28
31
  end
32
+
33
+ def authorizy_cop
34
+ Authorizy.config.cop.new(authorizy_user, params, session)
35
+ end
29
36
  end
30
37
  end
31
38
  end