access-granted 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/pokonski/access-granted.png?branch=master)](https://travis-ci.org/pokonski/access-granted) [![Code Climate](https://codeclimate.com/github/pokonski/access-granted.png)](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:
|