authorule 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +22 -0
- data/README.md +123 -0
- data/Rakefile +7 -0
- data/authorule.gemspec +27 -0
- data/lib/authorule/permission.rb +123 -0
- data/lib/authorule/permission_accessors.rb +47 -0
- data/lib/authorule/permission_holder.rb +55 -0
- data/lib/authorule/railtie.rb +21 -0
- data/lib/authorule/rule.rb +111 -0
- data/lib/authorule/rule_base.rb +141 -0
- data/lib/authorule/version.rb +3 -0
- data/lib/authorule.rb +79 -0
- data/lib/tasks/permissions.rake +17 -0
- data/spec/authorule/permission_holder_spec.rb +137 -0
- data/spec/authorule/rule_base_spec.rb +127 -0
- data/spec/authorule_spec.rb +113 -0
- data/spec/spec_helper.rb +19 -0
- metadata +143 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
module Authorule
|
2
|
+
|
3
|
+
# A permission rule base. This class performs the heart of the permission checking algorithms.
|
4
|
+
#
|
5
|
+
# == Algorithm description
|
6
|
+
#
|
7
|
+
# A rule base is always queried for one permission. The result should be whether it is allowed or denied.
|
8
|
+
#
|
9
|
+
# When running a permission through the rule base, the permission itself, and all dependent permissions
|
10
|
+
# are run through the rule base. See {Permission#dependencies} for more info about permission dependencies.
|
11
|
+
#
|
12
|
+
# The last defined rule (i.e. the rule with the highest priority) matching *any* of the checks is taken
|
13
|
+
# as the deciding rule.
|
14
|
+
#
|
15
|
+
# == Example
|
16
|
+
#
|
17
|
+
# Let's illustrate the given algorithm with an example. Let's say we have the following permissions, taken
|
18
|
+
# from the UI library:
|
19
|
+
#
|
20
|
+
# * +SpacePermission+: a permission to access a certain UI space (a collection of UI resources)
|
21
|
+
# * +ResourcePermission+: a permission to access a certain resource
|
22
|
+
#
|
23
|
+
# A resource permission has a dependency on its corresponding space permission. In other words, a user must
|
24
|
+
# have access to the resource's space as well as the resource itself for it to be accessible.
|
25
|
+
#
|
26
|
+
# Then, we consider a rule base with the following rules:
|
27
|
+
#
|
28
|
+
# 1. Deny all
|
29
|
+
# 2. Allow space 'CRM'
|
30
|
+
# 3. Deny resource 'Account'
|
31
|
+
#
|
32
|
+
# Now, we need to check whether the user may view the resource 'Account':
|
33
|
+
#
|
34
|
+
# permission = ResourcePermission.new(account_resource, :view)
|
35
|
+
#
|
36
|
+
# When resolving all dependencies, we end up with the following list of permissions:
|
37
|
+
#
|
38
|
+
# permissions = permission.resolve_dependencies
|
39
|
+
# # => [ SpacePermission.new(crm_space), ResourcePermission.new(account_resource, :view) ]
|
40
|
+
#
|
41
|
+
# Both of these permissions are now run through the rule base. The first permission is matched by rules
|
42
|
+
# 1 (all) and 2 (space 'CRM'). The second permission is matched by rules 1 (all) and 3 (resource 'Account').
|
43
|
+
# The last defined rule is the third rule. As it is set to deny access, the resulting access to the permission
|
44
|
+
# is denied. This makes sense because we deny it last in line.
|
45
|
+
#
|
46
|
+
# If however, the rule base were to switch around 2 and 3, the rule base would look as follows:
|
47
|
+
#
|
48
|
+
# 1. Deny all
|
49
|
+
# 2. Deny resource 'Account'
|
50
|
+
# 3. Allow space 'CRM'
|
51
|
+
#
|
52
|
+
# Now, the first permission will be matched by rule 3, which is the last rule to match. As it is set to allow
|
53
|
+
# access, the resulting access to the permission is allowed. As you can see, the second rule is overruled by the
|
54
|
+
# more generic rule 3.
|
55
|
+
class RuleBase
|
56
|
+
|
57
|
+
######
|
58
|
+
# Initialization
|
59
|
+
|
60
|
+
# Initializes the rule base with the given rules.
|
61
|
+
def initialize(rules)
|
62
|
+
@rules = rules.to_a
|
63
|
+
@index = build_index
|
64
|
+
end
|
65
|
+
|
66
|
+
######
|
67
|
+
# Attributes
|
68
|
+
|
69
|
+
# @!attribute [r] rules
|
70
|
+
# @return [Array] The rules in the rule base.
|
71
|
+
attr_reader :rules
|
72
|
+
|
73
|
+
######
|
74
|
+
# Index
|
75
|
+
|
76
|
+
# Builds an index that maps a permission key into a rule index.
|
77
|
+
def build_index
|
78
|
+
index = {}
|
79
|
+
|
80
|
+
rules.each_with_index do |rule, idx|
|
81
|
+
key = rule.key
|
82
|
+
|
83
|
+
# Use this to make sure any duplicate entry uses the maximum index (i.e. last defined rule).
|
84
|
+
index[key] = [ index[key], idx ].compact.max
|
85
|
+
end
|
86
|
+
index
|
87
|
+
end
|
88
|
+
private :build_index
|
89
|
+
|
90
|
+
######
|
91
|
+
# Runner
|
92
|
+
|
93
|
+
# Runs the given permission through the rule base.
|
94
|
+
#
|
95
|
+
# @return [true|false] +true+ if the permission is allowed, +false+ if not.
|
96
|
+
def run(permission)
|
97
|
+
last_rule_index = nil
|
98
|
+
|
99
|
+
permission.with_dependencies.each do |permission|
|
100
|
+
keys = permission_checks(permission)
|
101
|
+
|
102
|
+
# Compare the current index with the indices of all rules that match and take the maximum.
|
103
|
+
last_rule_index = ([ last_rule_index ] + keys.map{ |key| @index[key] }.flatten).compact.max
|
104
|
+
end
|
105
|
+
|
106
|
+
if last_rule_index
|
107
|
+
rules[last_rule_index].allow?
|
108
|
+
else
|
109
|
+
# The default policy is to deny the permission if no rules match.
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Determines all permission checks for the given permission.
|
115
|
+
def permission_checks(permission)
|
116
|
+
keys = []
|
117
|
+
|
118
|
+
if permission.action
|
119
|
+
# Add '<kind>:<name>:<action>'
|
120
|
+
keys << [ permission.kind, permission.name, permission.action ].join(':')
|
121
|
+
|
122
|
+
# Add '<kind>:all(:<action>'
|
123
|
+
keys << [ permission.kind, 'all', permission.action ].join(':')
|
124
|
+
end
|
125
|
+
|
126
|
+
# Add '<kind>:<name>'
|
127
|
+
keys << [ permission.kind, permission.name ].join(':')
|
128
|
+
|
129
|
+
# Add '<kind>:all'
|
130
|
+
keys << [ permission.kind, 'all' ].join(':')
|
131
|
+
|
132
|
+
# Add 'all'
|
133
|
+
keys << 'all'
|
134
|
+
|
135
|
+
keys
|
136
|
+
end
|
137
|
+
private :permission_checks
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/lib/authorule.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
|
4
|
+
module Authorule
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
class PermissionResolutionError < RuntimeError
|
8
|
+
end
|
9
|
+
|
10
|
+
autoload :Permission
|
11
|
+
autoload :Rule
|
12
|
+
autoload :RuleBase
|
13
|
+
autoload :PermissionAccessors
|
14
|
+
autoload :PermissionHolder
|
15
|
+
|
16
|
+
######
|
17
|
+
# Permission registration
|
18
|
+
|
19
|
+
class DuplicatePermission < RuntimeError
|
20
|
+
end
|
21
|
+
|
22
|
+
@@permission_classes = {}
|
23
|
+
mattr_reader :permission_classes
|
24
|
+
|
25
|
+
def self.register(kind, klass)
|
26
|
+
kind = kind.to_sym
|
27
|
+
unless klass < Permission
|
28
|
+
raise ArgumentError, "class #{klass.name} cannot be registered as a permission kind: it should be derived from Authorule::Permission"
|
29
|
+
end
|
30
|
+
if Authorule.permission_classes[kind]
|
31
|
+
raise DuplicatePermission, "another permission class has already been registered for kind :#{kind}"
|
32
|
+
end
|
33
|
+
|
34
|
+
permission_classes[kind] = klass
|
35
|
+
end
|
36
|
+
|
37
|
+
######
|
38
|
+
# Permission resolution
|
39
|
+
|
40
|
+
# Resolves a target. Tries all registered permission classes with a resolve block, and runs the target
|
41
|
+
# through the block. If anythin is returned, it is passed into the constructor of that permission class.
|
42
|
+
def self.resolve(target, action = nil)
|
43
|
+
return target if target.is_a?(Authorule::Permission)
|
44
|
+
|
45
|
+
permission_classes.values.each do |klass|
|
46
|
+
next unless klass.resolve_block
|
47
|
+
resolved = klass.resolve_block.call(target)
|
48
|
+
|
49
|
+
return klass.new(resolved, action) if resolved
|
50
|
+
end
|
51
|
+
|
52
|
+
# If we got here, no schema returned a matching permission.
|
53
|
+
raise PermissionResolutionError, "target #{target} could not be resolved into a permission"
|
54
|
+
end
|
55
|
+
|
56
|
+
######
|
57
|
+
# Permission listing
|
58
|
+
|
59
|
+
# Retrieves all available permissions, organized by their kind, into a hash.
|
60
|
+
#
|
61
|
+
# @return [Hash<Symbol,Array<Permission>>] An organized list of permissions.
|
62
|
+
def self.available_permissions
|
63
|
+
available_permissions = {}
|
64
|
+
|
65
|
+
permission_classes.each do |kind, klass|
|
66
|
+
next unless klass.list_block
|
67
|
+
|
68
|
+
available_permissions[kind] = []
|
69
|
+
objects = klass.list_block.call
|
70
|
+
|
71
|
+
objects.each do |object|
|
72
|
+
available_permissions[kind] << klass.new(object)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
available_permissions
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :authorule do
|
2
|
+
|
3
|
+
desc "Lists all available permissions"
|
4
|
+
task :list => :environment do
|
5
|
+
Authorule.available_permissions.each do |kind, permissions|
|
6
|
+
puts "#{kind}:"
|
7
|
+
permissions.each do |permission|
|
8
|
+
if permission.available_actions.blank?
|
9
|
+
puts " #{permission.name}"
|
10
|
+
else
|
11
|
+
puts " #{permission.name} (#{permission.available_actions.join(', ')})"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
describe Authorule::PermissionHolder do
|
5
|
+
|
6
|
+
let(:model) do
|
7
|
+
Class.new(ActiveRecord::Base) do
|
8
|
+
include Authorule::PermissionHolder
|
9
|
+
is_permission_holder!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
let(:record) do
|
13
|
+
record = Class.new()
|
14
|
+
record.class.send(:include, Authorule::PermissionHolder)
|
15
|
+
record.class.stub(:has_many)
|
16
|
+
record.class.is_permission_holder!
|
17
|
+
record
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should add a 'rules' association" do
|
21
|
+
association = model.reflect_on_association(:permission_rules)
|
22
|
+
|
23
|
+
association.should_not be_nil
|
24
|
+
association.macro.should == :has_many
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#permission_rule_base' do
|
28
|
+
|
29
|
+
it "should return a rule base based on the permission rules for the holder" do
|
30
|
+
rules = []
|
31
|
+
record.should_receive(:permission_rules).with(true).and_return(rules)
|
32
|
+
|
33
|
+
record.permission_rule_base.should be_a(Authorule::RuleBase)
|
34
|
+
record.permission_rule_base.rules.should be(rules)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should cache its value" do
|
38
|
+
rules = []
|
39
|
+
record.should_receive(:permission_rules).with(true).once.and_return(rules)
|
40
|
+
|
41
|
+
base = record.permission_rule_base
|
42
|
+
record.permission_rule_base.should be(base)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not cache its value when reload=false" do
|
46
|
+
rules = []
|
47
|
+
record.should_receive(:permission_rules).with(true).twice.and_return(rules)
|
48
|
+
|
49
|
+
base = record.permission_rule_base
|
50
|
+
record.permission_rule_base(true).should_not be(base)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'has_permission?' do
|
56
|
+
let(:rule_base) { double() }
|
57
|
+
before { record.should_receive(:permission_rule_base).and_return(rule_base) }
|
58
|
+
|
59
|
+
it "should run the given permission through the rule base, and return true if that returns true" do
|
60
|
+
permission = double()
|
61
|
+
rule_base.should_receive(:run).with(permission).and_return(true)
|
62
|
+
record.should have_permission(permission)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should run the given permission through the rule base, and return false if that returns false" do
|
66
|
+
permission = double()
|
67
|
+
rule_base.should_receive(:run).with(permission).and_return(false)
|
68
|
+
record.should_not have_permission(permission)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'may_* methods' do
|
73
|
+
|
74
|
+
let(:target) { double() }
|
75
|
+
let(:permission) { double() }
|
76
|
+
|
77
|
+
describe 'may_access?' do
|
78
|
+
|
79
|
+
it "should resolve the given argument to a permission, check it, and return what it returns" do
|
80
|
+
result = double()
|
81
|
+
|
82
|
+
Authorule.should_receive(:resolve).with(target).and_return(permission)
|
83
|
+
record.should_receive(:has_permission?).with(permission).and_return(result)
|
84
|
+
record.may_access?(target).should be(result)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'may?' do
|
90
|
+
|
91
|
+
before { Authorule.should_receive(:resolve).with(target, :view).and_return(permission) }
|
92
|
+
|
93
|
+
context "with a valid action" do
|
94
|
+
it "should resolve the given argument to a permission, check it, and return what it returns" do
|
95
|
+
permission.should_receive(:available_actions).and_return([:view])
|
96
|
+
|
97
|
+
result = double()
|
98
|
+
record.should_receive(:has_permission?).with(permission).and_return(result)
|
99
|
+
|
100
|
+
record.may?(:view, target).should be(result)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "with an invalid action" do
|
105
|
+
it "should raise an error" do
|
106
|
+
permission.should_receive(:available_actions).and_return([:create])
|
107
|
+
permission.should_receive(:class).and_return(double(:kind => :test))
|
108
|
+
expect { record.may?(:view, target) }.to raise_error(ArgumentError, "action :view not available for permission of kind :test")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#may_not_access?' do
|
115
|
+
it "should invert the result from may_access?" do
|
116
|
+
record.should_receive(:may_access?).with(target).and_return(false)
|
117
|
+
record.may_not_access?(target).should == true
|
118
|
+
end
|
119
|
+
it "should invert the result from may_access?" do
|
120
|
+
record.should_receive(:may_access?).with(target).and_return(true)
|
121
|
+
record.may_not_access?(target).should == false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#may_not?' do
|
126
|
+
it "should invert the result from may?" do
|
127
|
+
record.should_receive(:may?).with(:view, target).and_return(false)
|
128
|
+
record.may_not?(:view, target).should == true
|
129
|
+
end
|
130
|
+
it "should invert the result from may?" do
|
131
|
+
record.should_receive(:may?).with(:view, target).and_return(true)
|
132
|
+
record.may_not?(:view, target).should == false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Authorule::RuleBase do
|
4
|
+
|
5
|
+
let(:permission) { double() }
|
6
|
+
let(:rule_base) { Authorule::RuleBase.new(rules) }
|
7
|
+
|
8
|
+
context "providing no rules" do
|
9
|
+
let(:rules) { [] }
|
10
|
+
|
11
|
+
it "should deny all access" do
|
12
|
+
permission.should_receive(:with_dependencies).and_return([
|
13
|
+
double(:kind => :custom, :name => 'something', :action => nil)
|
14
|
+
])
|
15
|
+
rule_base.run(permission).should == false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "providing an 'allow all' rule" do
|
20
|
+
let(:rules) { [ double(:key => 'all', :allow? => true) ] }
|
21
|
+
|
22
|
+
it "should allow all access" do
|
23
|
+
permission.should_receive(:with_dependencies).and_return([
|
24
|
+
double(:kind => :custom, :name => 'something', :action => nil)
|
25
|
+
])
|
26
|
+
rule_base.run(permission).should == true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "providing a cascading rule set" do
|
31
|
+
let(:rules) do
|
32
|
+
[
|
33
|
+
double(:key => 'all', :allow? => false), # Deny all
|
34
|
+
double(:key => 'resource:all', :allow? => true), # Allow all resources
|
35
|
+
double(:key => 'resource:account', :allow? => false), # Deny access to resource 'account'
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should deny access to a non-resource permission" do
|
40
|
+
permission.should_receive(:with_dependencies).and_return([
|
41
|
+
double(:kind => :custom, :name => 'something', :action => nil)
|
42
|
+
])
|
43
|
+
rule_base.run(permission).should == false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should allow access to a non-account resource permission" do
|
47
|
+
permission.should_receive(:with_dependencies).and_return([
|
48
|
+
double(:kind => :resource, :name => 'contact', :action => nil)
|
49
|
+
])
|
50
|
+
rule_base.run(permission).should == true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should deny access to an account resource permission" do
|
54
|
+
permission.should_receive(:with_dependencies).and_return([
|
55
|
+
double(:kind => :resource, :name => 'account', :action => nil)
|
56
|
+
])
|
57
|
+
rule_base.run(permission).should == false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "using a permission with dependencies" do
|
62
|
+
|
63
|
+
let(:permission) do
|
64
|
+
# Create a permission that requires both access to space:crm as well as resource:account.
|
65
|
+
double(:with_dependencies => [
|
66
|
+
double(:kind => :space, :name => 'crm', :action => nil),
|
67
|
+
double(:kind => :resource, :name => 'account', :action => nil)
|
68
|
+
])
|
69
|
+
end
|
70
|
+
|
71
|
+
# deny CRM, allow account
|
72
|
+
let(:crm_rule) { double(:key => 'space:crm', :allow? => false) }
|
73
|
+
let(:account_rule) { double(:key => 'space:crm', :allow? => true) }
|
74
|
+
|
75
|
+
it "should allow access if the account rule is defined last" do
|
76
|
+
rule_base = Authorule::RuleBase.new([ crm_rule, account_rule ])
|
77
|
+
rule_base.run(permission).should == true
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should deny access if the CRM rule is defined last" do
|
81
|
+
rule_base = Authorule::RuleBase.new([ account_rule, crm_rule ])
|
82
|
+
rule_base.run(permission).should == false
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
context "targeting specific actions" do
|
88
|
+
|
89
|
+
let(:rules) do
|
90
|
+
[
|
91
|
+
double(:key => 'resource:all', :allow? => true), # Allow all resources
|
92
|
+
double(:key => 'resource:all:create', :allow? => false), # Deny creating all resources
|
93
|
+
double(:key => 'resource:account:create', :allow? => true), # Deny creating resource 'account'
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should allow viewing a 'contact' resource" do
|
98
|
+
permission.should_receive(:with_dependencies).and_return([
|
99
|
+
double(:kind => :resource, :name => 'contact', :action => :view)
|
100
|
+
])
|
101
|
+
rule_base.run(permission).should == true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should allow viewing an 'account' resource" do
|
105
|
+
permission.should_receive(:with_dependencies).and_return([
|
106
|
+
double(:kind => :resource, :name => 'account', :action => :view)
|
107
|
+
])
|
108
|
+
rule_base.run(permission).should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should deny creating a 'contact' resource" do
|
112
|
+
permission.should_receive(:with_dependencies).and_return([
|
113
|
+
double(:kind => :resource, :name => 'contact', :action => :create)
|
114
|
+
])
|
115
|
+
rule_base.run(permission).should == false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should allow creating an 'account' resource" do
|
119
|
+
permission.should_receive(:with_dependencies).and_return([
|
120
|
+
double(:kind => :resource, :name => 'account', :action => :create)
|
121
|
+
])
|
122
|
+
rule_base.run(permission).should == true
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Authorule do
|
4
|
+
|
5
|
+
# Stub the permission registry to prevent messing with the actual set up.
|
6
|
+
let(:registry) { Hash.new }
|
7
|
+
before { Authorule.stub(:permission_classes).and_return(registry) }
|
8
|
+
|
9
|
+
######
|
10
|
+
# Registration
|
11
|
+
|
12
|
+
describe '#register' do
|
13
|
+
|
14
|
+
it "should allow any class derived from Permission to be registered" do
|
15
|
+
klass = Class.new(Authorule::Permission)
|
16
|
+
|
17
|
+
Authorule.register :test, klass
|
18
|
+
registry[:test].should == klass
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not allow any other class to be registered" do
|
22
|
+
klass = Class.new
|
23
|
+
expect { Authorule.register :test, klass }.to raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not allow a registration for the same kind twice" do
|
27
|
+
klass = Class.new(Authorule::Permission)
|
28
|
+
|
29
|
+
Authorule.register :test, klass
|
30
|
+
expect { Authorule.register :test, klass }.to raise_error(Authorule::DuplicatePermission)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'Authorule::Permission.kind' do
|
37
|
+
|
38
|
+
# A bit outside the scope of this file, but it's part of registration.
|
39
|
+
it "should allow any Authorule::Permission derived class to register itself" do
|
40
|
+
klass = Class.new(Authorule::Permission)
|
41
|
+
|
42
|
+
Authorule.should_receive(:register).with(:test, klass)
|
43
|
+
klass.class_eval { register :test }
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
######
|
49
|
+
# Resolution & available permissions
|
50
|
+
|
51
|
+
describe '.resolve' do
|
52
|
+
let(:permission_class1) { Class.new(Authorule::Permission) }
|
53
|
+
let(:permission_class2) { Class.new(Authorule::Permission) }
|
54
|
+
before do
|
55
|
+
Authorule.stub(:permission_classes).and_return({})
|
56
|
+
Authorule.register :permission1, permission_class1
|
57
|
+
Authorule.register :permission2, permission_class2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should resolve any instance of Authorule::Permission into itself" do
|
61
|
+
permission = Authorule::Permission.new(double())
|
62
|
+
Authorule.resolve(permission).should be(permission)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise PermissionResolutionError if no permission classes were registered" do
|
66
|
+
Authorule.stub(:permission_classes).and_return({})
|
67
|
+
expect { Authorule.resolve(double()) }.to raise_error(Authorule::PermissionResolutionError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should raise PermissionResolutionError if no permission classes with resolution blocks were registered" do
|
71
|
+
expect { Authorule.resolve(double()) }.to raise_error(Authorule::PermissionResolutionError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should run through all registered permission classes and try to resolve the target - the first one found should be instantiated" do
|
75
|
+
target = double()
|
76
|
+
resolved = double()
|
77
|
+
permission_class1.stub(:resolve_block).and_return(->(tgt) { tgt != target ? resolved : nil })
|
78
|
+
permission_class2.stub(:resolve_block).and_return(->(tgt) { tgt == target ? resolved : nil })
|
79
|
+
|
80
|
+
permission = double()
|
81
|
+
permission_class2.should_receive(:new).with(resolved, :view).and_return(permission)
|
82
|
+
|
83
|
+
Authorule.resolve(target, :view).should be(permission)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '.available_permissions' do
|
88
|
+
let(:permission_class1) { Class.new(Authorule::Permission) }
|
89
|
+
let(:permission_class2) { Class.new(Authorule::Permission) }
|
90
|
+
before do
|
91
|
+
Authorule.stub(:permission_classes).and_return({})
|
92
|
+
Authorule.register :permission1, permission_class1
|
93
|
+
Authorule.register :permission2, permission_class2
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should include an item for each permission class having a list block" do
|
97
|
+
targets = [ :one, :two ]
|
98
|
+
permission_class1.stub(:list_block).and_return(proc { targets })
|
99
|
+
|
100
|
+
permissions = Authorule.available_permissions
|
101
|
+
permissions.should have(1).item
|
102
|
+
|
103
|
+
permissions[:permission1].should be_a(Array)
|
104
|
+
permissions[:permission1].should have(2).items
|
105
|
+
|
106
|
+
permissions[:permission1][0].should be_a(permission_class1)
|
107
|
+
permissions[:permission1][0].object.should == :one
|
108
|
+
permissions[:permission1][1].should be_a(permission_class1)
|
109
|
+
permissions[:permission1][1].object.should == :two
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "spec/"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'authorule'
|
7
|
+
require 'rspec/autorun'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
|
11
|
+
config.mock_with :rspec do |config|
|
12
|
+
config.syntax = [ :should, :expect ]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
18
|
+
# in spec/support/ and its subdirectories.
|
19
|
+
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
|