access-granted 0.1.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|