thwart 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/examples/a_complete_example.rb +56 -0
- data/examples/example_helper.rb +45 -0
- data/lib/thwart/action_group_builder.rb +62 -0
- data/lib/thwart/actions_store.rb +89 -0
- data/lib/thwart/actor.rb +36 -0
- data/lib/thwart/canable.rb +30 -0
- data/lib/thwart/dsl.rb +43 -0
- data/lib/thwart/enforcer.rb +15 -0
- data/lib/thwart/resource.rb +27 -0
- data/lib/thwart/role.rb +81 -0
- data/lib/thwart/role_builder.rb +143 -0
- data/lib/thwart/role_registry.rb +75 -0
- data/lib/thwart.rb +67 -0
- data/spec/action_group_builder_spec.rb +68 -0
- data/spec/actions_store_spec.rb +74 -0
- data/spec/actor_spec.rb +45 -0
- data/spec/canable_spec.rb +41 -0
- data/spec/dsl_spec.rb +103 -0
- data/spec/enforcer_spec.rb +17 -0
- data/spec/resource_spec.rb +42 -0
- data/spec/role_builder_spec.rb +197 -0
- data/spec/role_registry_spec.rb +137 -0
- data/spec/role_spec.rb +146 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/thwart_spec.rb +60 -0
- metadata +139 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'active_support/core_ext/hash/deep_merge'
|
2
|
+
|
3
|
+
module Thwart
|
4
|
+
class ActionOrGroupNotFoundError < StandardError; end
|
5
|
+
class OustideRoleDefinitionError < StandardError; end
|
6
|
+
|
7
|
+
class RoleBuilder
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
define_callbacks :build_role, :scope => [:kind, :name]
|
10
|
+
|
11
|
+
attr_accessor :last_built_role
|
12
|
+
attr_reader :actionables_store
|
13
|
+
delegate :actionables, :to => :actionables_store
|
14
|
+
|
15
|
+
def self.empty_role
|
16
|
+
Class.new do
|
17
|
+
include Thwart::Role
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(a_store)
|
22
|
+
@actionables_store = a_store
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_role(name, options = {}, &block)
|
26
|
+
@role = self.class.empty_role.new # Start empty role
|
27
|
+
@current_response = true # Assume the first permission definitions are allows
|
28
|
+
run_callbacks :build_role do
|
29
|
+
# Add parents
|
30
|
+
@role.name = name
|
31
|
+
@role.parents = options[:parents] if options[:parents]
|
32
|
+
|
33
|
+
# Run DSL block
|
34
|
+
if block_given?
|
35
|
+
@dsl ||= Thwart::Dsl.new
|
36
|
+
@dsl.all = true
|
37
|
+
@dsl.evaluate(self, &block)
|
38
|
+
end
|
39
|
+
self.last_built_role = @role
|
40
|
+
end
|
41
|
+
# Unset the @role instance variable to disable DSL methods and return the role
|
42
|
+
r = @role
|
43
|
+
@role = nil
|
44
|
+
return r
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_permission(name, *resources, &block)
|
48
|
+
ensure_in_dsl!
|
49
|
+
# Shift off first argument sent from method missing and convert any action groups to an array of actions
|
50
|
+
raise ArgumentError, "Unrecognized action or action group #{name}" if !self.actionables.has_key?(name)
|
51
|
+
names = self.actionables[name]
|
52
|
+
|
53
|
+
# Pop of last hash argument from method missing and merge in default options and :if block
|
54
|
+
options = {:if => false, :unless => false}
|
55
|
+
options.merge!(resources.pop) if resources.last.respond_to?(:keys)
|
56
|
+
options[:if] = block if block_given?
|
57
|
+
# Allow :all or blank resource specifiers
|
58
|
+
if resources.nil? || resources.empty? || resources.any? {|r| r == :all}
|
59
|
+
resources = [:_other]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate response based on @current_response and optional procs
|
63
|
+
generated_response = generate_response(options)
|
64
|
+
response_hash = hash_with_value(resources, generated_response)
|
65
|
+
|
66
|
+
# Merge into existing role definition
|
67
|
+
@role.responses.deep_merge!(hash_with_value(names) do |k|
|
68
|
+
response_hash
|
69
|
+
end)
|
70
|
+
end
|
71
|
+
|
72
|
+
def default(bool)
|
73
|
+
ensure_in_dsl!
|
74
|
+
@role.default_response = bool
|
75
|
+
end
|
76
|
+
|
77
|
+
def include(*args)
|
78
|
+
ensure_in_dsl!
|
79
|
+
@role.parents += args
|
80
|
+
end
|
81
|
+
|
82
|
+
def allow(&block)
|
83
|
+
evaluate_with_response(true, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def deny(&block)
|
87
|
+
evaluate_with_response(false, &block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def evaluate_with_response(response, &block)
|
91
|
+
ensure_in_dsl!
|
92
|
+
old = @current_response
|
93
|
+
@current_response = response
|
94
|
+
@dsl.evaluate(self, &block)
|
95
|
+
@current_response = old
|
96
|
+
end
|
97
|
+
|
98
|
+
def respond_to?(name)
|
99
|
+
return true if self.actionables.has_key?(name)
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
def method_missing(name, *args, &block)
|
104
|
+
return define_permission(name, *args, &block) if self.respond_to?(name)
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def generate_response(options)
|
110
|
+
# If a proc has been supplied
|
111
|
+
if(options[:if].respond_to?(:call) || options[:unless].respond_to?(:call))
|
112
|
+
# Copy variable for scope
|
113
|
+
check = @current_response
|
114
|
+
if options[:unless].respond_to?(:call)
|
115
|
+
check = !check
|
116
|
+
options[:if] = options[:unless]
|
117
|
+
end
|
118
|
+
|
119
|
+
return Proc.new do |*args|
|
120
|
+
check == options[:if].call(*args)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
@current_response
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def hash_with_value(array, val = nil, &block)
|
128
|
+
array.inject({}) do |acc, k|
|
129
|
+
acc[k] = if block_given?
|
130
|
+
yield k
|
131
|
+
else
|
132
|
+
val
|
133
|
+
end
|
134
|
+
acc
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def ensure_in_dsl!
|
139
|
+
raise OustideRoleDefinitionError, "You can only define role permissions inside a role defintion block!" if @role.nil?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Thwart
|
2
|
+
class DuplicateRoleError < StandardError; end
|
3
|
+
class MissingRoleError < StandardError; end
|
4
|
+
|
5
|
+
class RoleRegistry
|
6
|
+
def roles
|
7
|
+
@roles ||= []
|
8
|
+
@roles
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(role)
|
12
|
+
raise DuplicateRoleError, "Role #{role} already exists in the role registry!" if self.has_role?(role)
|
13
|
+
@roles << role
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(actor, resource, action)
|
17
|
+
role = self.find_actor_role(actor)
|
18
|
+
resource = self.find_resource_identifier(resource)
|
19
|
+
if role.nil? || !self.has_role?(role)
|
20
|
+
raise MissingRoleError, "Role #{role} could not be found in the registry!" if Thwart.actor_must_play_role
|
21
|
+
else
|
22
|
+
q = [role]
|
23
|
+
while r = q.shift
|
24
|
+
resp = r.query(actor, resource, action)
|
25
|
+
if resp != nil
|
26
|
+
return resp # positive/negative response from the role, a rule governs the role on this query
|
27
|
+
else
|
28
|
+
q = q | r.parents # add this roles parents to the query queue
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Thwart.default_query_response # return was not called above, return the default
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_role?(role)
|
37
|
+
self.roles.include?(role)
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_actor_role(actor)
|
41
|
+
r = actor.thwart_role if actor.respond_to?(:thwart_role)
|
42
|
+
r ||= r.to_sym if r.respond_to?(:to_sym)
|
43
|
+
if r.is_a?(Symbol)
|
44
|
+
r = self.roles.find {|a| a.name == r}
|
45
|
+
end
|
46
|
+
r
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_resource_identifier(resource)
|
50
|
+
r ||= resource.thwart_name if resource.respond_to?(:thwart_name)
|
51
|
+
if resource.class != Class
|
52
|
+
r ||= resource.class.thwart_name if resource.class.respond_to?(:thwart_name)
|
53
|
+
r ||= resource.class.name.downcase.to_sym if Thwart.all_classes_are_resources
|
54
|
+
end
|
55
|
+
r
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(role_creator = nil)
|
59
|
+
self.monitor_builder(role_creator) if !role_creator.nil?
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def monitor_builder(role_creator)
|
64
|
+
registry = self
|
65
|
+
unless role_creator.nil?
|
66
|
+
role_creator.class.set_callback :build_role, :after do |object|
|
67
|
+
registry.add(object.last_built_role)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@role_creator.class.reset_callbacks(:build_role)
|
71
|
+
end
|
72
|
+
@role_creator = role_creator
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/thwart.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/core_ext/array/wrap"
|
6
|
+
|
7
|
+
|
8
|
+
require 'thwart/canable'
|
9
|
+
require 'thwart/actions_store'
|
10
|
+
require 'thwart/action_group_builder'
|
11
|
+
require 'thwart/role_registry'
|
12
|
+
require 'thwart/role_builder'
|
13
|
+
require 'thwart/role'
|
14
|
+
require 'thwart/resource'
|
15
|
+
require 'thwart/actor'
|
16
|
+
require 'thwart/enforcer'
|
17
|
+
require 'thwart/dsl'
|
18
|
+
|
19
|
+
module Thwart
|
20
|
+
# autoload :Cans, 'thwart/canable'
|
21
|
+
# autoload :Ables, 'thwart/canable'
|
22
|
+
# autoload :ActionsStore, 'thwart/actions_store'
|
23
|
+
# autoload :ActionGroupBuilder, 'thwart/action_group_builder'
|
24
|
+
# autoload :RoleRegistry, 'thwart/role_registry'
|
25
|
+
# autoload :RoleBuilder, 'thwart/role_builder'
|
26
|
+
# autoload :Role, 'thwart/role'
|
27
|
+
# autoload :DefaultRole, 'thwart/role'
|
28
|
+
# autoload :Resource, 'thwart/resource'
|
29
|
+
# autoload :Actor, 'thwart/actor'
|
30
|
+
# autoload :Dsl, 'thwart/dsl'
|
31
|
+
|
32
|
+
# The default can => able methods for CRUD
|
33
|
+
CrudActions = {:create => :creatable, :view => :viewable, :update => :updatable, :destroy => :destroyable}
|
34
|
+
|
35
|
+
Actions = ActionsStore.new
|
36
|
+
Roles = RoleRegistry.new
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_reader :actionables_dsl, :role_dsl
|
40
|
+
attr_accessor :default_query_response, :role_registry, :actor_must_play_role, :all_classes_are_resources
|
41
|
+
delegate :create_action, :to => "Thwart::Actions"
|
42
|
+
delegate :create_action_group, :to => :actionables_dsl
|
43
|
+
delegate :create_role, :to => :role_dsl
|
44
|
+
delegate :query, :to => "Thwart::Roles"
|
45
|
+
|
46
|
+
def configure(&block)
|
47
|
+
# Create builder DSLs for this configuration block
|
48
|
+
@actionables_dsl = ActionGroupBuilder.new(Actions)
|
49
|
+
@role_dsl = RoleBuilder.new(@actionables_dsl)
|
50
|
+
Roles.monitor_builder(@role_dsl)
|
51
|
+
|
52
|
+
# Configure
|
53
|
+
dsl = Thwart::Dsl.new(:role => :create_role, :action => :create_action, :action_group => :create_action_group)
|
54
|
+
dsl.all = false
|
55
|
+
dsl.evaluate(self, &block)
|
56
|
+
|
57
|
+
# Unset and stop monitoring builder DSLs so they can be GC'd
|
58
|
+
@actionables_dsl = nil
|
59
|
+
@role_dsl = nil
|
60
|
+
Roles.monitor_builder(nil)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class MissingAttributeError < StandardError; end
|
66
|
+
class NoPermissionError < StandardError; end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::ActionGroupBuilder do
|
4
|
+
before do
|
5
|
+
@store = double("Actions Store")
|
6
|
+
@store.class.stub(:set_callback)
|
7
|
+
@builder = Thwart::ActionGroupBuilder.new(@store)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should add some simple actionables" do
|
11
|
+
@builder.add_actionable(:sing)
|
12
|
+
@builder.add_actionable(:dance, [:laugh, :play])
|
13
|
+
@builder.actionables.should == {:sing => [:sing], :dance => [:laugh, :play]}
|
14
|
+
end
|
15
|
+
it "should have the crud action group if added" do
|
16
|
+
@store.should_receive(:add_crud!)
|
17
|
+
Thwart::CrudActions.keys.each do |k|
|
18
|
+
@builder.add_actionable(k)
|
19
|
+
end
|
20
|
+
@builder.add_crud_group!
|
21
|
+
@builder.actionables.include?(:crud).should == true
|
22
|
+
end
|
23
|
+
it "should set an action group" do
|
24
|
+
@builder.add_actionable(:one)
|
25
|
+
@builder.add_actionable(:two)
|
26
|
+
@builder.create_action_group(:stuff, [:one, :two])
|
27
|
+
@builder.actionables[:stuff].should == [:one, :two]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should initialize a callback on the action creator" do
|
31
|
+
store_class = Class.new do
|
32
|
+
include ActiveSupport::Callbacks
|
33
|
+
end
|
34
|
+
store_class.should_receive(:set_callback)
|
35
|
+
Thwart::ActionGroupBuilder.new(store_class.new)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "action group resolution" do
|
39
|
+
it "should find existing actions" do
|
40
|
+
@builder.stub(:actionables).and_return({:view => [:view]})
|
41
|
+
@builder.resolve_action_group(:view).should == [:view]
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with crud and one group" do
|
45
|
+
before do
|
46
|
+
@actions = [:create, :update, :destroy]
|
47
|
+
@actionables = @actions.inject({}) {|acc, k| acc[k] = [k]; acc}.merge({:manage => @actions})
|
48
|
+
@builder.stub(:actionables).and_return(@actionables)
|
49
|
+
end
|
50
|
+
it "should find arrays of existing actions" do
|
51
|
+
@builder.resolve_action_group(@actions).should == @actions
|
52
|
+
end
|
53
|
+
it "should build action groups of other action groups" do
|
54
|
+
@builder.resolve_action_group(:manage).should == @actions
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and extra actions" do
|
58
|
+
before do
|
59
|
+
@actionables = @actionables.merge([:one, :two, :three].inject({}) {|acc, k| acc[k] = [k]; acc}).merge({:another => [:one, :two]})
|
60
|
+
@builder.stub(:actionables).and_return(@actionables)
|
61
|
+
end
|
62
|
+
it "should build action groups of other action groups and other actions" do
|
63
|
+
@builder.resolve_action_group([:manage, :another, :three]).should include(:one, :two, :three, :create, :update, :destroy)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::ActionsStore do
|
4
|
+
before do
|
5
|
+
@actions = Thwart::ActionsStore.new
|
6
|
+
end
|
7
|
+
describe "with no actions set up" do
|
8
|
+
before do
|
9
|
+
@actions.should_receive(:actions).and_return({})
|
10
|
+
end
|
11
|
+
it "shouldn't find any can methods" do
|
12
|
+
@actions.find_can(:can_view?).should == false
|
13
|
+
end
|
14
|
+
it "shouldn't find any able methods" do
|
15
|
+
@actions.find_able(:viewable_by?).should == false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe "with a custom action" do
|
19
|
+
before do
|
20
|
+
@actions.create_action(:foo, :fooable)
|
21
|
+
end
|
22
|
+
it "should have the can and able present" do
|
23
|
+
@actions.has_can?(:foo).should == true
|
24
|
+
@actions.has_able?(:fooable).should == true
|
25
|
+
end
|
26
|
+
it "shouln't have any other cans or ables present" do
|
27
|
+
@actions.has_can?(:bar).should == false
|
28
|
+
@actions.has_able?(:barable).should == false
|
29
|
+
end
|
30
|
+
it "should find the can method" do
|
31
|
+
@actions.find_can(:can_foo?).should == :foo
|
32
|
+
end
|
33
|
+
it "should find the able method" do
|
34
|
+
@actions.find_able(:fooable_by?).should == :fooable
|
35
|
+
end
|
36
|
+
it "should get the can from the able" do
|
37
|
+
@actions.can_from_able(:fooable).should == :foo
|
38
|
+
end
|
39
|
+
end
|
40
|
+
it "should create several actions from an array" do
|
41
|
+
some_actions = {:bleh => :blehable, :beef => :beefable}
|
42
|
+
@actions.create_action(some_actions)
|
43
|
+
@actions.actions.should == some_actions
|
44
|
+
end
|
45
|
+
describe "with the crud actions" do
|
46
|
+
before do
|
47
|
+
@actions.add_crud!
|
48
|
+
end
|
49
|
+
# This isnt DRY at all but I want to know which one is failing
|
50
|
+
it "should have C for create" do
|
51
|
+
@actions.has_can?(:create).should == true
|
52
|
+
end
|
53
|
+
it "should have R for ...er... view" do
|
54
|
+
@actions.has_can?(:view).should == true
|
55
|
+
end
|
56
|
+
it "should have U for update" do
|
57
|
+
@actions.has_can?(:update).should == true
|
58
|
+
end
|
59
|
+
it "should have D for delete" do
|
60
|
+
@actions.has_can?(:destroy).should == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fire the add callback and set the last added" do
|
65
|
+
klass = Thwart::ActionsStore.clone
|
66
|
+
actionz = klass.new
|
67
|
+
receiver = double('receiver')
|
68
|
+
receiver.should_receive(:before).with(actionz)
|
69
|
+
klass.set_callback :add, :before, receiver
|
70
|
+
|
71
|
+
actionz.create_action(:do, :doable)
|
72
|
+
actionz.last_action.should == :do
|
73
|
+
end
|
74
|
+
end
|
data/spec/actor_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Actor do
|
4
|
+
it "should not add the thwart_role method until thwart_access is called" do
|
5
|
+
actor_class = generic_model("Generic Resource") do
|
6
|
+
include Thwart::Actor
|
7
|
+
end
|
8
|
+
actor_class.new.should_not respond_to(:thwart_role)
|
9
|
+
actor_class.thwart_access
|
10
|
+
actor_class.new.should respond_to(:thwart_role)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "thwart_role defining and finding" do
|
14
|
+
before do
|
15
|
+
@actor_class = generic_model("Generic Resource") do
|
16
|
+
include Thwart::Actor
|
17
|
+
def arbitrary_attribute
|
18
|
+
:arb_return
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
it "should return a default role" do
|
23
|
+
@actor_class.thwart_access do
|
24
|
+
role :a_role
|
25
|
+
end
|
26
|
+
@actor_class.new.thwart_role.should == :a_role
|
27
|
+
end
|
28
|
+
it "should allow the specifcation of a method" do
|
29
|
+
@actor_class.thwart_access do
|
30
|
+
role_method :arbitrary_attribute
|
31
|
+
end
|
32
|
+
@actor_class.new.thwart_role.should == :arb_return
|
33
|
+
end
|
34
|
+
it "should allow the specification of a proc" do
|
35
|
+
@actor_class.thwart_access do
|
36
|
+
role_proc Proc.new { |a|
|
37
|
+
a.nil?.should == false
|
38
|
+
:proc_return
|
39
|
+
}
|
40
|
+
end
|
41
|
+
@actor_class.new.thwart_role.should == :proc_return
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "with some inheriting models set up" do
|
4
|
+
before do
|
5
|
+
@with_cans = generic_model do
|
6
|
+
include Thwart::Cans
|
7
|
+
end.new
|
8
|
+
@with_ables = generic_model do
|
9
|
+
include Thwart::Ables
|
10
|
+
end.new
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Thwart::Cans do
|
14
|
+
it "shouldn't find non existant methods" do
|
15
|
+
Thwart::Actions.should_receive(:find_can).with(:can_view?).at_least(1).and_return(false)
|
16
|
+
lambda {@with_cans.can_view?(@with_ables) }.should raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
it "should find a method" do
|
19
|
+
# rspec seems to call respond_to which calls this and it needs to work
|
20
|
+
# Thwart::Actions.should_receive(:find_can).with(:can_view?).at_least(1).and_return(:view)
|
21
|
+
Thwart::Actions.should_receive(:actions).any_number_of_times.and_return({:view => :viewable})
|
22
|
+
Thwart.should_receive(:query).with(@with_cans, @with_ables, :view)
|
23
|
+
@with_cans.can_view?(@with_ables)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Thwart::Ables do
|
28
|
+
it "shouldn't find any methods" do
|
29
|
+
Thwart::Actions.should_receive(:find_able).with(:viewable_by?).at_least(1).and_return(false)
|
30
|
+
lambda {@with_ables.viewable_by?(@with_cans) }.should raise_error(NoMethodError)
|
31
|
+
end
|
32
|
+
it "should find a method" do
|
33
|
+
# rspec seems to call respond_to which calls this and it needs to work properly
|
34
|
+
# Thwart::Actions.should_receive(:find_able).with(:viewable_by?).at_least(1).and_return(:viewable)
|
35
|
+
Thwart::Actions.should_receive(:actions).any_number_of_times.and_return({:view => :viewable})
|
36
|
+
Thwart::Actions.should_receive(:can_from_able).with(:viewable).and_return(:view)
|
37
|
+
Thwart.should_receive(:query).with(@with_cans, @with_ables, :view)
|
38
|
+
@with_ables.viewable_by?(@with_cans)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Dsl do
|
4
|
+
before do
|
5
|
+
@target = double("target")
|
6
|
+
end
|
7
|
+
describe "with no given map" do
|
8
|
+
before do
|
9
|
+
@dsl = Thwart::Dsl.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should pass methods on to the target" do
|
13
|
+
@target.should_receive(:foo)
|
14
|
+
@dsl.evaluate(@target) do
|
15
|
+
foo
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should pass writer methods on to the target" do
|
20
|
+
@target.should_receive(:foo=).with("bar")
|
21
|
+
@dsl.evaluate(@target) do
|
22
|
+
foo "bar"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "shouldn't find non existant methods" do
|
27
|
+
lambda { @dsl.evaluate(@target) do
|
28
|
+
foo "bar"
|
29
|
+
end }.should raise_error(NoMethodError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with an extra map" do
|
34
|
+
before do
|
35
|
+
@dsl = Thwart::Dsl.new :imperative => :foo=
|
36
|
+
end
|
37
|
+
it "should map attributes on to the target" do
|
38
|
+
@target.should_receive(:foo=).with("bar")
|
39
|
+
@dsl.evaluate(@target) do
|
40
|
+
imperative "bar"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "with method_missing defined on the target" do
|
46
|
+
before do
|
47
|
+
target_class = Class.new do
|
48
|
+
def respond_to?(name)
|
49
|
+
return true if [:test1, :test2].include?(name)
|
50
|
+
super
|
51
|
+
end
|
52
|
+
def method_missing(name, *args)
|
53
|
+
return self.test_method_called(name, *args) if self.respond_to?(name)
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
@target = target_class.new
|
58
|
+
end
|
59
|
+
it "should have a proper target to test with" do
|
60
|
+
@target.should_receive(:test_method_called).with(:test1)
|
61
|
+
@target.should_receive(:test_method_called).with(:test2)
|
62
|
+
@target.should_receive(:test_method_called).with(:test1, :foo, :bar)
|
63
|
+
@target.respond_to?(:test1).should == true
|
64
|
+
@target.respond_to?(:test2).should == true
|
65
|
+
@target.test1
|
66
|
+
@target.test2
|
67
|
+
@target.test1 :foo, :bar
|
68
|
+
end
|
69
|
+
context "with a all = true DSL" do
|
70
|
+
before do
|
71
|
+
@dsl = Thwart::Dsl.new :test2 => :something_else
|
72
|
+
@dsl.all = true
|
73
|
+
end
|
74
|
+
it "should call method missing on the target if the DSL doesn't have the method defined" do
|
75
|
+
@target.should_receive(:test_method_called).with(:test1)
|
76
|
+
@dsl.evaluate @target do
|
77
|
+
test1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
it "should call the method in the method map if the DSL has the method in the map" do
|
81
|
+
@target.should_not_receive(:method_missing)
|
82
|
+
@target.should_receive(:something_else)
|
83
|
+
@dsl.evaluate @target do
|
84
|
+
test2
|
85
|
+
end
|
86
|
+
end
|
87
|
+
it "should properly pass arguments to the missing method" do
|
88
|
+
@target.should_receive(:test_method_called).with(:test1, :foo, :baz)
|
89
|
+
@dsl.evaluate @target do
|
90
|
+
test1 :foo, :baz
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
it "shouldn't call method missing on the target if the DSL doesn't have the method defined and all is false" do
|
95
|
+
@target.should_not_receive(:test_method_called).with(:test1)
|
96
|
+
@dsl = Thwart::Dsl.new
|
97
|
+
@dsl.all = false
|
98
|
+
lambda { @dsl.evaluate @target do
|
99
|
+
test1
|
100
|
+
end }.should raise_error(NameError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe Thwart::Enforcer do
|
5
|
+
context "access enforcment" do
|
6
|
+
it "should need the current user method defined on the controller" do
|
7
|
+
lambda { instance_with_module(Thwart::Enforcer).thwart_access }.should raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
it "should need the params hash to have an action key" do
|
10
|
+
class_with_module(Thwart::Enforcer)
|
11
|
+
lambda { @controller.thwart_access }.should raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
it "should need the params[:actions] to be a recognized action"
|
14
|
+
it "should thwart access by raising an error if the user doesn't have permission"
|
15
|
+
it "should return true if the user does have permission"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Resource do
|
4
|
+
describe "without a special name" do
|
5
|
+
before do
|
6
|
+
resource_class = generic_model("Generic Resource") do
|
7
|
+
include Thwart::Resource
|
8
|
+
thwart_access
|
9
|
+
end
|
10
|
+
@resource = resource_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have set its own name" do
|
14
|
+
@resource.class.thwart_name.should == "generic"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "with a special name" do
|
19
|
+
before do
|
20
|
+
resource_class = generic_model("Generic Resource") do
|
21
|
+
include Thwart::Resource
|
22
|
+
thwart_access do
|
23
|
+
name "special"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@resource = resource_class.new
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have a different name" do
|
30
|
+
@resource.class.thwart_name.should == "special"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "without a name" do
|
35
|
+
it "should raise an error if no name could be found" do
|
36
|
+
lambda { @resource = Class.new do
|
37
|
+
include Thwart::Resource
|
38
|
+
thwart_access
|
39
|
+
end }.should raise_error(Thwart::MissingAttributeError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|