rbacanable 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/canable.rb ADDED
@@ -0,0 +1,191 @@
1
+ module Canable
2
+ Version = '0.2'
3
+
4
+ # Module that holds all the can_action? methods.
5
+ module Cans; end
6
+
7
+ # Module that holds all the [method]able_by? methods.
8
+ module Ables; end
9
+
10
+ # Module that is included by a role implementation
11
+ module Role
12
+ include Cans # each role has a distinct set of responses to all the can_action? methods
13
+
14
+ def self.included(base)
15
+ base.extend(ClassMethods)
16
+ end
17
+
18
+ # These are applied to the actual Role module 'instance' that includes this (Canable::Role) module
19
+ module ClassMethods
20
+ # Each role has a default query response, found in this variable
21
+ attr_accessor :_default_response
22
+
23
+
24
+ # Called when another Role imeplementation module tries to inherit an existing Role implementation
25
+ # Notice this method isn't self.included, this method becomes self.included on the module including this (Canable::Role) module
26
+ # This is nesscary to emulate inhertance of the default response and any other variables in the future
27
+ def included(base)
28
+ base._default_response = self._default_response
29
+ end
30
+
31
+ # Called when an Actor decides its role and extends itself (an instance) with a Role implementation
32
+ # Creates the default instance methods for an Actor and persists the can_action? response default down
33
+ def extended(base)
34
+ base.extend(RoleEnabledCanInstanceMethods)
35
+ this_role = self # can't use self inside the instance eval
36
+ base.instance_eval { @_canable_role = this_role }
37
+ end
38
+
39
+ # Methods given to an instance of an Actor
40
+ module RoleEnabledCanInstanceMethods
41
+ def _canable_default # the role default response
42
+ @_canable_role._default_response
43
+ end
44
+ end
45
+
46
+ # ----------------------
47
+ # Role building DSL
48
+ # ----------------------
49
+ def default_response(val)
50
+ self._default_response = val
51
+ end
52
+ end
53
+ end
54
+
55
+ module Actor
56
+ attr_accessor :canable_included_role
57
+
58
+ def self.included(base)
59
+ base.extend(ClassMethods)
60
+ end
61
+
62
+ module ClassMethods
63
+ attr_accessor :canable_default_role
64
+ attr_accessor :canable_role_attribute
65
+ # ---------------
66
+ # RBAC Actor building DSL
67
+ # ---------------
68
+
69
+ def default_role(role)
70
+ self.canable_default_role = role
71
+ end
72
+
73
+ def role_attribute(attribute)
74
+ self.canable_role_attribute = attribute
75
+ end
76
+
77
+ end
78
+
79
+ def initialize(*args)
80
+ super(*args)
81
+ self.__initialize_canable_role
82
+ self
83
+ end
84
+
85
+ def __initialize_canable_role
86
+ attribute = self.class.canable_role_attribute
87
+ attribute ||= :@role
88
+ role_constant = self.instance_variable_get(attribute)
89
+ if role_constant == nil
90
+ default_role = self.class.canable_default_role
91
+ self.act(default_role) unless default_role == nil
92
+ else
93
+ self.act(role_constant)
94
+ end
95
+ end
96
+
97
+ # Sets the role of this actor by including a role module
98
+ def act(role)
99
+ self.canable_included_role = role
100
+ if(role.respond_to?(:included))
101
+ self.extend role
102
+ else
103
+ self.extend Canable::Roles.const_get((role.to_s.capitalize+"Role").intern)
104
+ end
105
+ end
106
+ end
107
+
108
+ # Holds all the different roles that an actor may assume
109
+ module Roles
110
+ # Make one default role that is false for everything
111
+ module Role
112
+ include Canable::Role
113
+ default_response false
114
+ end
115
+ end
116
+
117
+ # Module that holds all the enforce_[action]_permission methods for use in controllers.
118
+ module Enforcers
119
+ def self.included(controller)
120
+ controller.class_eval do
121
+ Canable.actions.each do |can, able|
122
+ delegate "can_#{can}?", :to => :current_user
123
+ helper_method "can_#{can}?" if controller.respond_to?(:helper_method)
124
+ hide_action "can_#{can}?" if controller.respond_to?(:hide_action)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # Exception that gets raised when permissions are broken for whatever reason.
131
+ class Transgression < StandardError; end
132
+
133
+ # Default actions to an empty hash.
134
+ @actions = {}
135
+
136
+ # Returns hash of actions that have been added.
137
+ # {:view => :viewable, ...}
138
+ def self.actions
139
+ @actions
140
+ end
141
+
142
+ # Adds an action to actions and the correct methods to can and able modules.
143
+ #
144
+ # @param [Symbol] can_method The name of the can_[action]? method.
145
+ # @param [Symbol] resource_method The name of the [resource_method]_by? method.
146
+ def self.add(can, able)
147
+ @actions[can] = able
148
+ add_can_method(can)
149
+ add_able_method(can, able)
150
+ add_enforcer_method(can)
151
+ end
152
+
153
+ private
154
+ def self.add_can_method(can)
155
+ Cans.module_eval <<-EOM
156
+ def can_#{can}?(resource)
157
+ method = ("can_#{can}_"+resource.class.name.gsub(/::/,"_").downcase+"?").intern
158
+ if self.respond_to?(method, true)
159
+ self.send method, resource
160
+ elsif self.respond_to?(:_canable_default)
161
+ self._canable_default
162
+ else
163
+ false
164
+ end
165
+ end
166
+ EOM
167
+ end
168
+
169
+ def self.add_able_method(can, able)
170
+ Ables.module_eval <<-EOM
171
+ def #{able}_by?(actor)
172
+ return false if actor.blank?
173
+ actor.can_#{can}?(self)
174
+ end
175
+ EOM
176
+ end
177
+
178
+ def self.add_enforcer_method(can)
179
+ Enforcers.module_eval <<-EOM
180
+ def enforce_#{can}_permission(resource)
181
+ raise Canable::Transgression unless can_#{can}?(resource)
182
+ end
183
+ private :enforce_#{can}_permission
184
+ EOM
185
+ end
186
+ end
187
+
188
+ Canable.add(:view, :viewable)
189
+ Canable.add(:create, :creatable)
190
+ Canable.add(:update, :updatable)
191
+ Canable.add(:destroy, :destroyable)
data/specs.watchr ADDED
@@ -0,0 +1,47 @@
1
+ def growl(title, msg, img)
2
+ %x{growlnotify -m #{ msg.inspect} -t #{title.inspect} --image ~/.watchr/#{img}.png}
3
+ end
4
+
5
+ def form_growl_message(str)
6
+ results = str.split("\n").last
7
+ if results =~ /[1-9]\s(failure|error)s?/
8
+ growl "Test Results", "#{results}", "fail"
9
+ elsif results != ""
10
+ growl "Test Results", "#{results}", "pass"
11
+ end
12
+ end
13
+
14
+ def run(cmd)
15
+ puts(cmd)
16
+ output = ""
17
+ IO.popen(cmd) do |com|
18
+ com.each_char do |c|
19
+ print c
20
+ output << c
21
+ $stdout.flush
22
+ end
23
+ end
24
+ form_growl_message output
25
+ end
26
+
27
+ def run_test_file(file)
28
+ run %Q(ruby -I"lib:test" -rubygems #{file})
29
+ end
30
+
31
+ def run_all_tests
32
+ run "rake test"
33
+ end
34
+
35
+ watch('test/helper\.rb') { system('clear'); run_all_tests }
36
+ watch('test/test_.*\.rb') { |m| system('clear'); run_test_file(m[0]) }
37
+ watch('lib/.*') { |m| system('clear'); run_all_tests }
38
+
39
+ # Ctrl-\
40
+ Signal.trap('QUIT') do
41
+ puts " --- Running all tests ---\n\n"
42
+ run_all_tests
43
+ end
44
+
45
+ # Ctrl-C
46
+ Signal.trap('INT') { abort("\n") }
47
+
data/test/helper.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ gem 'mocha', '0.9.8'
5
+ gem 'shoulda', '2.10.3'
6
+ gem 'activesupport', '2.3.5'
7
+
8
+ require 'mocha'
9
+ require 'shoulda'
10
+ require 'active_support'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'canable'
15
+
16
+ class Test::Unit::TestCase
17
+ end
18
+
19
+ def Doc(name=nil, &block)
20
+ klass = Class.new do
21
+
22
+ if name
23
+ class_eval "def self.name; '#{name}' end"
24
+ class_eval "def self.to_s; '#{name}' end"
25
+ end
26
+ end
27
+
28
+ klass.class_eval(&block) if block_given?
29
+ klass
30
+ end
31
+
32
+ test_dir = File.expand_path(File.dirname(__FILE__) + '/../tmp')
33
+ FileUtils.mkdir_p(test_dir) unless File.exist?(test_dir)
@@ -0,0 +1,83 @@
1
+ require 'helper'
2
+
3
+ class AblesTest < Test::Unit::TestCase
4
+ context "Class with Canable::Ables included" do
5
+ setup do
6
+ klass = Doc do
7
+ include Canable::Ables
8
+ end
9
+
10
+ @resource = klass.new
11
+ @user = mock('user')
12
+ end
13
+
14
+ context "viewable_by?" do
15
+ should "be false if user cannot view" do
16
+ user = mock('user', :can_view? => false)
17
+ assert ! @resource.viewable_by?(user)
18
+ end
19
+
20
+ should "be true if user can view" do
21
+ user = mock('user', :can_view? => true)
22
+ assert @resource.viewable_by?(user)
23
+ end
24
+
25
+ should "be false if resource is blank" do
26
+ assert ! @resource.viewable_by?(nil)
27
+ assert ! @resource.viewable_by?('')
28
+ end
29
+ end
30
+
31
+ context "creatable_by?" do
32
+ should "be false if user cannot create" do
33
+ user = mock('user', :can_create? => false)
34
+ assert ! @resource.creatable_by?(user)
35
+ end
36
+
37
+ should "be true if user can create" do
38
+ user = mock('user', :can_create? => true)
39
+ assert @resource.creatable_by?(user)
40
+ end
41
+
42
+ should "be false if resource is blank" do
43
+ assert ! @resource.creatable_by?(nil)
44
+ assert ! @resource.creatable_by?('')
45
+ end
46
+ end
47
+
48
+ context "updatable_by?" do
49
+ should "be false if user cannot update" do
50
+ user = mock('user', :can_update? => false)
51
+ assert ! @resource.updatable_by?(user)
52
+ end
53
+
54
+ should "be true if user can update" do
55
+ user = mock('user', :can_update? => true)
56
+ assert @resource.updatable_by?(user)
57
+ end
58
+
59
+ should "be false if resource is blank" do
60
+ assert ! @resource.updatable_by?(nil)
61
+ assert ! @resource.updatable_by?('')
62
+ end
63
+ end
64
+
65
+ context "destroyable_by?" do
66
+ should "be false if user cannot destroy" do
67
+ user = mock('user', :can_destroy? => false)
68
+ assert ! @resource.destroyable_by?(user)
69
+ end
70
+
71
+ should "be true if user can destroy" do
72
+ user = mock('user', :can_destroy? => true)
73
+ assert @resource.destroyable_by?(user)
74
+ end
75
+
76
+ should "be false if resource is blank" do
77
+ assert ! @resource.destroyable_by?(nil)
78
+ assert ! @resource.destroyable_by?('')
79
+ end
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,26 @@
1
+ require 'helper'
2
+
3
+ class TestCanable < Test::Unit::TestCase
4
+ context "Canable" do
5
+ should "have view action by default" do
6
+ assert_equal :viewable, Canable.actions[:view]
7
+ end
8
+
9
+ should "have create action by default" do
10
+ assert_equal :creatable, Canable.actions[:create]
11
+ end
12
+
13
+ should "have update action by default" do
14
+ assert_equal :updatable, Canable.actions[:update]
15
+ end
16
+
17
+ should "have destroy action by default" do
18
+ assert_equal :destroyable, Canable.actions[:destroy]
19
+ end
20
+
21
+ should "be able to add another action" do
22
+ Canable.add(:publish, :publishable)
23
+ assert_equal :publishable, Canable.actions[:publish]
24
+ end
25
+ end
26
+ end
data/test/test_cans.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'helper'
2
+
3
+ class CansTest < Test::Unit::TestCase
4
+ context "Class with Canable::Cans included" do
5
+ setup do
6
+ klass = Class.new do
7
+ include Canable::Cans
8
+ end
9
+
10
+ @user = klass.new
11
+ @resource = mock('resource')
12
+ end
13
+
14
+ should "default viewable_by? to false" do
15
+ assert ! @user.can_view?(@resource)
16
+ end
17
+
18
+ should "default creatable_by? to false" do
19
+ assert ! @user.can_create?(@resource)
20
+ end
21
+
22
+ should "default updatable_by? to false" do
23
+ assert ! @user.can_update?(@resource)
24
+ end
25
+
26
+ should "default destroyable_by? to false" do
27
+ assert ! @user.can_destroy?(@resource)
28
+ end
29
+ end
30
+
31
+ context "Class that overrides a can method" do
32
+ setup do
33
+ klass = Doc do
34
+ include Canable::Cans
35
+
36
+ def can_view?(resource)
37
+ resource.owner == 'John'
38
+ end
39
+ end
40
+
41
+ @user = klass.new
42
+ @johns = mock('resource', :owner => 'John')
43
+ @steves = mock('resource', :owner => 'Steve')
44
+ end
45
+
46
+ should "use the overriden method and default to false" do
47
+ assert @user.can_view?(@johns)
48
+ assert ! @user.can_view?(@steves)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+
3
+ class EnforcersTest < Test::Unit::TestCase
4
+ context "Including Canable::Enforcers in a class" do
5
+ setup do
6
+ klass = Class.new do
7
+ include Canable::Enforcers
8
+ attr_accessor :current_user, :article
9
+
10
+ def show
11
+ enforce_view_permission(article)
12
+ end
13
+ end
14
+
15
+ @article = mock('article')
16
+ @user = mock('user')
17
+ @controller = klass.new
18
+ @controller.article = @article
19
+ @controller.current_user = @user
20
+ end
21
+
22
+ should "not raise error if can" do
23
+ @user.expects(:can_view?).with(@article).returns(true)
24
+ assert_nothing_raised { @controller.show }
25
+ end
26
+
27
+ should "raise error if cannot" do
28
+ @user.expects(:can_view?).with(@article).returns(false)
29
+ assert_raises(Canable::Transgression) { @controller.show }
30
+ end
31
+ end
32
+ end