access-granted 0.1.1 → 0.2
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/.travis.yml +6 -5
- data/Gemfile +3 -1
- data/README.md +236 -74
- data/access-granted.gemspec +2 -3
- data/benchmarks/README.md +24 -0
- data/benchmarks/config.rb +36 -0
- data/benchmarks/permissions.rb +45 -0
- data/lib/access-granted.rb +7 -1
- data/lib/access-granted/policy.rb +2 -5
- data/lib/access-granted/rails/controller_methods.rb +25 -0
- data/lib/access-granted/role.rb +10 -10
- data/spec/controller_methods_spec.rb +42 -0
- data/spec/role_spec.rb +1 -10
- data/spec/spec_helper.rb +3 -1
- metadata +12 -7
- data/lib/access-granted/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73df010b82397adef1ad1fbfdb345d0e5875ae86
|
4
|
+
data.tar.gz: f6db604ca6be5fc22d24225728b700df5ed4db5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98ae394fea6d7a5e4695e76746dc6e0701da2be4c21f9145edc979644e22faa2e3f5149bea758ee6799c2914e833d13bb678c051e7a8df8e35a6c42decae21a4
|
7
|
+
data.tar.gz: 92f00c7b8668b51f25b4041022e26f095d65e302b79a5853aa500901dd851735b107336fa39e03899e4be723dd29fb78c7506a0e458737a89917e91fb1b831df
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -5,120 +5,78 @@
|
|
5
5
|
|
6
6
|
Multi-role and whitelist based authorization gem for Rails. And it's lightweight (~300 lines of code)!
|
7
7
|
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
gem 'access-granted'
|
12
|
+
|
8
13
|
### Supported Ruby versions
|
9
14
|
|
10
|
-
Guaranteed to work on MRI 1.9.3
|
15
|
+
Guaranteed to work on all major Ruby versions MRI 1.9.3-2.2, Rubinius >= 2.X and JRuby >= 1.7.
|
11
16
|
|
12
|
-
|
17
|
+
## Summary
|
13
18
|
|
14
19
|
AccessGranted is meant as a replacement for CanCan to solve three major problems:
|
15
20
|
|
16
|
-
1.
|
21
|
+
1. Performance
|
22
|
+
On average AccessGranted is 50-60% faster in resolving identical dependencies and takes less memory.
|
23
|
+
See [benchmarks](https://github.com/chaps-io/access-granted/blob/master/benchmarks).
|
24
|
+
|
25
|
+
2. Built-in support for user roles
|
17
26
|
|
18
27
|
Easy to read access policy code where permissions are cleanly grouped into roles.
|
19
|
-
Additionally, permissions are forced to be unique in the scope of a role. This greatly simplifies
|
20
|
-
permissions while substantially reducing the code-base.
|
28
|
+
Additionally, permissions are forced to be unique in the scope of a role. This greatly simplifies resolving permissions and makes it faster.
|
21
29
|
|
22
|
-
|
30
|
+
3. white-list based
|
23
31
|
|
24
|
-
This means that you define what
|
32
|
+
This means that you define what the user **can** do, which results in clean, readable policies regardless of app complexity.
|
25
33
|
You don't have to worry about juggling `can`s and `cannot`s in a very convoluted way!
|
26
34
|
|
27
35
|
_Note_: `cannot` is still available, but has a very specifc use. See [Usage](#usage) below.
|
28
36
|
|
29
|
-
|
37
|
+
4. framework agnostic
|
30
38
|
|
31
39
|
Permissions can work on basically any object and AccessGranted is framework-agnostic,
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
See [Usage](#usage) for an example of a complete AccessPolicy file.
|
37
|
-
|
38
|
-
## Compatibility with CanCan
|
39
|
-
|
40
|
-
This gem was created as a replacement for CanCan and therefore it requires minimum work to switch.
|
41
|
-
|
42
|
-
### Main differences
|
43
|
-
|
44
|
-
1. AccessGranted does not extend ActiveRecord in any way, so it does not have the `accessible_by?`
|
45
|
-
method which could be used for querying objects available to current user.
|
46
|
-
This was very complex and only worked with permissions defined using hash conditions, so
|
47
|
-
I decided to not implement this functionality as it was mostly ignored by CanCan users.
|
48
|
-
|
49
|
-
2. Both `can?`/`cannot?` and `authorize!` methods work in Rails controllers and views, just like in CanCan.
|
50
|
-
The only change you have to make is to replace all `can? :manage, Class` with the exact action to check against.
|
51
|
-
`can :manage` is still available for **defining** methods and serves as a shortcut for defining `:read`, `:create`, `:update`, `:destroy` all in one line.
|
52
|
-
|
53
|
-
3. Syntax for defining permissions in AccessPolicy file (Ability in CanCan) is exactly the same,
|
54
|
-
with added roles on top. See [Usage](#usage) below.
|
55
|
-
|
56
|
-
|
57
|
-
## Installation
|
58
|
-
|
59
|
-
### Rails
|
60
|
-
|
61
|
-
This includes Rails-specific integration (`can?`, `cannot?`, `current_policy` helpers and more):
|
62
|
-
|
63
|
-
gem 'access-granted-rails'
|
64
|
-
|
65
|
-
### Others
|
66
|
-
|
67
|
-
gem 'access-granted'
|
40
|
+
but it has Rails support of out the box :)
|
41
|
+
It **does not depend on any libraries**, pure and clean Ruby code. Guaranteed to always work,
|
42
|
+
even when software around changes.
|
68
43
|
|
69
44
|
## Usage
|
70
45
|
|
71
46
|
Roles are defined using blocks (or by passing custom classes to keep things tidy).
|
72
|
-
Order of the roles is important, because they are being traversed in the top-to-bottom order. Generally at the top you will have
|
73
|
-
an admin or other important role giving the user top permissions, and as you go down you define less-privileged roles.
|
74
47
|
|
75
|
-
|
48
|
+
**Order of the roles is VERY important**, because they are being traversed in the top-to-bottom order.
|
49
|
+
At the top you must have an admin or other important role giving the user top permissions, and as you go down you define less-privileged roles.
|
50
|
+
|
51
|
+
### 1. Defining access policy
|
52
|
+
|
53
|
+
Let's start with a complete example of what can be achieved:
|
76
54
|
|
77
55
|
```ruby
|
78
|
-
|
56
|
+
# app/policies/access_policy.rb
|
57
|
+
|
58
|
+
class AccessPolicy
|
79
59
|
include AccessGranted::Policy
|
80
60
|
|
81
61
|
def configure(user)
|
82
|
-
# The most important role prohibiting banned
|
83
|
-
# users from doing anything.
|
84
|
-
# (even if they are moderators or admins)
|
85
|
-
role :banned, { is_banned: true } do
|
86
|
-
cannot [:create, :update, :destroy], Post
|
87
|
-
|
88
|
-
# same as above, :manage is just a shortcut for
|
89
|
-
# `[:read, :create, :update, :destroy]`
|
90
|
-
cannot :manage, Comment
|
91
|
-
end
|
92
62
|
|
93
|
-
#
|
94
|
-
|
63
|
+
# The most important admin role, gets checked first
|
64
|
+
|
95
65
|
role :admin, { is_admin: true } do
|
96
66
|
can :manage, Post
|
97
67
|
can :manage, Comment
|
98
68
|
end
|
99
69
|
|
100
|
-
#
|
101
|
-
# if the role should apply to a given user.
|
70
|
+
# Less privileged moderator role
|
102
71
|
role :moderator, proc {|u| u.moderator? } do
|
103
|
-
# takes precedence over :update/:destroy
|
104
|
-
# permissions defined in member role below
|
105
|
-
# and lets moderators edit and delete all posts
|
106
72
|
can [:update, :destroy], Post
|
107
|
-
|
108
|
-
# and a new permission which lets moderators
|
109
|
-
# modify user accounts
|
110
73
|
can :update, User
|
111
74
|
end
|
112
75
|
|
113
|
-
# The basic role.
|
114
|
-
# Applies to everyone logged in.
|
76
|
+
# The basic role. Applies to every user.
|
115
77
|
role :member do
|
116
78
|
can :create, Post
|
117
79
|
|
118
|
-
# For more advanced permissions
|
119
|
-
# you must use blocks. Hash
|
120
|
-
# conditions should be used for
|
121
|
-
# simple checks only.
|
122
80
|
can [:update, :destroy], Post do |post|
|
123
81
|
post.user_id == user.id && post.comments.empty?
|
124
82
|
end
|
@@ -127,6 +85,192 @@ class Policy
|
|
127
85
|
end
|
128
86
|
```
|
129
87
|
|
88
|
+
#### Defining roles
|
89
|
+
|
90
|
+
Each `role` method accepts the name of the role you're creating and an optional matcher.
|
91
|
+
Matchers are used to check if user belongs to that role and if the permissions inside should be executed against him.
|
92
|
+
|
93
|
+
The simplest role can be defined as follows:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
role :member do
|
97
|
+
can :read, Post
|
98
|
+
can :create, Post
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
This role will allow everyone (since we didn't supply a matcher) to read and create posts.
|
103
|
+
|
104
|
+
But now we want to let admins delete those posts (for example spam posts).
|
105
|
+
In this case we create a new role above the `:member` to add more permissions for the admin:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
role :admin, { is_admin: true } do
|
109
|
+
can :destroy, Post
|
110
|
+
end
|
111
|
+
|
112
|
+
role :member do
|
113
|
+
can :read, Post
|
114
|
+
can :create, Post
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
The `{ is_admin: true }` hash is compared with the user's attributes to see if the role should be applied to him.
|
119
|
+
So, if the user has an attribute `is_admin` set to `true`, then the role will be applied to him.
|
120
|
+
|
121
|
+
**Note:** you can use more keys in the hash to check many attributes at once.
|
122
|
+
|
123
|
+
#### Hash conditions
|
124
|
+
|
125
|
+
Hashes can be used as matchers as a check if action is permitted.
|
126
|
+
For example, we may allow users to only see published posts, like this:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
role :member do
|
130
|
+
can :read, Post, { published: true }
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Block conditions
|
135
|
+
|
136
|
+
"But wait! User should also be able to edit his posts, and only his posts!" you are wondering.
|
137
|
+
This can be done using a block condition in `can` method, like this:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
role :member do
|
141
|
+
can :update, Post do |post|
|
142
|
+
post.author_id == user.id
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
When the given block evaluates to `true`, the user is then given the permission to update the post.
|
148
|
+
|
149
|
+
#### Roles in order of importance
|
150
|
+
|
151
|
+
Additionally we can allow admins to update **all** posts despite them not being authors like this:
|
152
|
+
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
role :admin, { is_admin: true } do
|
156
|
+
can :update, Post
|
157
|
+
end
|
158
|
+
|
159
|
+
role :member do
|
160
|
+
can :update, Post do |post|
|
161
|
+
post.author_id == user.id
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
As stated before: **`:admin` role takes precedence over `:member`** role, so when AccessGranted sees that admin can update all posts, it stops looking at the less important roles.
|
167
|
+
|
168
|
+
That way you can keep a tidy and readable policy file which is basically human readable.
|
169
|
+
|
170
|
+
### Using in Rails
|
171
|
+
|
172
|
+
AccessGranted comes with a set of helpers available in Ruby on Rails apps:
|
173
|
+
|
174
|
+
#### Authorizing controller actions
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class PostsController
|
178
|
+
def show
|
179
|
+
@post = Post.find(params[:id])
|
180
|
+
authorize! :read, @post
|
181
|
+
end
|
182
|
+
|
183
|
+
def create
|
184
|
+
authorize! :create, Post
|
185
|
+
# (...)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
`authorize!` throws an exception when current user doesn't have a given permission.
|
191
|
+
You can rescue from it using `rescue_from`:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class ApplicationController < ActionController::Base
|
195
|
+
rescue_from "AccessGranted::AccessDenied" do |exception|
|
196
|
+
redirect_to root_path, alert: "You don't have permissions to access this page."
|
197
|
+
end
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
#### Checking permissions in controllers
|
202
|
+
|
203
|
+
To check if the user has a permission to perform an action, use `can?` and `cannot?` methods.
|
204
|
+
|
205
|
+
**Example:**
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
class UsersController
|
209
|
+
def update
|
210
|
+
# (...)
|
211
|
+
|
212
|
+
# only admins can elevate users to moderator status
|
213
|
+
|
214
|
+
if can? :make_moderator, @user
|
215
|
+
@user.moderator = params[:user][:moderator]
|
216
|
+
end
|
217
|
+
|
218
|
+
# (...)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
#### Checking permissions in views
|
223
|
+
|
224
|
+
Usually you don't want to show "Create" buttons for people who can't create something.
|
225
|
+
You can hide any part of the page from users without permissions like this:
|
226
|
+
|
227
|
+
```html
|
228
|
+
# app/views/categories/index.html.erb
|
229
|
+
|
230
|
+
<% if can? :create, Category %>
|
231
|
+
<%= link_to "Create new category", new_category_path %>
|
232
|
+
<% end %>
|
233
|
+
```
|
234
|
+
|
235
|
+
#### Customizing policy
|
236
|
+
|
237
|
+
By default AccessGranted adds this method to your controllers:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
def current_policy
|
241
|
+
@current_policy ||= ::AccessPolicy.new(current_user)
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
If you have a different policy class or if your user is not stored in `current_user` variable, then you can override it in any controllers and modify the logic as you please.
|
246
|
+
|
247
|
+
You can even have different policies for different controllers!
|
248
|
+
|
249
|
+
### Using in pure Ruby
|
250
|
+
|
251
|
+
Initialize the Policy class:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
|
255
|
+
policy = AccessPolicy.new(current_user)
|
256
|
+
```
|
257
|
+
|
258
|
+
Check the ability to do something:
|
259
|
+
|
260
|
+
with `can?`:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
policy.can?(:create, Post) #=> true
|
264
|
+
policy.can?(:update, @post) #=> false
|
265
|
+
```
|
266
|
+
|
267
|
+
or with `cannot?`:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
policy.cannot?(:create, Post) #=> false
|
271
|
+
policy.cannot?(:update, @ost) #=> true
|
272
|
+
```
|
273
|
+
|
130
274
|
## Common examples
|
131
275
|
|
132
276
|
### Extracting roles to separate files
|
@@ -165,6 +309,24 @@ class MemberRole < AccessGranted::Role
|
|
165
309
|
end
|
166
310
|
```
|
167
311
|
|
312
|
+
## Compatibility with CanCan
|
313
|
+
|
314
|
+
This gem was created as a replacement for CanCan and therefore it requires minimum work to switch.
|
315
|
+
|
316
|
+
### Main differences
|
317
|
+
|
318
|
+
1. AccessGranted does not extend ActiveRecord in any way, so it does not have the `accessible_by?`
|
319
|
+
method which could be used for querying objects available to current user.
|
320
|
+
This was very complex and only worked with permissions defined using hash conditions, so
|
321
|
+
I decided to not implement this functionality as it was mostly ignored by CanCan users.
|
322
|
+
|
323
|
+
2. Both `can?`/`cannot?` and `authorize!` methods work in Rails controllers and views, just like in CanCan.
|
324
|
+
The only change you have to make is to replace all `can? :manage, Class` with the exact action to check against.
|
325
|
+
`can :manage` is still available for **defining** methods and serves as a shortcut for defining `:read`, `:create`, `:update`, `:destroy` all in one line.
|
326
|
+
|
327
|
+
3. Syntax for defining permissions in AccessPolicy file (Ability in CanCan) is exactly the same,
|
328
|
+
with added roles on top. See [Usage](#usage) below.
|
329
|
+
|
168
330
|
|
169
331
|
## Contributing
|
170
332
|
|
data/access-granted.gemspec
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'access-granted/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "access-granted"
|
8
|
-
spec.version =
|
7
|
+
spec.version = "0.2"
|
9
8
|
spec.authors = ["Piotrek Okoński"]
|
10
9
|
spec.email = ["piotrek@okonski.org"]
|
11
10
|
spec.description = %q{Role based authorization gem}
|
@@ -19,5 +18,5 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.require_paths = ["lib"]
|
20
19
|
|
21
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rspec"
|
21
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
22
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Benchmark results
|
2
|
+
|
3
|
+
Benchmarks ran on Ubuntu 15.10 64bit, i5 2500k @ 4.4Ghz, 16 GB RAM with Ruby 2.2.
|
4
|
+
|
5
|
+
## permissions.rb
|
6
|
+
|
7
|
+
This benchmark runs `can?` method for the 3 user roles for 20 seconds each, for both CanCan and AccessGranted.
|
8
|
+
|
9
|
+
```
|
10
|
+
Calculating -------------------------------------
|
11
|
+
ag-admin 21.361k i/100ms
|
12
|
+
cancan-admin 13.631k i/100ms
|
13
|
+
ag-moderator 22.328k i/100ms
|
14
|
+
cancan-moderator 11.679k i/100ms
|
15
|
+
ag-user 25.860k i/100ms
|
16
|
+
cancan-user 16.308k i/100ms
|
17
|
+
-------------------------------------------------
|
18
|
+
ag-admin 283.174k (± 1.1%) i/s - 5.682M
|
19
|
+
cancan-admin 160.450k (± 1.0%) i/s - 3.217M
|
20
|
+
ag-moderator 301.290k (± 1.1%) i/s - 6.029M
|
21
|
+
cancan-moderator 134.591k (± 1.3%) i/s - 2.698M
|
22
|
+
ag-user 353.259k (± 0.9%) i/s - 7.086M
|
23
|
+
cancan-user 198.579k (± 1.6%) i/s - 3.979M
|
24
|
+
```
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Ability
|
2
|
+
include CanCan::Ability
|
3
|
+
|
4
|
+
def initialize(user)
|
5
|
+
if user.is_admin == true
|
6
|
+
can :destroy, String
|
7
|
+
end
|
8
|
+
|
9
|
+
if user.is_moderator == true
|
10
|
+
can :update, String
|
11
|
+
end
|
12
|
+
|
13
|
+
can :read, String
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class AccessPolicy
|
18
|
+
include AccessGranted::Policy
|
19
|
+
|
20
|
+
def configure(user)
|
21
|
+
role :administrator, { is_admin: true } do
|
22
|
+
can :destroy, String
|
23
|
+
end
|
24
|
+
|
25
|
+
role :moderator, { is_moderator: true } do
|
26
|
+
can :update, String
|
27
|
+
end
|
28
|
+
|
29
|
+
role :member do
|
30
|
+
can :read, String
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class User < Struct.new(:is_admin, :is_moderator)
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'access-granted'
|
3
|
+
require 'cancan'
|
4
|
+
require_relative './config'
|
5
|
+
|
6
|
+
admin = User.new(true, false)
|
7
|
+
mod = User.new(false, true)
|
8
|
+
user = User.new(false, false)
|
9
|
+
|
10
|
+
user_policy = AccessPolicy.new(user)
|
11
|
+
admin_policy = AccessPolicy.new(admin)
|
12
|
+
mod_policy = AccessPolicy.new(mod)
|
13
|
+
|
14
|
+
user_ability = Ability.new(user)
|
15
|
+
admin_ability = Ability.new(admin)
|
16
|
+
mod_ability = Ability.new(mod)
|
17
|
+
|
18
|
+
Benchmark.ips do |x|
|
19
|
+
x.config(time: 20, warmup: 2)
|
20
|
+
|
21
|
+
x.report("ag-admin") do
|
22
|
+
admin_policy.can?(:read, String)
|
23
|
+
end
|
24
|
+
|
25
|
+
x.report("ag-moderator") do
|
26
|
+
mod_policy.can?(:read, String)
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report("ag-user") do
|
30
|
+
user_policy.can?(:read, String)
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report("cancan-admin") do
|
34
|
+
admin_ability.can?(:read, String)
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report("cancan-moderator") do
|
38
|
+
mod_ability.can?(:read, String)
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report("cancan-user") do
|
42
|
+
user_ability.can?(:read, String)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/access-granted.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
-
require "access-granted/version"
|
2
1
|
require "access-granted/exceptions"
|
3
2
|
require "access-granted/policy"
|
4
3
|
require "access-granted/permission"
|
5
4
|
require "access-granted/role"
|
5
|
+
require 'access-granted/rails/controller_methods'
|
6
6
|
|
7
7
|
module AccessGranted
|
8
8
|
|
9
9
|
end
|
10
|
+
|
11
|
+
if defined? ActionController::Base
|
12
|
+
ActionController::Base.class_eval do
|
13
|
+
include AccessGranted::Rails::ControllerMethods
|
14
|
+
end
|
15
|
+
end
|
@@ -26,7 +26,8 @@ module AccessGranted
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def can?(action, subject)
|
29
|
-
|
29
|
+
roles.each do |role|
|
30
|
+
next unless role.applies_to?(@user)
|
30
31
|
permission = role.find_permission(action, subject)
|
31
32
|
return permission.granted if permission
|
32
33
|
end
|
@@ -37,10 +38,6 @@ module AccessGranted
|
|
37
38
|
!can?(*args)
|
38
39
|
end
|
39
40
|
|
40
|
-
def matching_roles
|
41
|
-
roles.select { |role| role.applies_to?(@user) }
|
42
|
-
end
|
43
|
-
|
44
41
|
def authorize!(action, subject)
|
45
42
|
if cannot?(action, subject)
|
46
43
|
raise AccessDenied
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AccessGranted
|
2
|
+
module Rails
|
3
|
+
module ControllerMethods
|
4
|
+
def current_policy
|
5
|
+
@current_policy ||= ::AccessPolicy.new(current_user)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.helper_method :can?, :cannot?, :current_policy
|
10
|
+
end
|
11
|
+
|
12
|
+
def can?(*args)
|
13
|
+
current_policy.can?(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def cannot?(*args)
|
17
|
+
current_policy.cannot?(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorize!(*args)
|
21
|
+
current_policy.authorize!(*args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/access-granted/role.rb
CHANGED
@@ -28,8 +28,8 @@ module AccessGranted
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def find_permission(action, subject)
|
31
|
-
|
32
|
-
permission.matches_conditions?(subject)
|
31
|
+
permissions_by_action(action).detect do |permission|
|
32
|
+
permission.matches_subject?(subject) && permission.matches_conditions?(subject)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -44,12 +44,6 @@ module AccessGranted
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def relevant_permissions(action, subject)
|
48
|
-
permissions_by_action(action).select do |perm|
|
49
|
-
perm.matches_subject?(subject)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
47
|
def matches_hash?(user, conditions = {})
|
54
48
|
conditions.all? do |name, value|
|
55
49
|
user.send(name) == value
|
@@ -58,7 +52,7 @@ module AccessGranted
|
|
58
52
|
|
59
53
|
def add_permission(granted, action, subject, conditions, block)
|
60
54
|
prepare_actions(action).each do |a|
|
61
|
-
raise DuplicatePermission if
|
55
|
+
raise DuplicatePermission if permission_exists?(a, subject)
|
62
56
|
@permissions << Permission.new(granted, a, subject, conditions, block)
|
63
57
|
@permissions_by_action[a] ||= []
|
64
58
|
@permissions_by_action[a] << @permissions.size - 1
|
@@ -67,11 +61,17 @@ module AccessGranted
|
|
67
61
|
|
68
62
|
private
|
69
63
|
|
64
|
+
def permission_exists?(action, subject)
|
65
|
+
permissions_by_action(action).any? do |permission|
|
66
|
+
permission.matches_subject?(subject)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
70
|
def prepare_actions(action)
|
71
71
|
if action == :manage
|
72
72
|
actions = [:read, :create, :update, :destroy]
|
73
73
|
else
|
74
|
-
actions = [action]
|
74
|
+
actions = Array(*[action])
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AccessGranted::Rails::ControllerMethods do
|
4
|
+
before(:each) do
|
5
|
+
@current_user = double("User")
|
6
|
+
@controller_class = Class.new
|
7
|
+
@controller = @controller_class.new
|
8
|
+
allow(@controller_class).to receive(:helper_method).with(:can?, :cannot?, :current_policy)
|
9
|
+
@controller_class.send(:include, AccessGranted::Rails::ControllerMethods)
|
10
|
+
allow(@controller).to receive(:current_user).and_return(@current_user)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have current_policy method returning Policy instance" do
|
14
|
+
expect(@controller.current_policy).to be_kind_of(AccessGranted::Policy)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "provides can? and cannot? method delegated to current_policy" do
|
18
|
+
expect(@controller.can?(:read, String)).to eq(false)
|
19
|
+
expect(@controller.cannot?(:read, String)).to eq(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#authorize!" do
|
23
|
+
it "raises exception when authorization fails" do
|
24
|
+
expect { @controller.authorize!(:read, String) }.to raise_error(AccessGranted::AccessDenied)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns subject if authorization succeeds" do
|
28
|
+
klass = Class.new do
|
29
|
+
include AccessGranted::Policy
|
30
|
+
|
31
|
+
def configure(user)
|
32
|
+
role :member, 1 do
|
33
|
+
can :read, String
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
policy = klass.new(@current_user)
|
38
|
+
allow(@controller).to receive(:current_policy).and_return(policy)
|
39
|
+
expect(@controller.authorize!(:read, String)).to eq(String)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/role_spec.rb
CHANGED
@@ -4,22 +4,13 @@ describe AccessGranted::Role do
|
|
4
4
|
subject { AccessGranted::Role }
|
5
5
|
|
6
6
|
it "requires a role name" do
|
7
|
-
expect { subject.new }.to raise_error
|
7
|
+
expect { subject.new }.to raise_error(ArgumentError)
|
8
8
|
end
|
9
9
|
|
10
10
|
it "creates a default role without conditions" do
|
11
11
|
expect(subject.new(:member).conditions).to be_nil
|
12
12
|
end
|
13
13
|
|
14
|
-
describe "#relevant_permissions?" do
|
15
|
-
it "returns only matching permissions" do
|
16
|
-
role = subject.new(:member)
|
17
|
-
role.can :read, String
|
18
|
-
role.can :read, Hash
|
19
|
-
expect(role.relevant_permissions(:read, String)).to eq([AccessGranted::Permission.new(true, :read, String)])
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
14
|
describe "#applies_to?" do
|
24
15
|
it "matches user when no conditions given" do
|
25
16
|
role = subject.new(:member)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
|
+
|
3
4
|
if ENV["COV"]
|
4
5
|
require 'simplecov'
|
5
6
|
SimpleCov.start
|
@@ -19,12 +20,13 @@ module ActionController
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
23
|
+
|
22
24
|
require 'access-granted'
|
23
25
|
|
24
26
|
class FakePost < Struct.new(:user_id)
|
25
27
|
end
|
26
28
|
|
27
|
-
class
|
29
|
+
class AccessPolicy
|
28
30
|
include AccessGranted::Policy
|
29
31
|
end
|
30
32
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: access-granted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotrek Okoński
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '3.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '3.0'
|
41
41
|
description: Role based authorization gem
|
42
42
|
email:
|
43
43
|
- piotrek@okonski.org
|
@@ -53,12 +53,16 @@ files:
|
|
53
53
|
- README.md
|
54
54
|
- Rakefile
|
55
55
|
- access-granted.gemspec
|
56
|
+
- benchmarks/README.md
|
57
|
+
- benchmarks/config.rb
|
58
|
+
- benchmarks/permissions.rb
|
56
59
|
- lib/access-granted.rb
|
57
60
|
- lib/access-granted/exceptions.rb
|
58
61
|
- lib/access-granted/permission.rb
|
59
62
|
- lib/access-granted/policy.rb
|
63
|
+
- lib/access-granted/rails/controller_methods.rb
|
60
64
|
- lib/access-granted/role.rb
|
61
|
-
-
|
65
|
+
- spec/controller_methods_spec.rb
|
62
66
|
- spec/permission_spec.rb
|
63
67
|
- spec/policy_spec.rb
|
64
68
|
- spec/role_spec.rb
|
@@ -89,6 +93,7 @@ specification_version: 4
|
|
89
93
|
summary: Elegant whitelist and role based authorization with ability to prioritize
|
90
94
|
roles.
|
91
95
|
test_files:
|
96
|
+
- spec/controller_methods_spec.rb
|
92
97
|
- spec/permission_spec.rb
|
93
98
|
- spec/policy_spec.rb
|
94
99
|
- spec/role_spec.rb
|