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 +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +119 -27
- data/lib/authorizy/base_cop.rb +3 -3
- data/lib/authorizy/config.rb +4 -3
- data/lib/authorizy/core.rb +34 -21
- data/lib/authorizy/expander.rb +15 -16
- data/lib/authorizy/extension.rb +11 -4
- data/lib/authorizy/rspec.rb +44 -0
- data/lib/authorizy/version.rb +1 -1
- data/spec/authorizy/base_cop/access_question_spec.rb +2 -1
- data/spec/authorizy/config/aliases_spec.rb +2 -2
- data/spec/authorizy/config/cop_spec.rb +2 -2
- data/spec/authorizy/config/current_user_spec.rb +4 -6
- data/spec/authorizy/config/dependencies_spec.rb +2 -2
- data/spec/authorizy/config/field_spec.rb +29 -0
- data/spec/authorizy/config/initialize_spec.rb +1 -1
- data/spec/authorizy/config/redirect_url_spec.rb +4 -4
- data/spec/authorizy/cop/controller_spec.rb +1 -2
- data/spec/authorizy/cop/model_spec.rb +7 -6
- data/spec/authorizy/cop/namespaced_controller_spec.rb +1 -2
- data/spec/authorizy/core/access_spec.rb +119 -75
- data/spec/authorizy/expander/expand_spec.rb +41 -46
- data/spec/authorizy/extension/authorizy_question_spec.rb +14 -10
- data/spec/authorizy/extension/authorizy_spec.rb +15 -3
- data/spec/authorizy/rspec_spec.rb +11 -0
- data/spec/common_helper.rb +2 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/support/coverage.rb +5 -1
- data/spec/support/models/authorizy_cop.rb +5 -5
- data/spec/support/schema.rb +1 -1
- metadata +73 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bdbf8fe26ec2fa456858b922f21efa63830f329018f07d6c2bed196d7e5cd8a
|
4
|
+
data.tar.gz: 49ad3862405c7707a3ab83c458e8c950d4fcf8b3e6488d7edc6fa6416e912cd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
7
|
-
[![Sponsor](https://img.shields.io/badge/
|
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
|
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
|
-
|
59
|
-
|
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,
|
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
|
+
```
|
data/lib/authorizy/base_cop.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module Authorizy
|
4
4
|
class BaseCop
|
5
|
-
def initialize(current_user, params, session
|
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
|
data/lib/authorizy/config.rb
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
module Authorizy
|
4
4
|
class Config
|
5
|
-
attr_accessor :aliases, :
|
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 = ->
|
10
|
+
@current_user = ->(context) { context.respond_to?(:current_user) ? context.current_user : nil }
|
11
11
|
@dependencies = {}
|
12
|
-
@
|
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
|
data/lib/authorizy/core.rb
CHANGED
@@ -2,42 +2,55 @@
|
|
2
2
|
|
3
3
|
module Authorizy
|
4
4
|
class Core
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
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 @
|
13
|
+
return false if @user.blank?
|
15
14
|
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
end
|
19
|
+
return @cop.public_send(cop_controller) == true if @cop.respond_to?(cop_controller)
|
21
20
|
|
22
|
-
|
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
|
30
|
-
|
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
|
-
|
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
|
38
|
-
Authorizy
|
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
|
data/lib/authorizy/expander.rb
CHANGED
@@ -8,24 +8,27 @@ module Authorizy
|
|
8
8
|
result = {}
|
9
9
|
|
10
10
|
permissions.each do |permission|
|
11
|
-
|
11
|
+
controller = permission[0].to_s
|
12
|
+
action = permission[1].to_s
|
12
13
|
|
13
|
-
result[
|
14
|
+
result["#{controller}##{action}"] = [controller, action]
|
14
15
|
|
15
|
-
if (items = controller_dependency(
|
16
|
-
items.each
|
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[
|
22
|
+
actions = [default_aliases[action]].flatten.compact
|
20
23
|
|
21
24
|
next if actions.blank?
|
22
25
|
|
23
|
-
actions.each do |
|
24
|
-
result[
|
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(
|
38
|
-
return if (actions = dependencies[
|
39
|
-
return if (permissions = actions[
|
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 { |
|
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
|
data/lib/authorizy/extension.rb
CHANGED
@@ -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',
|
13
|
+
info = I18n.t('authorizy.denied', controller: params[:controller], action: params[:action])
|
14
14
|
|
15
|
-
return render(json: { message: info }, status:
|
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
|
-
|
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
|