authorule 1.0.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 +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}
|