access-granted 1.0.4 → 1.1.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/.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
|
## [](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:
|