pundit_roles 0.2.1 → 0.5.0
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 +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: []
|