access-granted 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +32 -26
- data/access-granted.gemspec +1 -1
- data/benchmarks/config.rb +14 -4
- data/benchmarks/permissions.rb +8 -9
- data/lib/access-granted.rb +1 -1
- data/lib/access-granted/policy.rb +21 -6
- data/lib/access-granted/role.rb +8 -14
- data/spec/policy_spec.rb +8 -8
- data/spec/spec_helper.rb +4 -2
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62c9307deb8d9c208a9934214e56999b093c017b
|
4
|
+
data.tar.gz: 74049e46c543d0015f565c96236ddc7b7fd7bea6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c15fffc2c35008b3e0dee3b758827c483ea12bcacc3eb0f9fabfec61004dd1929c92210846bb6a1ec0c0c66dab1b78c0d087f2d096d822f48f50519661f5a558
|
7
|
+
data.tar.gz: 70d741d7ed4606a3b1309e6a1b1a8ffdf84c0624c6040cb7e56cc6882c1dfbccbc533e65ac035b99ee695f33392059865e07fa694dc480e19913a4a25dd671d2
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -3,12 +3,19 @@
|
|
3
3
|
## [![](http://i.imgur.com/ya8Wnyl.png)](https://chaps.io) proudly made by [Chaps](https://chaps.io)
|
4
4
|
|
5
5
|
|
6
|
-
|
6
|
+
AccessGranted is a multi-role and whitelist based authorization gem for Rails. And it's lightweight (~300 lines of code)!
|
7
7
|
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
11
|
-
|
11
|
+
Add the gem to your gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'access-granted', '~> 1.0.0'
|
15
|
+
```
|
16
|
+
Run the bundle command to install it. Then run the generator:
|
17
|
+
|
18
|
+
rails generate access_granted:policy
|
12
19
|
|
13
20
|
### Supported Ruby versions
|
14
21
|
|
@@ -20,37 +27,37 @@ AccessGranted is meant as a replacement for CanCan to solve major problems:
|
|
20
27
|
|
21
28
|
1. Performance
|
22
29
|
|
23
|
-
On average AccessGranted is
|
30
|
+
On average AccessGranted is **20 times faster** in resolving identical permissions and takes less memory.
|
24
31
|
See [benchmarks](https://github.com/chaps-io/access-granted/blob/master/benchmarks).
|
25
32
|
|
26
33
|
2. Roles
|
27
34
|
|
28
|
-
Adds support for roles, so no more `if`
|
35
|
+
Adds support for roles, so no more `if`s and `else`s in your Policy file. This makes it extremely easy to maintain and read the code.
|
29
36
|
|
30
|
-
3.
|
37
|
+
3. Whitelists
|
31
38
|
|
32
|
-
This means that you define what the user
|
39
|
+
This means that you define what the user can do, which results in clean, readable policies regardless of application complexity.
|
33
40
|
You don't have to worry about juggling `can`s and `cannot`s in a very convoluted way!
|
34
41
|
|
35
42
|
_Note_: `cannot` is still available, but has a very specifc use. See [Usage](#usage) below.
|
36
43
|
|
37
|
-
4.
|
44
|
+
4. Framework agnostic
|
38
45
|
|
39
46
|
Permissions can work on basically any object and AccessGranted is framework-agnostic,
|
40
|
-
but it has Rails support out of the box :)
|
41
|
-
It
|
47
|
+
but it has Rails support out of the box. :)
|
48
|
+
It does not depend on any libraries, pure and clean Ruby code. Guaranteed to always work,
|
42
49
|
even when software around changes.
|
43
50
|
|
44
51
|
## Usage
|
45
52
|
|
46
53
|
Roles are defined using blocks (or by passing custom classes to keep things tidy).
|
47
54
|
|
48
|
-
**Order of the roles is VERY important**, because they are being traversed in
|
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.
|
55
|
+
**Order of the roles is VERY important**, because they are being traversed in top-to-bottom order.
|
56
|
+
At the top you must have an admin or some other important role giving the user top permissions, and as you go down you define less-privileged roles.
|
50
57
|
|
51
58
|
**I recommend starting your adventure by reading my [blog post about AccessGranted](http://blog.chaps.io/2015/11/13/role-based-authorization-in-rails.html), where I demonstrate its abilities on a real life example.**
|
52
59
|
|
53
|
-
###
|
60
|
+
### Defining an access policy
|
54
61
|
|
55
62
|
Let's start with a complete example of what can be achieved:
|
56
63
|
|
@@ -89,7 +96,7 @@ end
|
|
89
96
|
#### Defining roles
|
90
97
|
|
91
98
|
Each `role` method accepts the name of the role you're creating and an optional matcher.
|
92
|
-
Matchers are used to check if user belongs to that role and if the permissions inside should be executed against it.
|
99
|
+
Matchers are used to check if the user belongs to that role and if the permissions inside should be executed against it.
|
93
100
|
|
94
101
|
The simplest role can be defined as follows:
|
95
102
|
|
@@ -123,7 +130,7 @@ So, if the user has an attribute `is_admin` set to `true`, then the role will be
|
|
123
130
|
|
124
131
|
#### Hash conditions
|
125
132
|
|
126
|
-
Hashes can be used as matchers
|
133
|
+
Hashes can be used as matchers to check if an action is permitted.
|
127
134
|
For example, we may allow users to only see published posts, like this:
|
128
135
|
|
129
136
|
```ruby
|
@@ -145,7 +152,7 @@ role :member do
|
|
145
152
|
end
|
146
153
|
```
|
147
154
|
|
148
|
-
When the given block evaluates to `true`, then user is given the permission to update the post.
|
155
|
+
When the given block evaluates to `true`, then `user` is given the permission to update the post.
|
149
156
|
|
150
157
|
#### Roles in order of importance
|
151
158
|
|
@@ -168,7 +175,7 @@ As stated before: **`:admin` role takes precedence over `:member`** role, so whe
|
|
168
175
|
|
169
176
|
That way you can keep a tidy and readable policy file which is basically human readable.
|
170
177
|
|
171
|
-
###
|
178
|
+
### Usage with Rails
|
172
179
|
|
173
180
|
AccessGranted comes with a set of helpers available in Ruby on Rails apps:
|
174
181
|
|
@@ -188,20 +195,20 @@ class PostsController
|
|
188
195
|
end
|
189
196
|
```
|
190
197
|
|
191
|
-
`authorize!` throws an exception when
|
198
|
+
`authorize!` throws an exception when `current_user` doesn't have a given permission.
|
192
199
|
You can rescue from it using `rescue_from`:
|
193
200
|
|
194
201
|
```ruby
|
195
202
|
class ApplicationController < ActionController::Base
|
196
203
|
rescue_from "AccessGranted::AccessDenied" do |exception|
|
197
|
-
redirect_to root_path, alert: "You don't have
|
204
|
+
redirect_to root_path, alert: "You don't have permission to access this page."
|
198
205
|
end
|
199
206
|
end
|
200
207
|
```
|
201
208
|
|
202
209
|
#### Checking permissions in controllers
|
203
210
|
|
204
|
-
To check if the user has a permission to perform an action, use `can?` and `cannot?` methods.
|
211
|
+
To check if the user has a permission to perform an action, use the `can?` and `cannot?` methods.
|
205
212
|
|
206
213
|
**Example:**
|
207
214
|
|
@@ -244,16 +251,15 @@ By default, AccessGranted adds this method to your controllers:
|
|
244
251
|
end
|
245
252
|
```
|
246
253
|
|
247
|
-
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 controller and modify the logic as you please.
|
254
|
+
If you have a different policy class or if your user is not stored in the `current_user` variable, then you can override it in any controller and modify the logic as you please.
|
248
255
|
|
249
256
|
You can even have different policies for different controllers!
|
250
257
|
|
251
|
-
###
|
258
|
+
### Usage with pure Ruby
|
252
259
|
|
253
260
|
Initialize the Policy class:
|
254
261
|
|
255
262
|
```ruby
|
256
|
-
|
257
263
|
policy = AccessPolicy.new(current_user)
|
258
264
|
```
|
259
265
|
|
@@ -296,7 +302,7 @@ end
|
|
296
302
|
|
297
303
|
```
|
298
304
|
|
299
|
-
And roles should look like this
|
305
|
+
And roles should look like this:
|
300
306
|
|
301
307
|
```ruby
|
302
308
|
# app/roles/member_role.rb
|
@@ -326,8 +332,8 @@ This gem has been created as a replacement for CanCan and therefore it requires
|
|
326
332
|
The only change you have to make is to replace all `can? :manage, Class` with the exact action to check against.
|
327
333
|
`can :manage` is still available for **defining** methods and serves as a shortcut for defining `:create`, `:read`, `:update`, `:destroy` all in one line.
|
328
334
|
|
329
|
-
3. Syntax for defining permissions in AccessPolicy file (Ability in CanCan) is exactly the same,
|
330
|
-
with roles added on top. See [Usage](#usage)
|
335
|
+
3. Syntax for defining permissions in the AccessPolicy file (Ability in CanCan) is exactly the same,
|
336
|
+
with roles added on top. See [Usage](#usage) above.
|
331
337
|
|
332
338
|
|
333
339
|
## Contributing
|
@@ -336,4 +342,4 @@ This gem has been created as a replacement for CanCan and therefore it requires
|
|
336
342
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
337
343
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
338
344
|
4. Push to the branch (`git push origin my-new-feature`)
|
339
|
-
5. Create new
|
345
|
+
5. Create new pull request
|
data/access-granted.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "access-granted"
|
7
|
-
spec.version = "1.0
|
7
|
+
spec.version = "1.1.0"
|
8
8
|
spec.authors = ["Piotrek Okoński"]
|
9
9
|
spec.email = ["piotrek@okonski.org"]
|
10
10
|
spec.description = %q{Role based authorization gem}
|
data/benchmarks/config.rb
CHANGED
@@ -2,35 +2,45 @@ class Ability
|
|
2
2
|
include CanCan::Ability
|
3
3
|
|
4
4
|
def initialize(user)
|
5
|
-
if user.is_admin
|
5
|
+
if user.is_admin
|
6
6
|
can :destroy, String
|
7
|
+
can :foo, Integer
|
7
8
|
end
|
8
9
|
|
9
|
-
if user.is_moderator
|
10
|
+
if user.is_moderator
|
10
11
|
can :update, String
|
12
|
+
can :bar, String
|
11
13
|
end
|
12
14
|
|
13
15
|
can :read, String
|
16
|
+
can :zoom, Integer
|
17
|
+
can :boom, Hash
|
18
|
+
can :rub, Fixnum
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
22
|
class AccessPolicy
|
18
23
|
include AccessGranted::Policy
|
19
24
|
|
20
|
-
def configure
|
25
|
+
def configure
|
21
26
|
role :administrator, { is_admin: true } do
|
22
27
|
can :destroy, String
|
28
|
+
can :foo, Integer
|
23
29
|
end
|
24
30
|
|
25
31
|
role :moderator, { is_moderator: true } do
|
26
32
|
can :update, String
|
33
|
+
can :bar, String
|
27
34
|
end
|
28
35
|
|
29
36
|
role :member do
|
30
37
|
can :read, String
|
38
|
+
can :zoom, Integer
|
39
|
+
can :boom, Hash
|
40
|
+
can :rub, Fixnum
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
35
|
-
class User < Struct.new(:is_admin, :is_moderator)
|
45
|
+
class User < Struct.new(:id, :is_admin, :is_moderator)
|
36
46
|
end
|
data/benchmarks/permissions.rb
CHANGED
@@ -3,9 +3,9 @@ require 'access-granted'
|
|
3
3
|
require 'cancan'
|
4
4
|
require_relative './config'
|
5
5
|
|
6
|
-
admin = User.new(true, false)
|
7
|
-
mod = User.new(false, true)
|
8
|
-
user = User.new(false, false)
|
6
|
+
admin = User.new(1, true, false)
|
7
|
+
mod = User.new(2, false, true)
|
8
|
+
user = User.new(3, false, false)
|
9
9
|
|
10
10
|
user_policy = AccessPolicy.new(user)
|
11
11
|
admin_policy = AccessPolicy.new(admin)
|
@@ -16,18 +16,18 @@ admin_ability = Ability.new(admin)
|
|
16
16
|
mod_ability = Ability.new(mod)
|
17
17
|
|
18
18
|
Benchmark.ips do |x|
|
19
|
-
x.config(time:
|
19
|
+
x.config(time: 5, warmup: 1)
|
20
20
|
|
21
21
|
x.report("ag-admin") do
|
22
22
|
admin_policy.can?(:read, String)
|
23
23
|
end
|
24
24
|
|
25
25
|
x.report("ag-moderator") do
|
26
|
-
mod_policy.can?(:
|
26
|
+
mod_policy.can?(:bar, String)
|
27
27
|
end
|
28
28
|
|
29
29
|
x.report("ag-user") do
|
30
|
-
user_policy.can?(:
|
30
|
+
user_policy.can?(:zoom, Integer)
|
31
31
|
end
|
32
32
|
|
33
33
|
x.report("cancan-admin") do
|
@@ -35,11 +35,10 @@ Benchmark.ips do |x|
|
|
35
35
|
end
|
36
36
|
|
37
37
|
x.report("cancan-moderator") do
|
38
|
-
mod_ability.can?(:
|
38
|
+
mod_ability.can?(:bar, String)
|
39
39
|
end
|
40
40
|
|
41
41
|
x.report("cancan-user") do
|
42
|
-
user_ability.can?(:
|
42
|
+
user_ability.can?(:zoom, Integer)
|
43
43
|
end
|
44
|
-
|
45
44
|
end
|
data/lib/access-granted.rb
CHANGED
@@ -2,7 +2,7 @@ require "access-granted/exceptions"
|
|
2
2
|
require "access-granted/policy"
|
3
3
|
require "access-granted/permission"
|
4
4
|
require "access-granted/role"
|
5
|
-
require
|
5
|
+
require "access-granted/rails/controller_methods"
|
6
6
|
|
7
7
|
module AccessGranted
|
8
8
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
module AccessGranted
|
2
2
|
module Policy
|
3
|
-
attr_accessor :roles
|
3
|
+
attr_accessor :roles, :cache
|
4
|
+
attr_reader :user
|
4
5
|
|
5
|
-
def initialize(user)
|
6
|
+
def initialize(user, cache_enabled = true)
|
6
7
|
@user = user
|
7
8
|
@roles = []
|
9
|
+
@cache = {}
|
8
10
|
configure
|
9
11
|
end
|
10
12
|
|
@@ -17,20 +19,25 @@ module AccessGranted
|
|
17
19
|
raise DuplicateRole, "Role '#{name}' already defined"
|
18
20
|
end
|
19
21
|
r = if conditions_or_klass.is_a?(Class) && conditions_or_klass <= AccessGranted::Role
|
20
|
-
conditions_or_klass.new(name, conditions,
|
22
|
+
conditions_or_klass.new(name, conditions, user, block)
|
21
23
|
else
|
22
|
-
Role.new(name, conditions_or_klass,
|
24
|
+
Role.new(name, conditions_or_klass, user, block)
|
23
25
|
end
|
24
26
|
roles << r
|
25
27
|
r
|
26
28
|
end
|
27
29
|
|
28
30
|
def can?(action, subject = nil)
|
29
|
-
|
30
|
-
|
31
|
+
cache[action] ||= {}
|
32
|
+
cache[action][subject] ||= check_permission(action, subject)
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_permission(action, subject)
|
36
|
+
applicable_roles.each do |role|
|
31
37
|
permission = role.find_permission(action, subject)
|
32
38
|
return permission.granted if permission
|
33
39
|
end
|
40
|
+
|
34
41
|
false
|
35
42
|
end
|
36
43
|
|
@@ -44,5 +51,13 @@ module AccessGranted
|
|
44
51
|
end
|
45
52
|
subject
|
46
53
|
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def applicable_roles
|
58
|
+
@applicable_roles ||= roles.select do |role|
|
59
|
+
role.applies_to?(user)
|
60
|
+
end
|
61
|
+
end
|
47
62
|
end
|
48
63
|
end
|
data/lib/access-granted/role.rb
CHANGED
@@ -8,7 +8,7 @@ module AccessGranted
|
|
8
8
|
@conditions = conditions
|
9
9
|
@block = block
|
10
10
|
@permissions = []
|
11
|
-
|
11
|
+
|
12
12
|
if @block
|
13
13
|
instance_eval(&@block)
|
14
14
|
else
|
@@ -28,8 +28,10 @@ module AccessGranted
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def find_permission(action, subject)
|
31
|
-
|
32
|
-
permission.
|
31
|
+
permissions.detect do |permission|
|
32
|
+
permission.action == action &&
|
33
|
+
permission.matches_subject?(subject) &&
|
34
|
+
permission.matches_conditions?(subject)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -52,17 +54,15 @@ module AccessGranted
|
|
52
54
|
|
53
55
|
def add_permission(granted, action, subject, conditions, block)
|
54
56
|
prepare_actions(action).each do |a|
|
55
|
-
raise DuplicatePermission if
|
56
|
-
|
57
|
-
@permissions_by_action[a] ||= []
|
58
|
-
@permissions_by_action[a] << @permissions.size - 1
|
57
|
+
raise DuplicatePermission if find_permission(a, subject)
|
58
|
+
permissions << Permission.new(granted, a, subject, @user, conditions, block)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
private
|
63
63
|
|
64
64
|
def permission_exists?(action, subject)
|
65
|
-
|
65
|
+
permissions.any? do |permission|
|
66
66
|
permission.matches_subject?(subject)
|
67
67
|
end
|
68
68
|
end
|
@@ -74,11 +74,5 @@ module AccessGranted
|
|
74
74
|
actions = Array(*[action])
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
78
|
-
def permissions_by_action(action)
|
79
|
-
(@permissions_by_action[action] || []).map do |index|
|
80
|
-
@permissions[index]
|
81
|
-
end
|
82
|
-
end
|
83
77
|
end
|
84
78
|
end
|
data/spec/policy_spec.rb
CHANGED
@@ -6,16 +6,16 @@ describe AccessGranted::Policy do
|
|
6
6
|
|
7
7
|
describe "#configure" do
|
8
8
|
before :each do
|
9
|
-
@member =
|
10
|
-
@mod =
|
11
|
-
@admin =
|
12
|
-
@banned =
|
9
|
+
@member = FakeUser.new(1, false, false, false)
|
10
|
+
@mod = FakeUser.new(2, true, false, false)
|
11
|
+
@admin = FakeUser.new(3, false, true, false)
|
12
|
+
@banned = FakeUser.new(4, false, true, true)
|
13
13
|
end
|
14
14
|
|
15
15
|
it "passes user object to permission block" do
|
16
16
|
post_owner = double(id: 123)
|
17
17
|
other_user = double(id: 5)
|
18
|
-
post = FakePost.new(post_owner.id)
|
18
|
+
post = FakePost.new(1, post_owner.id)
|
19
19
|
|
20
20
|
klass = Class.new do
|
21
21
|
include AccessGranted::Policy
|
@@ -63,8 +63,8 @@ describe AccessGranted::Policy do
|
|
63
63
|
|
64
64
|
context "when multiple roles define the same permission" do
|
65
65
|
it "checks all roles until conditions are met" do
|
66
|
-
user_post = FakePost.new(@member.id)
|
67
|
-
other_post = FakePost.new(66)
|
66
|
+
user_post = FakePost.new(1, @member.id)
|
67
|
+
other_post = FakePost.new(2, 66)
|
68
68
|
|
69
69
|
klass = Class.new do
|
70
70
|
include AccessGranted::Policy
|
@@ -120,7 +120,7 @@ describe AccessGranted::Policy do
|
|
120
120
|
end
|
121
121
|
end
|
122
122
|
expect(klass.new(@member).can?(:create, String)).to eq(true)
|
123
|
-
expect(klass.new(@banned).
|
123
|
+
expect(klass.new(@banned).can?(:create, String)).to eq(false)
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
data/spec/spec_helper.rb
CHANGED
@@ -23,10 +23,12 @@ end
|
|
23
23
|
|
24
24
|
require 'access-granted'
|
25
25
|
|
26
|
-
class
|
26
|
+
class FakeUser < Struct.new(:id, :is_moderator, :is_admin, :is_banned)
|
27
|
+
end
|
28
|
+
|
29
|
+
class FakePost < Struct.new(:id, :user_id)
|
27
30
|
end
|
28
31
|
|
29
32
|
class AccessPolicy
|
30
33
|
include AccessGranted::Policy
|
31
34
|
end
|
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: 1.0
|
4
|
+
version: 1.1.0
|
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:
|
11
|
+
date: 2016-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
89
|
version: '0'
|
90
90
|
requirements: []
|
91
91
|
rubyforge_project:
|
92
|
-
rubygems_version: 2.4.5
|
92
|
+
rubygems_version: 2.4.5.1
|
93
93
|
signing_key:
|
94
94
|
specification_version: 4
|
95
95
|
summary: Elegant whitelist and role based authorization with ability to prioritize
|
@@ -100,4 +100,3 @@ test_files:
|
|
100
100
|
- spec/policy_spec.rb
|
101
101
|
- spec/role_spec.rb
|
102
102
|
- spec/spec_helper.rb
|
103
|
-
has_rdoc:
|