rbacanable 0.2

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.
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