access-granted 0.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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +7 -0
- data/access-granted.gemspec +26 -0
- data/lib/access-granted.rb +16 -0
- data/lib/access-granted/controller_methods.rb +24 -0
- data/lib/access-granted/exceptions.rb +7 -0
- data/lib/access-granted/permission.rb +47 -0
- data/lib/access-granted/policy.rb +56 -0
- data/lib/access-granted/role.rb +91 -0
- data/lib/access-granted/version.rb +3 -0
- data/spec/controller_methods_spec.rb +42 -0
- data/spec/permission_spec.rb +56 -0
- data/spec/policy_spec.rb +105 -0
- data/spec/role_spec.rb +95 -0
- data/spec/spec_helper.rb +26 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 990382c04aeeeb43c67adcd3adc1fc0e4ac6b7ac
|
4
|
+
data.tar.gz: d9970ae5c29e073953f41ec55c3f19b8a0d18225
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ddcadc2b5e812ae350e91479047c344d705e6cb7f3011f35e695aab0ca545402a550c1e9f8c45661e64cce0228b0f44334c384615721eb2eca38938d0ea501e
|
7
|
+
data.tar.gz: 9060e6c5d8243a43f33dfe9a4b6274a68edd748d1e32930939c5c7097db0679be03f21a168ae98f031806696da654a4806c3844f866ac2aadc4ccde6f5102b51
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Piotrek Okoński
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# AccessGranted [](https://travis-ci.org/pokonski/access-granted) [](https://codeclimate.com/github/pokonski/access-granted)
|
2
|
+
|
3
|
+
Multi-role and whitelist based authorization gem for Rails. And it's lightweight (~300 lines of code)!
|
4
|
+
|
5
|
+
# Summary
|
6
|
+
|
7
|
+
AccessGranted is meant as replacement for CanCan to solve three major problems:
|
8
|
+
|
9
|
+
1. built-in support for roles
|
10
|
+
|
11
|
+
Easy to read acess policy code where permissions are cleanly grouped into roles which may or may not apply to a user.
|
12
|
+
Additionally permissions are forced to be unique in the scope a role greatly simplifying the
|
13
|
+
permission resolving and extremely reducing the code-base.
|
14
|
+
|
15
|
+
2. white-list based
|
16
|
+
|
17
|
+
This means that you define what a role **can** do,
|
18
|
+
not overidding permissions with `cannot` in a specific order which results in an ugly and unmaintainable code.
|
19
|
+
|
20
|
+
3. Permissions can work on basically any object and AccessGranted is framework-agnostic,
|
21
|
+
(the only Rails-specific methods are `can?`/`cannot?`/`authorize!` helpers injected
|
22
|
+
into the framework only when it's present).
|
23
|
+
|
24
|
+
See [Usage](#usage) for an example of a complete AccessPolicy file.
|
25
|
+
|
26
|
+
## Compatibility
|
27
|
+
|
28
|
+
This gem was created as a replacement for CanCan and therefore it requires minimum work to switch.
|
29
|
+
|
30
|
+
1. Both `can?`/`cannot?` and `authorize!` methods work in Rails controllers and views, so
|
31
|
+
**you don't have to adjust your views at all**.
|
32
|
+
2. Syntax for defining permissions in AccessPolicy file (Ability in CanCan) is exactly the same,
|
33
|
+
with added roles on top. See [Usage](#usage) below.
|
34
|
+
3. **Main difference**: AccessGranted does not extend ActiveRecord in any way, so it does not have the `accessible_by?`
|
35
|
+
method to keep the code as simple as possible.
|
36
|
+
That is because `accessible_by?` was very limited making it useless in most cases (complex permissions with lambdas).
|
37
|
+
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
Add this line to your application's Gemfile:
|
42
|
+
|
43
|
+
gem 'access-granted'
|
44
|
+
|
45
|
+
And then execute:
|
46
|
+
|
47
|
+
$ bundle
|
48
|
+
|
49
|
+
Or install it yourself as:
|
50
|
+
|
51
|
+
$ gem install access-granted
|
52
|
+
|
53
|
+
## Usage
|
54
|
+
|
55
|
+
Roles are defined using blocks (or by passing custom classes to keep things tidy).
|
56
|
+
Order of the roles is important, because they are being traversed in the top-to-bottom order. Generally at the top you will have
|
57
|
+
an admin or other important role giving the user top permissions, and as you go down you define less-privileged roles.
|
58
|
+
|
59
|
+
See full example:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class Policy
|
63
|
+
include AccessGranted::Policy
|
64
|
+
|
65
|
+
def configure(user)
|
66
|
+
# The most important role prohibiting banned
|
67
|
+
# users from doing anything.
|
68
|
+
# (even if they are moderators or admins)
|
69
|
+
role :banned, { is_banned: true } do
|
70
|
+
cannot [:create, :update, :destroy], Post
|
71
|
+
|
72
|
+
# same as above, :manage is just a shortcut for
|
73
|
+
# `[:create, :update, :destroy]`
|
74
|
+
cannot :manage, Comment
|
75
|
+
end
|
76
|
+
|
77
|
+
# Takes precedences over roles placed lower
|
78
|
+
# and explicitly lets admin mamange everything.
|
79
|
+
role :admin, { is_admin: true } do
|
80
|
+
can :manage, Post
|
81
|
+
can :manage, Comment
|
82
|
+
end
|
83
|
+
|
84
|
+
# You can also use Procs to determine
|
85
|
+
# if the role should apply to a given user.
|
86
|
+
role :moderator, proc {|u| u.moderator? } do
|
87
|
+
# overwrites permission that only allows removing own content in :member
|
88
|
+
# and lets moderators edit and delete all posts
|
89
|
+
can [:update, :destroy], Post
|
90
|
+
|
91
|
+
# and a new permission which lets moderators
|
92
|
+
# modify user accounts
|
93
|
+
can :update, User
|
94
|
+
end
|
95
|
+
|
96
|
+
# Applies to everyone logged in.
|
97
|
+
# The basic role.
|
98
|
+
role :member do
|
99
|
+
can :create, Post
|
100
|
+
|
101
|
+
# For more advanced permissions
|
102
|
+
# you must use blocks. Hash
|
103
|
+
# conditions should be used for
|
104
|
+
# simple checks only.
|
105
|
+
can [:update, :destroy], Post do |post|
|
106
|
+
post.user_id == user.id && post.comments.empty?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
|
114
|
+
## Contributing
|
115
|
+
|
116
|
+
1. Fork it
|
117
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
118
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
119
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
120
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'access-granted/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "access-granted"
|
8
|
+
spec.version = AccessGranted::VERSION
|
9
|
+
spec.authors = ["Piotrek Okoński"]
|
10
|
+
spec.email = ["piotrek@okonski.org"]
|
11
|
+
spec.description = %q{Whitelist and role based authorization gem}
|
12
|
+
spec.summary = %q{Elegant whitelist and role based authorization with ability to prioritize roles.}
|
13
|
+
spec.homepage = "https://github.com/pokonski/access-granted"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "text-table"
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "access-granted/version"
|
2
|
+
require "access-granted/exceptions"
|
3
|
+
require "access-granted/policy"
|
4
|
+
require "access-granted/permission"
|
5
|
+
require "access-granted/controller_methods"
|
6
|
+
require "access-granted/role"
|
7
|
+
|
8
|
+
module AccessGranted
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
if defined? ActionController::Base
|
13
|
+
ActionController::Base.class_eval do
|
14
|
+
include AccessGranted::ControllerMethods
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AccessGranted
|
2
|
+
module ControllerMethods
|
3
|
+
def current_policy
|
4
|
+
@current_policy ||= ::Policy.new(current_user)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.helper_method :can?, :cannot?, :current_ability
|
9
|
+
end
|
10
|
+
|
11
|
+
def can?(*args)
|
12
|
+
current_policy.can?(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def cannot?(*args)
|
16
|
+
current_policy.cannot?(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def authorize!(*args)
|
20
|
+
current_policy.authorize!(*args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module AccessGranted
|
2
|
+
class Permission
|
3
|
+
attr_reader :action, :subject, :granted, :conditions
|
4
|
+
|
5
|
+
def initialize(granted, action, subject, conditions = {}, block = nil)
|
6
|
+
@action = action
|
7
|
+
@granted = granted
|
8
|
+
@subject = subject
|
9
|
+
@conditions = conditions
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches_action?(action)
|
14
|
+
@action == action
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches_subject?(subject)
|
18
|
+
@subject == subject || subject.class == @subject
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches_conditions?(subject)
|
22
|
+
if @block
|
23
|
+
@block.call(subject)
|
24
|
+
else
|
25
|
+
matches_hash_conditions?(subject)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches_hash_conditions?(subject)
|
30
|
+
@conditions.each_pair do |name, value|
|
31
|
+
return false if subject.send(name) != value
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def eql?(other)
|
37
|
+
other.class == self.class &&
|
38
|
+
@action == other.action &&
|
39
|
+
@subject == other.subject &&
|
40
|
+
@granted == other.granted
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
eql?(other)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module AccessGranted
|
2
|
+
module Policy
|
3
|
+
attr_accessor :roles
|
4
|
+
|
5
|
+
def initialize(user)
|
6
|
+
@user = user
|
7
|
+
@roles = []
|
8
|
+
@last_priority = 0
|
9
|
+
configure(@user)
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure(user)
|
13
|
+
end
|
14
|
+
|
15
|
+
def role(name, conditions_or_klass = nil, conditions = nil, &block)
|
16
|
+
name = name.to_sym
|
17
|
+
if roles.select {|r| r.name == name }.any?
|
18
|
+
raise DuplicateRole, "Role '#{name}' already defined"
|
19
|
+
end
|
20
|
+
@last_priority += 1
|
21
|
+
r = if conditions_or_klass.is_a?(Class) && conditions_or_klass <= AccessGranted::Role
|
22
|
+
conditions_or_klass.new(name, @last_priority, conditions, @user, block)
|
23
|
+
else
|
24
|
+
Role.new(name, @last_priority, conditions_or_klass, @user, block)
|
25
|
+
end
|
26
|
+
roles << r
|
27
|
+
roles.sort_by! {|r| r.priority }
|
28
|
+
r
|
29
|
+
end
|
30
|
+
|
31
|
+
def can?(action, subject)
|
32
|
+
match_roles(@user).each do |role|
|
33
|
+
permission = role.find_permission(action, subject)
|
34
|
+
return permission.granted if permission
|
35
|
+
end
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def cannot?(*args)
|
40
|
+
!can?(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def match_roles(user)
|
44
|
+
roles.select do |role|
|
45
|
+
role.applies_to?(user)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def authorize!(action, subject)
|
50
|
+
if cannot?(action, subject)
|
51
|
+
raise AccessDenied
|
52
|
+
end
|
53
|
+
subject
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module AccessGranted
|
2
|
+
class Role
|
3
|
+
attr_reader :name, :user, :priority, :conditions, :permissions
|
4
|
+
|
5
|
+
def initialize(name, priority, conditions = nil, user = nil, block = nil)
|
6
|
+
@user = user
|
7
|
+
@name = name
|
8
|
+
@priority = priority
|
9
|
+
@conditions = conditions
|
10
|
+
@block = block
|
11
|
+
@permissions = []
|
12
|
+
@permissions_by_action = {}
|
13
|
+
if @block
|
14
|
+
instance_eval(&@block)
|
15
|
+
else
|
16
|
+
configure(@user)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(user)
|
21
|
+
end
|
22
|
+
|
23
|
+
def can(action, subject, conditions = {}, &block)
|
24
|
+
add_permission(true, action, subject, conditions, block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def cannot(action, subject, conditions = {}, &block)
|
28
|
+
add_permission(false, action, subject, conditions, block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def can?(action, subject)
|
32
|
+
permission = find_permission(action, subject)
|
33
|
+
permission ? permission.granted : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_permission(action, subject)
|
37
|
+
relevant_permissions(action, subject).detect do |permission|
|
38
|
+
permission.matches_conditions?(subject)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def applies_to?(user)
|
43
|
+
case @conditions
|
44
|
+
when Hash
|
45
|
+
matches_hash(user, @conditions)
|
46
|
+
when Proc
|
47
|
+
@conditions.call(user)
|
48
|
+
else
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def relevant_permissions(action, subject)
|
55
|
+
permissions_by_action(action).select do |perm|
|
56
|
+
perm.matches_subject?(subject)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def matches_hash(user, conditions = {})
|
61
|
+
conditions.all? do |name, value|
|
62
|
+
user.send(name) == value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_permission(granted, action, subject, conditions, block)
|
67
|
+
prepare_actions(action).each do |a|
|
68
|
+
raise DuplicatePermission if relevant_permissions(a, subject).any?
|
69
|
+
@permissions << Permission.new(granted, a, subject, conditions, block)
|
70
|
+
@permissions_by_action[a] ||= []
|
71
|
+
@permissions_by_action[a] << @permissions.size - 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def prepare_actions(action)
|
78
|
+
if action == :manage
|
79
|
+
actions = [:create, :update, :destroy]
|
80
|
+
else
|
81
|
+
actions = [action].flatten
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def permissions_by_action(action)
|
86
|
+
(@permissions_by_action[action] || []).map do |index|
|
87
|
+
@permissions[index]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AccessGranted::ControllerMethods do
|
4
|
+
before(:each) do
|
5
|
+
@current_user = double("User")
|
6
|
+
@controller_class = Class.new
|
7
|
+
@controller = @controller_class.new
|
8
|
+
@controller_class.stub(:helper_method).with(:can?, :cannot?, :current_ability)
|
9
|
+
@controller_class.send(:include, AccessGranted::ControllerMethods)
|
10
|
+
@controller.stub(:current_user).and_return(@current_user)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have current_policy method returning Policy instance" do
|
14
|
+
@controller.current_policy.should be_kind_of(AccessGranted::Policy)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "provides can? and cannot? method delegated to current_policy" do
|
18
|
+
@controller.can?(:read, String).should be_false
|
19
|
+
@controller.cannot?(:read, String).should be_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
|
+
@controller.stub(:current_policy).and_return(policy)
|
39
|
+
@controller.authorize!(:read, String).should == String
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AccessGranted::Permission do
|
4
|
+
subject { AccessGranted::Permission }
|
5
|
+
|
6
|
+
describe "#matches_conditions?" do
|
7
|
+
it "matches when no conditions given" do
|
8
|
+
perm = subject.new(true, :read, String)
|
9
|
+
perm.matches_conditions?(String).should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "matches proc conditions" do
|
13
|
+
sub = double("Element", published?: true)
|
14
|
+
perm = subject.new(true, :read, sub.class, {}, proc {|el| el.published? })
|
15
|
+
perm.matches_conditions?(sub).should be_true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#matches_hash_conditions?" do
|
20
|
+
it "matches condition hash is empty" do
|
21
|
+
perm = subject.new(true, :read, String)
|
22
|
+
perm.matches_hash_conditions?(String).should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "matches when conditions given" do
|
26
|
+
sub = double("Element", published: true)
|
27
|
+
perm = subject.new(true, :read, sub, { published: true })
|
28
|
+
perm.matches_hash_conditions?(sub).should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "does not match if one of the conditions mismatches" do
|
32
|
+
sub = double("Element", published: true, readable: false)
|
33
|
+
perm = subject.new(true, :read, sub, { published: true, readable: true })
|
34
|
+
perm.matches_hash_conditions?(sub).should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#matches_action?" do
|
39
|
+
it "matches if actions are identical" do
|
40
|
+
perm = subject.new(true, :read, String)
|
41
|
+
perm.matches_action?(:read).should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#matches_subject?" do
|
46
|
+
it "matches if subjects are identical" do
|
47
|
+
perm = subject.new(true, :read, String)
|
48
|
+
expect(perm.matches_subject? String).to be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "matches if class is equal to subject" do
|
52
|
+
perm = subject.new(true, :read, String)
|
53
|
+
expect(perm.matches_subject? "test").to be_true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/policy_spec.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AccessGranted::Policy do
|
4
|
+
before :each do
|
5
|
+
@policy = Class.new do
|
6
|
+
include AccessGranted::Policy
|
7
|
+
end.new(nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#configure" do
|
11
|
+
before :each do
|
12
|
+
@member = double("member", is_moderator: false, is_admin: false, is_banned: false)
|
13
|
+
@mod = double("moderator", is_moderator: true, is_admin: false, is_banned: false)
|
14
|
+
@admin = double("administrator", is_moderator: false, is_admin: true, is_banned: false)
|
15
|
+
@banned = double("banned", is_moderator: false, is_admin: true, is_banned: true)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "selects permission based on role priority" do
|
19
|
+
klass = Class.new do
|
20
|
+
include AccessGranted::Policy
|
21
|
+
|
22
|
+
def configure(user)
|
23
|
+
role :member do
|
24
|
+
can :read, String
|
25
|
+
end
|
26
|
+
|
27
|
+
role :moderator, { is_moderator: true } do
|
28
|
+
can :edit, String
|
29
|
+
end
|
30
|
+
|
31
|
+
role :administrator, { is_admin: true } do
|
32
|
+
can :destroy, String
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
klass.new(@member).cannot?(:destroy, String).should be_true
|
37
|
+
klass.new(@admin).can?(:destroy, String).should be_true
|
38
|
+
klass.new(@admin).can?(:read, String).should be_true
|
39
|
+
klass.new(@mod).cannot?(:destroy, String).should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#cannot" do
|
43
|
+
it "forbids action when used in higher role" do
|
44
|
+
klass = Class.new do
|
45
|
+
include AccessGranted::Policy
|
46
|
+
|
47
|
+
def configure(user)
|
48
|
+
role :banned, { is_banned: true } do
|
49
|
+
cannot :create, String
|
50
|
+
end
|
51
|
+
|
52
|
+
role :member do
|
53
|
+
can :create, String
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
klass.new(@member).can?(:create, String).should be_true
|
58
|
+
klass.new(@banned).can?(:create, String).should be_false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#role" do
|
64
|
+
it "allows passing role class" do
|
65
|
+
klass_role = Class.new AccessGranted::Role do
|
66
|
+
def configure(user)
|
67
|
+
can :read, String
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@policy.role(:member, klass_role)
|
71
|
+
@policy.roles.first.class.should == klass_role
|
72
|
+
end
|
73
|
+
|
74
|
+
it "allows defining a default role" do
|
75
|
+
@policy.role(:member)
|
76
|
+
@policy.roles.map(&:name).should include(:member)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "does not allow duplicate role names" do
|
80
|
+
@policy.role(:member)
|
81
|
+
expect { @policy.role(:member) }.to raise_error AccessGranted::DuplicateRole
|
82
|
+
end
|
83
|
+
|
84
|
+
it "allows nesting `can` calls inside a block" do
|
85
|
+
role = @policy.role(:member) do
|
86
|
+
can :read, String
|
87
|
+
end
|
88
|
+
|
89
|
+
role.can?(:read, String).should be_true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#match_roles" do
|
94
|
+
it "returns all matching roles in the order of priority" do
|
95
|
+
user = double("User", is_moderator: true, is_admin: true)
|
96
|
+
|
97
|
+
@policy.role(:administrator, { is_admin: true })
|
98
|
+
@policy.role(:moderator, { is_moderator: true })
|
99
|
+
@policy.role(:member)
|
100
|
+
|
101
|
+
@policy.match_roles(user).map(&:name).should == [:administrator, :moderator, :member]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/spec/role_spec.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AccessGranted::Role do
|
4
|
+
subject { AccessGranted::Role }
|
5
|
+
|
6
|
+
it "requires a role name" do
|
7
|
+
expect { subject.new }.to raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it "requires priority" do
|
11
|
+
expect { subject.new(:member) }.to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "creates a default role without conditions" do
|
15
|
+
subject.new(:member, 1).conditions.should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#relevant_permissions?" do
|
19
|
+
it "returns only matching permissions" do
|
20
|
+
role = subject.new(:member, 1)
|
21
|
+
role.can :read, String
|
22
|
+
role.can :read, Hash
|
23
|
+
role.relevant_permissions(:read, String).should == [AccessGranted::Permission.new(true, :read, String)]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#applies_to?" do
|
28
|
+
it "matches user when no conditions given" do
|
29
|
+
role = subject.new(:member, 1)
|
30
|
+
user = double("User")
|
31
|
+
role.applies_to?(user).should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "matches user by hash conditions" do
|
35
|
+
role = subject.new(:moderator, 1, { is_moderator: true })
|
36
|
+
user = double("User", is_moderator: true)
|
37
|
+
role.applies_to?(user).should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "doesn't match user if any of hash conditions is not met" do
|
41
|
+
role = subject.new(:moderator, 1, { is_moderator: true, is_admin: true })
|
42
|
+
user = double("User", is_moderator: true, is_admin: false)
|
43
|
+
role.applies_to?(user).should be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "matches user by Proc conditions" do
|
47
|
+
role = subject.new(:moderator, 1, proc {|user| user.is_moderator? })
|
48
|
+
user = double("User", is_moderator?: true)
|
49
|
+
role.applies_to?(user).should be_true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#can" do
|
54
|
+
before :each do
|
55
|
+
@role = AccessGranted::Role.new(:member, 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "forbids creating actions with the same name" do
|
59
|
+
@role.can :read, String
|
60
|
+
expect { @role.can :read, String }.to raise_error AccessGranted::DuplicatePermission
|
61
|
+
end
|
62
|
+
|
63
|
+
it "accepts :manage shortcut for CRUD actions" do
|
64
|
+
@role.can :manage, String
|
65
|
+
@role.permissions.map(&:action).should include(:create, :update, :destroy)
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "when action is an Array" do
|
69
|
+
it "creates multiple permissions" do
|
70
|
+
@role.can [:read, :create], String
|
71
|
+
@role.permissions.should have(2).items
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "when no conditions given" do
|
76
|
+
it "should be able to read a class" do
|
77
|
+
@role.can :read, String
|
78
|
+
@role.can?(:read, String).should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be able to read instance of class" do
|
82
|
+
@role.can :read, String
|
83
|
+
@role.can?(:read, "text").should be_true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "when conditions given" do
|
88
|
+
it "should be able to read when conditions match" do
|
89
|
+
sub = double("Element", published: true)
|
90
|
+
@role.can :read, sub.class, { published: true }
|
91
|
+
@role.can?(:read, sub).should be_true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
config.order = 'random'
|
13
|
+
end
|
14
|
+
|
15
|
+
module ActionController
|
16
|
+
class Base
|
17
|
+
def self.helper_method(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
require 'access-granted'
|
22
|
+
|
23
|
+
class Policy
|
24
|
+
include AccessGranted::Policy
|
25
|
+
end
|
26
|
+
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: access-granted
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotrek Okoński
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: text-table
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Whitelist and role based authorization gem
|
84
|
+
email:
|
85
|
+
- piotrek@okonski.org
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- .gitignore
|
91
|
+
- .rspec
|
92
|
+
- .travis.yml
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- access-granted.gemspec
|
98
|
+
- lib/access-granted.rb
|
99
|
+
- lib/access-granted/controller_methods.rb
|
100
|
+
- lib/access-granted/exceptions.rb
|
101
|
+
- lib/access-granted/permission.rb
|
102
|
+
- lib/access-granted/policy.rb
|
103
|
+
- lib/access-granted/role.rb
|
104
|
+
- lib/access-granted/version.rb
|
105
|
+
- spec/controller_methods_spec.rb
|
106
|
+
- spec/permission_spec.rb
|
107
|
+
- spec/policy_spec.rb
|
108
|
+
- spec/role_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
homepage: https://github.com/pokonski/access-granted
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.0.3
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Elegant whitelist and role based authorization with ability to prioritize
|
134
|
+
roles.
|
135
|
+
test_files:
|
136
|
+
- spec/controller_methods_spec.rb
|
137
|
+
- spec/permission_spec.rb
|
138
|
+
- spec/policy_spec.rb
|
139
|
+
- spec/role_spec.rb
|
140
|
+
- spec/spec_helper.rb
|
141
|
+
has_rdoc:
|