pundit_roles 0.2.1 → 0.5.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 +8 -2
- data/README.md +133 -34
- data/lib/pundit_roles/policy/base.rb +99 -39
- data/lib/pundit_roles/policy/role.rb +8 -6
- data/lib/pundit_roles/pundit.rb +32 -8
- data/lib/pundit_roles/version.rb +1 -1
- data/pundit_roles.gemspec +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d24862dda5081913834ff5fc0063cc46c98e81c0
|
4
|
+
data.tar.gz: 8a426b027d8cf4889d4b11c466e11e6492dfa5fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41103de643378f38f4f55306f39010301a15ed09d4bc89c7effe183a29c011f0293fbbc478e4e0e925fab4f4e9d51647622d4ae4c66fd502798902b8b1817bea
|
7
|
+
data.tar.gz: 46fa3a352eb53d5d7a098d990255c96a4572793c0a20df4f3f6cb7b7712a9c9eb945670908e0fd6aadea2e6d9655e01c724f0f9eb1b5684e6d5e5d8b7c975e9a
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# PunditRoles
|
2
2
|
|
3
|
-
## 0.2.
|
3
|
+
## 0.2.1 (2017-10-30)
|
4
4
|
|
5
5
|
- Roles and permitted options are no longer separately declared with `role` and
|
6
6
|
`permitted_for` methods. Declaration of options has been consolidated into the
|
7
7
|
`role` method.
|
8
8
|
- Roles can no longer be inherited from the superclass.
|
9
9
|
- Test conditions for the roles are now guessed from the name of the role,
|
10
|
-
instead of being declared explicitly with the `authorize_with` option.
|
10
|
+
instead of being declared explicitly with the `authorize_with` option.
|
11
|
+
- Policy instance variable @record renamed @resource
|
12
|
+
|
13
|
+
## 0.5.0 (2017-11-08)
|
14
|
+
|
15
|
+
- `authorize` method has been renamed to `authorize!`
|
16
|
+
- added support for limiting scopes, can be called with `policy_scope!`
|
data/README.md
CHANGED
@@ -7,15 +7,15 @@
|
|
7
7
|
|
8
8
|
PunditRoles is a helper gem which works on top of [Pundit](https://github.com/elabs/pundit)
|
9
9
|
(if you are not familiar with Pundit, it is recommended you read it's documentation before continuing).
|
10
|
-
It allows you to extend Pundit's authorization system to include attributes and associations
|
10
|
+
It allows you to extend Pundit's authorization system to include attributes and associations, and provides a couple of
|
11
|
+
helpers for convenience.
|
11
12
|
|
12
13
|
If you are already using Pundit, this should not conflict with any of Pundit's existing functionality.
|
13
|
-
You may use Pundit's features as well as the features from this gem interchangeably.
|
14
|
+
You may use Pundit's features as well as the features from this gem interchangeably. There are
|
15
|
+
some caveats however, see the [Porting over from Pundit](#Porting over from Pundit).
|
14
16
|
|
15
17
|
Please note that this gem is not affiliated with Pundit or it's creators, but it very much
|
16
|
-
appreciates the work that they did with their great authorization system.
|
17
|
-
|
18
|
-
* **Important**: This gem is **not** yet considered production ready.
|
18
|
+
appreciates the work that they did with their great authorization system.
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
|
@@ -44,8 +44,7 @@ end
|
|
44
44
|
PunditRoles operates around the notion of _**roles**_. Each role needs to be defined at the Policy level
|
45
45
|
and provided with a conditional method that determines whether the `@user`(the `current_user` in the context of a Policy)
|
46
46
|
falls into this role. Additionally, each role can have a set of permitted
|
47
|
-
_**attributes**_ and _**associations**_
|
48
|
-
defined for it. A basic example for a UserPolicy would be:
|
47
|
+
_**attributes**_ and _**associations**_ defined for it. A basic example for a UserPolicy would be:
|
49
48
|
```ruby
|
50
49
|
class UserPolicy < ApplicationPolicy
|
51
50
|
role :regular_user,
|
@@ -71,7 +70,8 @@ end
|
|
71
70
|
This assumes that there are two methods defined in the UserPolicy called `regular_user?` and
|
72
71
|
`correct_user?`.
|
73
72
|
|
74
|
-
* Please note, that there
|
73
|
+
* Please note, that there were a couple of breaking change since `0.2.1`. View the
|
74
|
+
[changelog](https://github.com/StairwayB/pundit_roles/blob/master/CHANGELOG.md) for additional details.
|
75
75
|
|
76
76
|
And then in you query method, you simply say:
|
77
77
|
```ruby
|
@@ -90,19 +90,19 @@ def show?
|
|
90
90
|
end
|
91
91
|
```
|
92
92
|
|
93
|
-
Finally, in your controller you call
|
93
|
+
Finally, in your controller you call `authorize!` method and pass it's return value
|
94
94
|
to a variable:
|
95
95
|
```ruby
|
96
96
|
class UserController < ApplicationController
|
97
97
|
def show
|
98
98
|
@user = User.find(params[:id])
|
99
|
-
permitted = authorize @user
|
99
|
+
permitted = authorize! @user
|
100
100
|
# [...]
|
101
101
|
end
|
102
102
|
end
|
103
103
|
```
|
104
104
|
|
105
|
-
The `authorize
|
105
|
+
The `authorize!` method will return a hash of permitted attributes and associations for the corresponding action that the
|
106
106
|
user has access to. What you do with that is your business. Accessors for each segment look like this:
|
107
107
|
```ruby
|
108
108
|
permitted[:attributes][:show] # ex. returns => [:username, :name, :avatar, :is_confirmed, :created_at]
|
@@ -112,7 +112,7 @@ permitted[:associations][:show]
|
|
112
112
|
permitted[:associations][:update]
|
113
113
|
```
|
114
114
|
|
115
|
-
|
115
|
+
The hash also contains the roles that the user has fulfilled:
|
116
116
|
```ruby
|
117
117
|
permitted[:roles] # ex. returns => [:regular_user, :correct_user]
|
118
118
|
```
|
@@ -122,37 +122,42 @@ If the user does not fall into any roles permitted by a query, the `authorize` m
|
|
122
122
|
### Defining roles
|
123
123
|
|
124
124
|
Roles are defined with the `role` method. It receives the name of the role as it's first argument and the
|
125
|
-
options for the role as it's second
|
126
|
-
|
127
|
-
|
125
|
+
options for the role as it's second. Additionally, you need to define a method which checks if
|
126
|
+
the user falls into that role. This method's name must be the name of the role with a question
|
127
|
+
mark at the end. For example, a `:correct_user` role's conditional method must be declared as
|
128
|
+
`correct_user?`.
|
128
129
|
|
129
|
-
|
130
|
+
Valid options for roles are:
|
131
|
+
`:attributes, :associations, :scope`
|
130
132
|
|
131
133
|
```ruby
|
132
|
-
role :
|
134
|
+
role :correct_user,
|
133
135
|
attributes: {show: [:name]},
|
134
136
|
associations: {show: [:posts]}
|
135
137
|
|
136
138
|
private
|
137
139
|
|
138
|
-
def
|
139
|
-
@user.
|
140
|
+
def correct_user?
|
141
|
+
@user.id == @resource.id
|
140
142
|
end
|
141
143
|
```
|
142
144
|
|
143
145
|
One thing to watch out for is that roles are not inherited, because each is unique to the model in question.
|
144
146
|
But since the name of the role is just the conditional method for the role,
|
145
147
|
without the '?' question mark, it is encouraged to inherit from an `ApplicationPolicy`,
|
146
|
-
and define common `role`
|
148
|
+
and define common `role` conditionals there.
|
149
|
+
|
150
|
+
* see [Declaring attributes and associations](#Declaring attributes and associations) for how to declare
|
151
|
+
attributes and associations.
|
147
152
|
|
148
153
|
### Users with multiple roles
|
149
154
|
|
150
|
-
You may have noticed that in the first example `correct_user` has fewer permitted
|
155
|
+
You may have noticed that in the first example `correct_user` has fewer permitted attributes and associations
|
151
156
|
defined than `regular_user`. That is because PunditRoles does not treat roles as exclusionary.
|
152
157
|
Users may have a single role or they may have multiple roles, within the context of the model they are trying to access.
|
153
158
|
In the previous example, a `correct_user`, meaning a `regular_user` trying to access it's own model, is naturally
|
154
|
-
also a `regular_user`, so it will have access to all
|
155
|
-
|
159
|
+
also a `regular_user`, so it will have access to all attributes and associations a `regular_user` has access to plus the
|
160
|
+
ones that a `correct_user` has access to.
|
156
161
|
|
157
162
|
Take this example, to better illustrate what is happening:
|
158
163
|
|
@@ -174,7 +179,7 @@ role :admin_user,
|
|
174
179
|
```
|
175
180
|
|
176
181
|
Here, a user which fulfills the `admin_user` condition trying to access it's own model, would receive the
|
177
|
-
|
182
|
+
attributes and associations of all three roles, without any duplicates, meaning the `permitted[:attributes][:show]` would look like:
|
178
183
|
```ruby
|
179
184
|
[:username, :name, :avatar, :email, :phone_number, :is_admin]
|
180
185
|
```
|
@@ -217,16 +222,106 @@ end
|
|
217
222
|
with other roles. It is also the first role that is evaluated, and if the user is a `:guest`, it will return the guest
|
218
223
|
attributes if `:guest` is allowed, or raise `Pundit::NotAuthorizedError` if not.
|
219
224
|
|
220
|
-
* Do **not** use a custom role for `nil`
|
221
|
-
If you do, it will most likely lead to unwanted
|
225
|
+
* Do **not** use a custom role for `nil` users, use `:guest`.
|
226
|
+
If you do, it will most likely lead to unwanted errors.
|
227
|
+
|
228
|
+
### Scopes
|
229
|
+
PunditRoles supports limiting scopes for actions which return a list of records. If you wish to do
|
230
|
+
this, define a scope option for a role as a `lambda`, and then call `policy_scope!` for the list you want to
|
231
|
+
limit. It should look something like this:
|
232
|
+
```ruby
|
233
|
+
role :guest,
|
234
|
+
attributes: {
|
235
|
+
show: %i(name avatar),
|
236
|
+
},
|
237
|
+
associations: {},
|
238
|
+
scope: lambda{resource.where(visible_publicly: true)}
|
239
|
+
role :regular_user,
|
240
|
+
attributes: {
|
241
|
+
show: %i(username name avatar)
|
242
|
+
},
|
243
|
+
associations: {
|
244
|
+
show: %i(posts followers following)
|
245
|
+
},
|
246
|
+
scope: lambda{resource.where.not(id: user.id)}
|
247
|
+
|
248
|
+
def index?
|
249
|
+
allow :guest, :regular_user
|
250
|
+
end
|
251
|
+
```
|
252
|
+
Then in your controller you pass the list you want to limit based on what role the current user fulfills:
|
253
|
+
```ruby
|
254
|
+
def index
|
255
|
+
@users = policy_scope!(User.all)
|
256
|
+
end
|
257
|
+
```
|
258
|
+
The `policy_scope!` method returns the scope for the role, or raises `Pundit::NotAuthorizedError` if the user is not
|
259
|
+
allowed to perform the action. Since the syntax for permitting scopes is the same as the syntax for getting the permitted
|
260
|
+
attributes and associations, you may use both `authorize!` and `policy_scope!` for the same action. A recommended usage is
|
261
|
+
to use both(this example uses the excellent [jsonapi-rails](https://github.com/jsonapi-rb/jsonapi-rails) gem for serialization):
|
262
|
+
```ruby
|
263
|
+
def index
|
264
|
+
@users = policy_scope!(User.all)
|
265
|
+
permitted = authorize! @users
|
266
|
+
render jsonapi: @users, fields: {users: permitted[:attributes][:show]}
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
#### Important: Scope declaration order
|
271
|
+
|
272
|
+
While attributes and associations for roles are merged, scopes are **not**! This means that whenever you wish to authorize a list of records,
|
273
|
+
you must take care in what order you define the roles. PunditRoles will go over the allowed roles in a query method in the
|
274
|
+
order in which they were defined, and when it finds a role that the user fulfills, it will return the scope for that role.
|
275
|
+
|
276
|
+
Take this example, where there are two roles permitted for an `index` action: `regular_user` and `:admin_user`:
|
277
|
+
```ruby
|
278
|
+
|
279
|
+
role :regular_user, scope: lambda{resource.regular_user}
|
280
|
+
role :admin_user, scope: lambda{resource.admin_user}
|
222
281
|
|
223
|
-
|
282
|
+
def index?
|
283
|
+
allow :regular_user, :admin_user
|
284
|
+
end
|
285
|
+
|
286
|
+
private
|
287
|
+
|
288
|
+
def regular_user?
|
289
|
+
@user.present?
|
290
|
+
end
|
291
|
+
|
292
|
+
def admin_user?
|
293
|
+
@user.admin?
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
Whenever an admin tries to access the `index` action, PunditRoles will first check if the admin is a `regular_user`,
|
298
|
+
which will be true, since admin is in fact logged in. Therefore, it will return the scope defined for `regular_user`,
|
299
|
+
instead of the scope defined for `admin_user`. This is not the desired behaviour. In order to avoid this, the `index?` method
|
300
|
+
needs to look like this:
|
301
|
+
```ruby
|
302
|
+
def index?
|
303
|
+
allow :admin_user, :regular_user
|
304
|
+
end
|
305
|
+
```
|
306
|
+
In this case, `admin_user` is evaluated before `regular_user`, so admins will correctly get their own scope, instead of the
|
307
|
+
`regular_user` scope.
|
308
|
+
|
309
|
+
* The rule is: whenever a role supersedes another, declare that role first. If two or more roles are exclusionary,
|
310
|
+
meaning that there is no way that a user can fulfill more than one of these roles, then the order in which they are declared
|
311
|
+
does not matter. The guest role can be declared wherever, since PunditRoles will always evaluate whether the user is a
|
312
|
+
`guest` first.
|
313
|
+
|
314
|
+
### Declaring attributes and associations
|
315
|
+
|
316
|
+
* Attributes and associations in this heading are referred to collectively as _options_
|
317
|
+
|
318
|
+
##### Explicit declaration of options
|
224
319
|
|
225
320
|
Options are declared with the `attributes` and `associations` options of the role method.
|
226
321
|
|
227
322
|
Valid options for both `:attributes` and `:associations` are `:show`,`:create`,`:update` and `:save` or the implicit options.
|
228
323
|
|
229
|
-
|
324
|
+
##### Implicit declaration of options
|
230
325
|
|
231
326
|
PunditRoles provides a set of helpers to be able to implicitly declare the options of a role.
|
232
327
|
|
@@ -299,17 +394,21 @@ RESTRICTED_SHOW_ATTRIBUTES = RESTRICTED_SHOW_ATTRIBUTES + [:attr_one, :attr_two]
|
|
299
394
|
There are 8 `RESTRICTED_#{action}_#{option_type}` constants in total, where `option_type` refers
|
300
395
|
to either `ATTRIBUTES` or `ASSOCIATIONS` and `action` refers to `SHOW`, `CREATE`, `UPDATE` or `SAVE`.
|
301
396
|
|
302
|
-
##
|
303
|
-
|
304
|
-
|
305
|
-
|
397
|
+
## Porting over from Pundit
|
398
|
+
If you're already using Pundit, this gem should not conflict with any existing functionality. However, there
|
399
|
+
are a couple of things to watch out for:
|
400
|
+
* PunditRoles uses `@resouce` instead of `@record` in the Policy. This change was made, to reflect the
|
401
|
+
fact that the Policy can have scopes as well as records passed to it.
|
402
|
+
* PunditRoles uses the bang methods `authorize!` and `policy_scope!`, instead of `authorize` and `policy_scope`.
|
403
|
+
* PunditRoles does not use the `Scope` class of Pundit, but it is included in `Policy::Base` so you may use
|
404
|
+
that as well, if you so choose.
|
306
405
|
|
406
|
+
## Planned updates
|
407
|
+
Authorizing associations, generators, and possibly rspec helpers will be coming in the near future.
|
307
408
|
|
308
409
|
## Contributing
|
309
|
-
|
310
410
|
Bug reports are welcome on GitHub at [StairwayB](https://github.com/StairwayB/pundit_roles).
|
311
411
|
|
312
412
|
## License
|
313
|
-
|
314
413
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
315
414
|
|
@@ -8,7 +8,7 @@ module Policy
|
|
8
8
|
# return a uniquely merged hash of permitted attributes and associations of each role the @user has.
|
9
9
|
#
|
10
10
|
# @attr_reader user [Object] the user that initiated the action
|
11
|
-
# @attr_reader
|
11
|
+
# @attr_reader resource [Object] the object we're checking permissions of
|
12
12
|
class Base
|
13
13
|
extend Role
|
14
14
|
include PolicyDefaults
|
@@ -25,62 +25,89 @@ module Policy
|
|
25
25
|
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
26
26
|
def resolve_query(query)
|
27
27
|
permitted_roles = public_send(query)
|
28
|
-
if permitted_roles.is_a? TrueClass or permitted_roles.is_a? FalseClass
|
29
|
-
return permitted_roles
|
30
|
-
end
|
28
|
+
return permitted_roles if permitted_roles.is_a? TrueClass or permitted_roles.is_a? FalseClass
|
31
29
|
|
32
|
-
|
30
|
+
validate_permission_type(permitted_roles, query)
|
31
|
+
permissions = self.class.permissions
|
33
32
|
|
34
|
-
# Always checks if user is a guest, and return the appropriate permission if true
|
35
|
-
# the guest role cannot be merged with other roles
|
36
33
|
if guest?
|
37
|
-
return
|
34
|
+
return handle_guest_options(permitted_roles, permissions)
|
38
35
|
end
|
39
|
-
current_roles = determine_current_roles(permitted_roles, permissions_hash)
|
40
36
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
37
|
+
current_roles = determine_current_roles(permitted_roles)
|
38
|
+
return false unless current_roles.present?
|
44
39
|
|
45
|
-
|
46
|
-
|
40
|
+
return options_or_merge(current_roles, permissions)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Retrieves the permitted roles for the current query and checks each role, until it finds one that
|
44
|
+
# that the user fulfills. It returns the defined scope for that role. Scopes do no merge with other scopes
|
45
|
+
#
|
46
|
+
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
47
|
+
def resolve_scope(query)
|
48
|
+
permitted_roles = public_send(query)
|
49
|
+
return permitted_roles if permitted_roles.is_a? TrueClass or permitted_roles.is_a? FalseClass
|
50
|
+
|
51
|
+
validate_permission_type(permitted_roles, query)
|
52
|
+
scopes = self.class.scopes
|
53
|
+
|
54
|
+
if guest?
|
55
|
+
return handle_guest_scope(permitted_roles, scopes)
|
47
56
|
end
|
48
57
|
|
49
|
-
|
58
|
+
current_roles = determine_current_roles(permitted_roles)
|
59
|
+
return false unless current_roles.present?
|
60
|
+
|
61
|
+
return instance_eval &scopes[current_roles[0]]
|
50
62
|
end
|
51
63
|
|
52
64
|
private
|
53
65
|
|
54
|
-
# Return the default :guest role if guest is present in
|
66
|
+
# Return the default :guest role if guest is present in permitted_roles. Return false otherwise
|
55
67
|
#
|
56
68
|
# @param permitted_roles [Hash] roles returned by the query
|
57
|
-
# @param
|
58
|
-
def
|
69
|
+
# @param permissions [Hash] unrefined hash of options defined by all permitted_for methods
|
70
|
+
def handle_guest_options(permitted_roles, permissions)
|
71
|
+
if permitted_roles.include? :guest
|
72
|
+
return permissions[:guest].merge({roles: [:guest]})
|
73
|
+
end
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_guest_scope(permitted_roles, scopes)
|
59
78
|
if permitted_roles.include? :guest
|
60
|
-
return
|
79
|
+
return instance_eval &scopes[:guest]
|
61
80
|
end
|
62
81
|
return false
|
63
82
|
end
|
64
83
|
|
65
|
-
#
|
66
|
-
#
|
84
|
+
# inspects the current_roles and returns the appropriate option
|
85
|
+
#
|
86
|
+
# @param current_roles [Hash] roles that the current user fulfills
|
87
|
+
# @param permissions [Hash] unrefined hash of options defined by all permitted_for methods
|
88
|
+
def options_or_merge(current_roles, permissions)
|
89
|
+
return false unless current_roles.present?
|
90
|
+
|
91
|
+
if current_roles.length == 1
|
92
|
+
return permissions[current_roles[0]].merge({roles: [current_roles[0]]})
|
93
|
+
end
|
94
|
+
|
95
|
+
return unique_merge(current_roles, permissions)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Build an Array of the roles that the user fulfills.
|
67
99
|
#
|
68
100
|
# @param permitted_roles [Hash] roles returned by the query
|
69
|
-
|
70
|
-
|
71
|
-
current_roles = {}
|
101
|
+
def determine_current_roles(permitted_roles)
|
102
|
+
current_roles = []
|
72
103
|
|
73
104
|
permitted_roles.each do |permitted_role|
|
74
|
-
if permitted_role == :guest
|
105
|
+
if permitted_role == :guest
|
75
106
|
next
|
76
107
|
end
|
77
108
|
|
78
|
-
|
79
|
-
|
80
|
-
current_roles[permitted_role] = permissions_hash[permitted_role]
|
81
|
-
end
|
82
|
-
rescue NoMethodError => e
|
83
|
-
raise NoMethodError, "Undefined test condition, it must be defined as 'role?', where, role is :#{permitted_role}, => #{e.message}"
|
109
|
+
if test_condition?(permitted_role)
|
110
|
+
current_roles << permitted_role
|
84
111
|
end
|
85
112
|
end
|
86
113
|
|
@@ -89,13 +116,13 @@ module Policy
|
|
89
116
|
|
90
117
|
# Uniquely merge the options of all roles that the user fulfills
|
91
118
|
#
|
92
|
-
# @param roles [Hash] roles
|
93
|
-
|
94
|
-
|
119
|
+
# @param roles [Hash] roles that the user fulfills
|
120
|
+
# @param permissions [Hash] the options for all roles
|
121
|
+
def unique_merge(roles, permissions)
|
122
|
+
merged_hash = {attributes: {}, associations: {}, roles: roles}
|
95
123
|
|
96
|
-
roles.each do |role
|
97
|
-
|
98
|
-
option.each do |type, actions|
|
124
|
+
roles.each do |role|
|
125
|
+
permissions[role].each do |type, actions|
|
99
126
|
actions.each do |key, value|
|
100
127
|
unless merged_hash[type][key]
|
101
128
|
merged_hash[type][key] = []
|
@@ -108,6 +135,22 @@ module Policy
|
|
108
135
|
return merged_hash
|
109
136
|
end
|
110
137
|
|
138
|
+
# Helper method for testing the conditional of a role
|
139
|
+
#
|
140
|
+
# @param role [Symbol] the role to be tested
|
141
|
+
# @raise [NoMethodError] if the test condition is undefined
|
142
|
+
def test_condition?(role)
|
143
|
+
begin
|
144
|
+
if send("#{role}?")
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
rescue NoMethodError => e
|
148
|
+
raise NoMethodError, "Undefined test condition, it must be defined as 'role?', where, role is :#{role}, => #{e.message}"
|
149
|
+
end
|
150
|
+
|
151
|
+
return false
|
152
|
+
end
|
153
|
+
|
111
154
|
# Helper method to be able to define allow: :guest, :user, etc. in the query methods
|
112
155
|
#
|
113
156
|
# @param *roles [Array] an array of permitted roles for a particular action
|
@@ -120,8 +163,25 @@ module Policy
|
|
120
163
|
@user.nil?
|
121
164
|
end
|
122
165
|
|
123
|
-
#
|
124
|
-
|
166
|
+
# @api private
|
167
|
+
def validate_permission_type(permitted_roles, query)
|
168
|
+
valid = false
|
169
|
+
_allowed_permission_types.each do |type|
|
170
|
+
if permitted_roles.is_a? type
|
171
|
+
valid = true
|
172
|
+
break
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
raise ArgumentError, "expected #{_allowed_permission_types} in #{query}, got #{permitted_roles.inspect}" unless valid
|
177
|
+
end
|
178
|
+
|
179
|
+
# @api private
|
180
|
+
def _allowed_permission_types
|
181
|
+
[Array, FalseClass, TrueClass]
|
182
|
+
end
|
183
|
+
|
184
|
+
# Scope class from Pundit, not used in this gem
|
125
185
|
class Scope
|
126
186
|
attr_reader :user, :scope
|
127
187
|
|
@@ -3,21 +3,22 @@ require_relative 'role/option_builder'
|
|
3
3
|
# Extended by Policy::Base. Defines the methods necessary for declaring roles.
|
4
4
|
module Role
|
5
5
|
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :permissions
|
7
|
+
attr_accessor :scopes
|
7
8
|
|
8
|
-
# Builds a new role by saving it into the #
|
9
|
+
# Builds a new role by saving it into the #permissions class instance variable
|
9
10
|
# Valid options are :attributes, :associations, :scope, :uses_db, :extend
|
10
11
|
#
|
11
12
|
# @param *opts [Array] the roles, and the options which define the roles
|
12
13
|
# @raise [ArgumentError] if the options are incorrectly defined, or no options are present
|
13
|
-
# @return [Hash] Returns the permissions hash or the record
|
14
14
|
def role(*opts)
|
15
15
|
user_opts = opts.extract_options!.dup
|
16
16
|
options = user_opts.slice(*_role_default_keys)
|
17
17
|
|
18
18
|
raise ArgumentError, 'Please provide at least one role' unless opts.present?
|
19
19
|
|
20
|
-
@
|
20
|
+
@permissions = {} if @permissions.nil?
|
21
|
+
@scopes = {} if @scopes.nil?
|
21
22
|
|
22
23
|
options.each do |key, value|
|
23
24
|
if value.present?
|
@@ -32,7 +33,8 @@ module Role
|
|
32
33
|
|
33
34
|
opts.each do |role|
|
34
35
|
raise ArgumentError, "Expected Symbol for #{role}, got #{role.class}" unless role.is_a? Symbol
|
35
|
-
@
|
36
|
+
@permissions[role] = OptionBuilder.new(self, options[:attributes], options[:associations], options[:scope]).permitted
|
37
|
+
@scopes[role] = options[:scope]
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -43,7 +45,7 @@ module Role
|
|
43
45
|
|
44
46
|
# @api private
|
45
47
|
private def _role_option_validations
|
46
|
-
{attributes: [Hash, Symbol], associations: [Hash, Symbol], scope: [
|
48
|
+
{attributes: [Hash, Symbol], associations: [Hash, Symbol], scope: [Proc], uses_db: [Symbol], extend: [Symbol]}
|
47
49
|
end
|
48
50
|
|
49
51
|
end
|
data/lib/pundit_roles/pundit.rb
CHANGED
@@ -1,32 +1,56 @@
|
|
1
1
|
# Contains the overwritten #authorize method
|
2
2
|
module PunditOverwrite
|
3
3
|
|
4
|
-
#
|
5
|
-
#
|
4
|
+
# A modified version of Pundit's default authorization. Returns a hash of permitted attributes or raises exception
|
5
|
+
# it the user is not authorized
|
6
6
|
#
|
7
7
|
# @param resource [Object] the object we're checking permissions of
|
8
8
|
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
|
9
9
|
# If omitted then this defaults to the Rails controller action name.
|
10
10
|
# @raise [NotAuthorizedError] if the given query method returned false
|
11
|
-
# @return [Object, Hash] Returns the permissions hash or the
|
12
|
-
def authorize(resource, query = nil)
|
11
|
+
# @return [Object, Hash] Returns the permissions hash or the resource
|
12
|
+
def authorize!(resource, query = nil)
|
13
13
|
query ||= params[:action].to_s + '?'
|
14
14
|
|
15
15
|
@_pundit_policy_authorized = true
|
16
16
|
|
17
17
|
policy = policy(resource)
|
18
|
-
|
19
18
|
permitted_records = policy.resolve_query(query)
|
20
19
|
|
21
|
-
|
20
|
+
return determine_action(resource, query, policy, permitted_records)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the permitted scope or raises exception
|
24
|
+
#
|
25
|
+
# @param resource [Object] the object we're checking permissions of
|
26
|
+
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
|
27
|
+
# If omitted then this defaults to the Rails controller action name.
|
28
|
+
# @raise [NotAuthorizedError] if the given query method returned false
|
29
|
+
# @return [Object, ActiveRecord::Association] Returns the permissions hash or the resource
|
30
|
+
def policy_scope!(resource, query = nil)
|
31
|
+
query ||= params[:action].to_s + '?'
|
32
|
+
|
33
|
+
@_pundit_policy_scoped = true
|
34
|
+
|
35
|
+
policy = policy(resource)
|
36
|
+
permitted_scope = policy.resolve_scope(query)
|
37
|
+
|
38
|
+
return determine_action(resource, query, policy, permitted_scope)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def determine_action(resource, query, policy, permitted)
|
45
|
+
unless permitted
|
22
46
|
raise Pundit::NotAuthorizedError, query: query, record: resource, policy: policy
|
23
47
|
end
|
24
48
|
|
25
|
-
if
|
49
|
+
if permitted.is_a? TrueClass
|
26
50
|
return resource
|
27
51
|
end
|
28
52
|
|
29
|
-
return
|
53
|
+
return permitted
|
30
54
|
end
|
31
55
|
end
|
32
56
|
|
data/lib/pundit_roles/version.rb
CHANGED
data/pundit_roles.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Daniel Balogh"]
|
10
10
|
spec.email = ["danielferencbalogh@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{Extends Pundit with roles, allowing attribute and association level authorizations}
|
13
|
-
spec.description = %q{Extends Pundit with roles, allowing attribute and association level authorizations}
|
12
|
+
spec.summary = %q{Extends Pundit with roles, allowing attribute and association level authorizations, suits modern APIs well}
|
13
|
+
spec.description = %q{Extends Pundit with roles, allowing attribute and association level authorizations, suits modern APIs well}
|
14
14
|
spec.homepage = "https://github.com/StairwayB/pundit_roles"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pundit_roles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Balogh
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -38,7 +38,8 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.1.0
|
41
|
-
description: Extends Pundit with roles, allowing attribute and association level authorizations
|
41
|
+
description: Extends Pundit with roles, allowing attribute and association level authorizations,
|
42
|
+
suits modern APIs well
|
42
43
|
email:
|
43
44
|
- danielferencbalogh@gmail.com
|
44
45
|
executables: []
|
@@ -86,5 +87,6 @@ rubyforge_project:
|
|
86
87
|
rubygems_version: 2.6.11
|
87
88
|
signing_key:
|
88
89
|
specification_version: 4
|
89
|
-
summary: Extends Pundit with roles, allowing attribute and association level authorizations
|
90
|
+
summary: Extends Pundit with roles, allowing attribute and association level authorizations,
|
91
|
+
suits modern APIs well
|
90
92
|
test_files: []
|