authorization-san 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ (c) 2009 Fingertips, Manfred Stienstra <m.stienstra@fngtps.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,21 @@
1
+ = Authorization-San
2
+
3
+ Authorization-san allows you to specify access policies in your controllers. The plugin assumes a number of things about the application.
4
+
5
+ * If a user has authenticated with the application, it's stored in <tt>@authenticated</tt>. The method of authentication doesn't matter. It also doesn't matter what you put in @authenticated, as long as it's truthy.
6
+ * <tt>@authenticated</tt> has either a <tt>role</tt> attribute or a number of methods to query for the role: <tt>admin?</tt>, <tt>editor?</tt>, <tt>guest?</tt>. When the <tt>@authenticated</tt> object doesn't have role methods you can't use role based authentication rules, but the rest still works.
7
+
8
+ == What does it look like?
9
+
10
+ class BooksController < ActionController::Base
11
+ # Visitors can see list of books and book pages
12
+ allow_access :all, :only => [:index, :show]
13
+ # An editor can create new books, but…
14
+ allow_access :editor, :only => [:new, :create]
15
+ # …she can only update her own books.
16
+ allow_access(:editor, :only => [:edit, :update]) { @book = @authenticated.books.find(params[:id]) }
17
+ # Admin users can do it all.
18
+ allow_access :admin
19
+ end
20
+
21
+ The best place to start learning more is the <tt>examples</tt> directory in the source.
@@ -0,0 +1,11 @@
1
+ # The administrations controller is nested under organizations (ie. /organizations/3214/administrations)
2
+ class PagesController < ApplicationController
3
+ # The following rule only allows @authenticated if @authenticated.organization.id == params[:organization_id].
4
+ # Roughly translated this means that the authenticated user can only access resources belonging to its own
5
+ # organization.
6
+ allow_access :authenticated, :scope => :organization
7
+
8
+ def index
9
+ @administrations = @authenticated.organization.administrations
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ class ApplicationController < ActionController::Base
2
+ # You have to specify where you want these actions to appear in your filter chain. Make sure you :block_access
3
+ # before any sensitive processing occurs.
4
+ before_filter :find_authenticated, :block_access
5
+
6
+ private
7
+
8
+ # Find the authenticated user
9
+ def find_authenticated
10
+ @authenticated = authenticate_with_http_basic { |username, password| User.authenticate(username, password) }
11
+ end
12
+
13
+ # Access was forbidden to client requesting the resource. React to that appropriately. Note that this reply is very
14
+ # bare bones and you might want to return more elaborate responses in a real application.
15
+ def access_forbidden
16
+ if @authenticated.nil?
17
+ request_http_basic_authentication "Accounting"
18
+ else
19
+ head :forbidden
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ class ApplicationController < ActionController::Base
2
+ before_filter :find_authenticated, :block_access
3
+
4
+ private
5
+
6
+ # Find the authenticated user, cookie based authentication for browser users and HTTP Basic Authentication for
7
+ # API users. Note that this does not allow you to get HTML resources when logged in through Basic Auth.
8
+ def find_authenticated
9
+ respond_to do |format|
10
+ format.html do
11
+ @authenticated = Person.find_by_id session[:authenticated_id] unless session[:authenticated_id].nil?
12
+ end
13
+ format.xml do
14
+ @authenticated = authenticate_with_http_basic { |username, password| User.authenticate(username, password) }
15
+ end
16
+ end
17
+ end
18
+
19
+ # Access was forbidden to client requesting the resource. React to that appropriately. Note that this reply is very
20
+ # bare bones and you might want to return more elaborate responses in a real application.
21
+ def access_forbidden
22
+ unless @authenticated
23
+ # The user is not authenticated; ask for credentials
24
+ respond_to do |format|
25
+ format.html { redirect_to login_url }
26
+ format.xml { request_http_basic_authentication "Accounting" }
27
+ end
28
+ else
29
+ # The user is authentication but unauthorized for this resource
30
+ head :forbidden
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ class AuthenticatedController < ApplicationController
2
+ # Authenticated users can access all actions
3
+ allow_access :authenticated
4
+
5
+ def index; end
6
+ end
@@ -0,0 +1,28 @@
1
+ # The pages controller is a nest resource under users (ie. /users/12/pages)
2
+ class PagesController < ApplicationController
3
+ # A user may only access her own index
4
+ allow_access(:authenticated, :only => :index) { @authenticated == @user }
5
+ # A user may only access her own pages
6
+ allow_access(:authenticated, :only => :show) { @authenticated == @page.user}
7
+
8
+ # Always find the user the pages are nested under before applying the rules
9
+ prepend_before_filter :find_user
10
+ # Find the page before applying the rules when the show action is called
11
+ prepend_before_filter :find_page, :only => :show
12
+
13
+ def index
14
+ @pages = @user.pages
15
+ end
16
+
17
+ def show; end
18
+
19
+ private
20
+
21
+ def find_user
22
+ @user = User.find params[:user_id]
23
+ end
24
+
25
+ def find_page
26
+ @page = Page.find params[:id]
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ # The pages controller is nested under users (ie. /users/12/pages)
2
+ class PagesController < ApplicationController
3
+ # Users can only reach pages nested under their user_id. Note that this doesn't define the complete access policy,
4
+ # some of the authorization is still done in the actions. See pages_controller_with_full_policy.rb for an example
5
+ # of specifying everything in access rules.
6
+ allow_access(:authenticated) { @authenticated.to_param == params[:user_id].to_param }
7
+
8
+ before_filter :find_user
9
+
10
+ def index
11
+ @pages = @user.pages
12
+ end
13
+
14
+ def show
15
+ @page = @user.pages.find params[:id]
16
+ rescue ActiveRecord::RecordNotFound
17
+ head :forbidden
18
+ end
19
+
20
+ private
21
+
22
+ def find_user
23
+ @user = User.find params[:user_id]
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class PublicController < ApplicationController
2
+ # Everyone can access all actions
3
+ allow_access
4
+
5
+ def index; end
6
+ end
@@ -0,0 +1,27 @@
1
+ class UsersController < ApplicationController
2
+ # The default is to deny all access. Every rule creates a 'hole' in this policy. You can specify multiple rules
3
+ # per role if you want.
4
+
5
+ # The 'admin' role (@authenticated.role) has access to all the actions.
6
+ allow_access :admin
7
+ # The 'editor' role has access to the index and show action.
8
+ allow_access :editor, :only => [:index, :show]
9
+ # The 'user' role has access to the index, show, edit and update role only if the resource he's editing is the same
10
+ # as the user resource.
11
+ allow_access :user, :only => [:index, :show, :edit, :update], :user_resource => true
12
+ # The 'guest' role has access to the index and show action if the Proc returns true.
13
+ allow_access(:guest, :only => [:index, :show]) { @authenticated.valid_email? }
14
+ # Everyone can access the listing and the index action, the other actions can be accessed when it's not sunday.
15
+ allow_access :only => :listing
16
+ allow_access :only => :index
17
+ allow_access() { Time.now.strftime('%A') != 'Sunday' }
18
+
19
+ def index; end
20
+ def listing; end
21
+ def new; end
22
+ def create; end
23
+ def show; end
24
+ def edit; end
25
+ def update; end
26
+ def destroy; end
27
+ end
@@ -0,0 +1,2 @@
1
+ require 'authorization/allow_access'
2
+ require 'authorization/block_access'
@@ -0,0 +1,78 @@
1
+ module Authorization
2
+ module AllowAccess
3
+ # By default you block all access to the controller with <tt>block_access</tt>, with <tt>allow_access</tt> you
4
+ # specify who can access the actions on the controller under certain conditions. <tt>allow_access</tt> can deal
5
+ # with accounts with and without roles.
6
+ #
7
+ # *Examples*
8
+ #
9
+ # Everyone can access all actions on the controller.
10
+ # allow_access
11
+ # allow_access :all
12
+ # Everyone with the admin role can access the controller.
13
+ # allow_access :admin
14
+ # Everyone with the admin and editor role can access the controller.
15
+ # allow_access :admin, :editor
16
+ # Everyone with the editor role can access the index. show, edit and update actions.
17
+ # allow_access :editor, :only => [:index, :show, :edit, :update]
18
+ # A coordinator can do anything the admin can, except for delete
19
+ # allow_access :coordinator, :except => :destroy
20
+ # Everyone with the admin and editor role can access the show action.
21
+ # allow_access :admin, :editor, :action => :show
22
+ # A guest can view all resources if he has view permissions. The block is evaltuated in the controller's instance.
23
+ # Note that rules are evaluated when <tt>block_access</tt> is run.
24
+ # allow_access(:guest, :only => [:index, :show]) { @authenticated.view_permission? }
25
+ # Specifying a role is optional, if you don't specify a role the rule is added for the default role <tt>:all</tt>.
26
+ # allow_access(:only => :unsubscribe) { @authenticated.subscribed? }
27
+ # Only allow authenticated users, :authenticated is a special role meaning all authenticated users.
28
+ # allow_access :authenticated
29
+ # You need to be authenticated for the secret action
30
+ # allow_access :all, :except => :secret
31
+ # allow_access :authenticated, :only => :secret
32
+ #
33
+ # The following special directives might be a little hard to explain, I will give the equivalent rule with the
34
+ # block access.
35
+ #
36
+ # Imagine we have a user controller, every user has an organization association. The users resource is nested
37
+ # in the organization resource like this.
38
+ # map.resources :organizations { |org| org.resources :users }
39
+ # Now we want the user to edit his own resource (for instance to update the password).
40
+ # allow_access :only => [:index, :show, :edit, :update], :user_resource => true
41
+ # allow_access(:only => [:index, :show, :edit, :update]) do
42
+ # @authenticated.id == params[:id].to_i
43
+ # end
44
+ # We could also specify that a user can edit everything in his own organization.
45
+ # allow_access :only => [:index, :show, :edit, :update], :scope => :organization
46
+ # allow_access(:only => [:index, :show, :edit, :update]) do
47
+ # @authenticated.organization.id == params[:organization_id].to_i
48
+ # end
49
+ def allow_access(*args, &block)
50
+ unless self.respond_to?(:access_allowed_for)
51
+ self.class_inheritable_accessor(:access_allowed_for)
52
+ send(:protected, :access_allowed_for, :access_allowed_for=)
53
+ end
54
+ self.access_allowed_for ||= HashWithIndifferentAccess.new
55
+ if args.first.kind_of?(Hash) || args.empty?
56
+ self.access_allowed_for[:all] ||= []
57
+ self.access_allowed_for[:all] << {
58
+ :directives => args.first || {},
59
+ :block => block
60
+ }
61
+ else
62
+ directives = args.extract_options!
63
+ roles = args.flatten
64
+ if roles.delete(:authenticated) or roles.delete('authenticated')
65
+ roles = [:all] if roles.empty?
66
+ directives[:authenticated] = true
67
+ end
68
+ roles.each do |role|
69
+ self.access_allowed_for[role.to_s] ||= []
70
+ self.access_allowed_for[role.to_s] << {
71
+ :directives => directives,
72
+ :block => block
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,133 @@
1
+ module Authorization
2
+ module BlockAccess
3
+ protected
4
+
5
+ # Block access to all actions in the controller, designed to be used as a <tt>before_filter</tt>.
6
+ # class ApplicationController < ActionController::Base
7
+ # before_filter :block_access
8
+ # end
9
+ def block_access
10
+ die_if_undefined
11
+ unless @authenticated.nil?
12
+ # Find the user's roles
13
+ roles = []
14
+ roles << @authenticated.role if @authenticated.respond_to?(:role)
15
+ access_allowed_for.keys.each do |role|
16
+ roles << role.to_s if @authenticated.respond_to?("#{role}?") and @authenticated.send("#{role}?")
17
+ end
18
+ # Check if any of the roles give her access
19
+ roles.each do |role|
20
+ return true if access_allowed?(params, role, @authenticated)
21
+ end
22
+ end
23
+ return true if access_allowed?(params, :all, @authenticated)
24
+ access_forbidden
25
+ end
26
+
27
+ # Checks if access is allowed for the params, role and authenticated user.
28
+ # access_allowed?({:action => :show, :id => 1}, :admin, @authenticated)
29
+ def access_allowed?(params, role, authenticated=nil)
30
+ die_if_undefined
31
+ if (rules = access_allowed_for[role]).nil?
32
+ logger.debug(" \e[31mCan't find rules for `#{role}'\e[0m")
33
+ return false
34
+ end
35
+ !rules.detect do |rule|
36
+ if !action_allowed_by_rule?(rule, params, role) or !resource_allowed_by_rule?(rule, params, role, authenticated) or !block_allowed_by_rule?(rule)
37
+ logger.debug(" \e[31mAccess DENIED by RULE #{rule.inspect} FOR `#{role}'\e[0m")
38
+ false
39
+ else
40
+ logger.debug(" \e[32mAccess GRANTED by RULE #{rule.inspect} FOR `#{role}'\e[0m")
41
+ true
42
+ end
43
+ end.nil?
44
+ end
45
+
46
+ # <tt>access_forbidden</tt> is called by <tt>block_access</tt> when access is forbidden. This method does
47
+ # nothing by default. Make sure you return <tt>false</tt> from the method if you want to halt the filter
48
+ # chain.
49
+ def access_forbidden
50
+ false
51
+ end
52
+
53
+ # Checks if a certain action can be accessed by the role.
54
+ # If you want to check for <tt>action_allowed?</tt>, <tt>resource_allowed?</tt> and <tt>block_allowed?</tt>
55
+ # use <tt>access_allowed?</tt>.
56
+ # action_allowed?({:action => :show, :id => 1}, :editor)
57
+ def action_allowed?(params, role=:all)
58
+ die_if_undefined
59
+ return false if (rules = access_allowed_for[role]).nil?
60
+ !rules.detect { |rule| action_allowed_by_rule?(rule, params, role) }.nil?
61
+ end
62
+
63
+ def action_allowed_by_rule?(rule, params, role) #:nodoc:
64
+ return false if (action = params[:action]).nil?
65
+ directives = rule[:directives]
66
+ return false if directives[:only].kind_of?(Array) and !directives[:only].include?(action.to_sym)
67
+ return false if directives[:only].kind_of?(Symbol) and directives[:only] != action.to_sym
68
+ return false if directives[:except].kind_of?(Array) and directives[:except].include?(action.to_sym)
69
+ return false if directives[:except].kind_of?(Symbol) and directives[:except] == action.to_sym
70
+ true
71
+ end
72
+
73
+ # Checks if the resource indicated by the params can be accessed by user.
74
+ # If you want to check for <tt>action_allowed?</tt>, <tt>resource_allowed?</tt> and <tt>block_allowed?</tt>
75
+ # use <tt>access_allowed?</tt>.
76
+ # resource_allowed?({:id => 1, :organization_id => 12}, :guest, @authenticated)
77
+ def resource_allowed?(params, role=:all, user=nil)
78
+ user ||= @authenticated
79
+ die_if_undefined
80
+ return false if (rules = access_allowed_for[role]).nil?
81
+ !rules.detect { |rule| resource_allowed_by_rule?(rule, params, role, user) }.nil?
82
+ end
83
+
84
+ def resource_allowed_by_rule?(rule, params, role, user) #:nodoc:
85
+ directives = rule[:directives]
86
+ if directives[:authenticated]
87
+ return false unless user
88
+ end
89
+ begin
90
+ if directives[:user_resource]
91
+ return false if params[:id].nil? or user.id.nil?
92
+ return false if params[:id].to_i != user.id.to_i
93
+ end
94
+ rescue NoMethodError
95
+ end
96
+ begin
97
+ if scope = directives[:scope]
98
+ assoc_id = params["#{scope}_id"].to_i
99
+ begin
100
+ object_id = user.send(scope).id.to_i
101
+ rescue NoMethodError
102
+ return false
103
+ end
104
+ return false if assoc_id.nil? or object_id.nil?
105
+ return false if assoc_id != object_id
106
+ end
107
+ rescue NoMethodError
108
+ end
109
+ true
110
+ end
111
+
112
+ # Checks if the blocks associated with the rules doesn't stop the user from acessing the resource.
113
+ # If you want to check for <tt>action_allowed?</tt>, <tt>resource_allowed?</tt> and <tt>block_allowed?</tt>
114
+ # use <tt>access_allowed?</tt>.
115
+ # block_allowed?(:guest)
116
+ def block_allowed?(role)
117
+ die_if_undefined
118
+ return false if (rules = access_allowed_for[role]).nil?
119
+ !rules.detect { |rule| block_allowed_by_rule?(rule) }.nil?
120
+ end
121
+
122
+ def block_allowed_by_rule?(rule) #:nodoc:
123
+ return false if !rule[:block].nil? and !rule[:block].bind(self).call
124
+ true
125
+ end
126
+
127
+ def die_if_undefined #:nodoc:
128
+ if !self.respond_to?(:access_allowed_for) or access_allowed_for.nil?
129
+ raise ArgumentError, "Please specify access control using `allow_access' in the controller"
130
+ end
131
+ end
132
+ end
133
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'authorization'
2
+
3
+ ActionController::Base.send(:include, Authorization::BlockAccess)
4
+ ActionController::Base.send(:extend, Authorization::AllowAccess)
@@ -0,0 +1,167 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ require 'controllers/all'
4
+ require 'models/resource'
5
+
6
+ class BehaviourTest < ActionController::TestCase
7
+ test "access is denied for nonexistant actions without an access rule" do
8
+ tests UsersController, :authenticated => Resource.new(:role => :tester, :id => 1)
9
+ get :unknown, :id => 1
10
+ assert_response :forbidden
11
+ end
12
+
13
+ test "roles are properly checked" do
14
+ tests UsersController, :authenticated => Resource.new
15
+ {
16
+ [:admin, :index] => :ok,
17
+ [:admin, :show] => :ok,
18
+ [:admin, :guest] => :ok,
19
+ [:admin, :listing] => :ok,
20
+ [:admin, :react] => :ok,
21
+ [:editor, :index] => :ok,
22
+ [:editor, :guest] => :forbidden,
23
+ [:editor, :listing] => :ok,
24
+ [:editor, :react] => :ok,
25
+ [:guest, :index] => :forbidden,
26
+ [:guest, :guest] => :ok,
27
+ [:guest, :listing] => :ok,
28
+ [:guest, :react] => :ok,
29
+ [:user, :listing] => :ok,
30
+ [:user, :react] => :ok,
31
+ [:user, :index] => :forbidden,
32
+ }.each do |(role, action), status|
33
+ @controller.authenticated.role = role
34
+ get action
35
+ assert_response status
36
+ end
37
+ end
38
+
39
+ test "authenticated is allowed to access its own resource" do
40
+ tests UsersController, :authenticated => Resource.new(:role => :tester, :id => 1)
41
+ get :show, :id => 1
42
+ assert_response :ok
43
+ end
44
+
45
+ test "authenticated is not allowed to access other users" do
46
+ tests UsersController, :authenticated => Resource.new(:role => :tester, :id => 1)
47
+ get :show, :id => 2
48
+ assert_response :forbidden
49
+ end
50
+
51
+ test "authenticated is allowed to access within the defined scope" do
52
+ tests UsersController, :authenticated => Resource.new(:role => :reader, :organization => Resource.new(:id => 1))
53
+ get :show, :organization_id => 1
54
+ assert_response :success
55
+ end
56
+
57
+ test "authenticated is not allowed to access outside of the defined scope" do
58
+ tests UsersController, :authenticated => Resource.new(:role => :tester, :id => 1)
59
+ get :show, :organization_id => 2
60
+ assert_response :forbidden
61
+ end
62
+ test "rule without restrictions opens up the whole controller" do
63
+ tests PublicController
64
+ get :index
65
+ assert_response :ok
66
+ end
67
+
68
+ test "rule with special role :authenticated allows when @authenticated is truthy" do
69
+ tests AuthenticatedController, :authenticated => true
70
+ get :index
71
+ assert_response :ok
72
+ end
73
+
74
+ test "rule with special role :authenticated disallows when @authenticated is not truthy" do
75
+ tests AuthenticatedController, :authenticated => false
76
+ get :index
77
+ assert_response :forbidden
78
+ end
79
+
80
+ test "rule with broken block should raise an exception when evaluated" do
81
+ tests BrokenBlockController
82
+ assert_raises(NoMethodError) do
83
+ get :index
84
+ end
85
+ end
86
+
87
+ test "rule with block should only be evaluated when the action matches" do
88
+ tests BrokenBlockController
89
+ assert_nothing_raised do
90
+ get :show
91
+ end
92
+ end
93
+
94
+ test "rule with block should only be evaluated when the role matches" do
95
+ tests BrokenBlockController, :authenticated => Resource.new(:role => :admin)
96
+ assert_nothing_raised do
97
+ get :show
98
+ end
99
+ end
100
+
101
+ test "rule with block should only be evaluated when the special role matches" do
102
+ tests BrokenBlockController, :authenticated => true
103
+ assert_nothing_raised do
104
+ get :show
105
+ end
106
+ end
107
+
108
+ test "rule with multiple roles" do
109
+ tests MultipleRolesController, :authenticated => Resource.new
110
+ {
111
+ [:a, :index] => :ok,
112
+ [:b, :index] => :ok,
113
+ [:c, :index] => :ok,
114
+ [:d, :index] => :ok,
115
+ [:e, :index] => :ok,
116
+ [:f, :index] => :ok,
117
+ [:e, :show] => :forbidden,
118
+ [:f, :show] => :forbidden,
119
+ [:g, :index] => :forbidden,
120
+ [:h, :index] => :forbidden,
121
+ [:g, :show] => :ok,
122
+ [:h, :show] => :ok,
123
+ }.each do |(role, action), status|
124
+ @controller.authenticated.role = role
125
+ get action
126
+ assert_response status
127
+ end
128
+ end
129
+
130
+ test "rule with special role, user resource and action restriction, should disallow unauthenticated" do
131
+ tests ComplicatedController
132
+ get :show, :id => 1
133
+ assert_response :forbidden
134
+ end
135
+
136
+ test "rule with special role, user resource and action restriction, should disallow incorrect user" do
137
+ tests ComplicatedController, :authenticated => Resource.new(:id => 2)
138
+ get :show, :id => 1
139
+ assert_response :forbidden
140
+ end
141
+
142
+ test "rule with special role, user resource and action restriction, should allow correct user" do
143
+ tests ComplicatedController, :authenticated => Resource.new(:id => 1)
144
+ get :show, :id => 1
145
+ assert_response :ok
146
+ end
147
+
148
+ test "controller with rule about special role, user resource and action restriction, should allow open actions" do
149
+ tests ComplicatedController
150
+ get :index
151
+ assert_response :ok
152
+ end
153
+
154
+ private
155
+
156
+ def tests(controller, options={})
157
+ @request = ActionController::TestRequest.new
158
+ @response = ActionController::TestResponse.new
159
+ @controller ||= controller.new rescue nil
160
+
161
+ @controller.request = @request
162
+ @controller.params = {}
163
+ @controller.send(:initialize_current_url)
164
+
165
+ @controller.authenticated = options[:authenticated]
166
+ end
167
+ end
@@ -0,0 +1,252 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ require 'models/resource'
4
+
5
+ class MethodsTest < ActiveSupport::TestCase
6
+ include Authorization::BlockAccess
7
+ attr_accessor :params, :access_allowed_for
8
+
9
+ def logger
10
+ @logger ||= Logger.new('/dev/null')
11
+ end
12
+
13
+ def do_false
14
+ false
15
+ end
16
+
17
+ def do_true
18
+ true
19
+ end
20
+
21
+ def test_action_allowed
22
+ @access_allowed_for = {
23
+ :admin => [{
24
+ :directives => {}
25
+ }],
26
+ :editor => [{
27
+ :directives => {:only => :index}
28
+ }],
29
+ :complex => [
30
+ {:directives => {:only => :index}},
31
+ {:directives => {:only => :show}}
32
+ ],
33
+ :all => [{
34
+ :directives => {:only => :listing}
35
+ }]
36
+ }
37
+ assert_action_allowed({
38
+ [:admin, :index] => true,
39
+ [:admin, :show] => true,
40
+ [:admin, :unknown] => true,
41
+ [:editor, :unknown] => false,
42
+ [:editor, :index] => true,
43
+ [:all, :index] => false,
44
+ [:all, :unknown] => false,
45
+ [:all, :listing] => true,
46
+ [:complex, :index] => true,
47
+ [:complex, :show] => true,
48
+ [:complex, :unknown] => false
49
+ })
50
+ end
51
+
52
+ def test_action_allowed_open
53
+ @access_allowed_for = {:all => [{:directives => {}}] }
54
+ assert_action_allowed({
55
+ [:admin, :index] => false,
56
+ [:all, :show] => true,
57
+ [:unknown, :show] => false
58
+ })
59
+ end
60
+
61
+ def test_action_allowed_closed
62
+ @access_allowed_for = {}
63
+ assert_action_allowed({
64
+ [:admin, :index] => false,
65
+ [:all, :show] => false,
66
+ [:show, :unknown] => false
67
+ })
68
+ end
69
+
70
+ def test_action_allowed_nil
71
+ @access_allowed_for = nil
72
+ params = HashWithIndifferentAccess.new :action => :something
73
+ assert_raises(ArgumentError) { action_allowed?(params, :something) }
74
+ end
75
+
76
+ def test_resource_allowed_user_resource
77
+ @access_allowed_for = {
78
+ :user => [{
79
+ :directives => {:only => [:index, :show], :user_resource => true}
80
+ }]
81
+ }
82
+ assert_resource_allowed({
83
+ [{}, :admin, {}] => false,
84
+ [{:id => 1}, :admin, {:id => 1}] => false,
85
+ [{}, :admin, {:id => 1}] => false,
86
+ [{:id => 1}, :admin, {}] => false,
87
+ [{}, :user, {}] => false,
88
+ [{:id => 1}, :user, {:id => 1}] => true,
89
+ [{:id => 2}, :user, {:id => 1}] => false,
90
+ [{:id => 1}, :user, {:id => 2}] => false,
91
+ [{}, :user, {:id => 1}] => false,
92
+ [{:id => 1}, :user, {}] => false,
93
+ })
94
+ end
95
+
96
+ def test_resource_allowed_scope
97
+ @access_allowed_for = {
98
+ :user => [{
99
+ :directives => {:only => [:index, :show], :scope => :organization}
100
+ }]
101
+ }
102
+ assert_resource_allowed({
103
+ [{}, :admin, {}] => false,
104
+ [{:organization_id => 1}, :admin, {:organization => Resource.new({:id => 1})}] => false,
105
+ [{}, :admin, {:organization => Resource.new({:id => 1})}] => false,
106
+ [{:organization_id => 1}, :admin, {}] => false,
107
+ [{}, :user, {}] => false,
108
+ [{:organization_id => 1}, :user, {:organization => Resource.new({:id => 1})}] => true,
109
+ [{}, :user, {:organization => Resource.new({:id => 1})}] => false,
110
+ [{:organization_id => 1}, :user, {}] => false,
111
+ [{:organization_id => 2}, :user, {:organization => Resource.new({:id => 1})}] => false,
112
+ [{:organization_id => 1}, :user, {:organization => Resource.new({:id => 2})}] => false,
113
+ })
114
+ end
115
+
116
+ def test_resource_allowed_authenticated
117
+ @access_allowed_for = {
118
+ :all => [{
119
+ :directives => {:authenticated => true}
120
+ }]
121
+ }
122
+ assert !resource_allowed?({}, :admin, nil)
123
+ assert !resource_allowed?({}, :admin, true)
124
+ assert resource_allowed?({}, :all, true)
125
+ assert resource_allowed?({:action => :edit}, :all, true)
126
+ end
127
+
128
+ def test_block_allowed
129
+ @access_allowed_for = {
130
+ :admin => [{:block => MethodsTest.instance_method(:do_true)}],
131
+ :all => [{:block => MethodsTest.instance_method(:do_false)}]
132
+ }
133
+ assert_block_allowed({
134
+ :admin => true,
135
+ :all => false
136
+ })
137
+ end
138
+
139
+ def test_access_forbidden
140
+ assert_equal false, access_forbidden
141
+ end
142
+
143
+ def test_block_access
144
+ @access_allowed_for = {
145
+ :admin => [{
146
+ :directives => {}
147
+ }],
148
+ :editor => [{
149
+ :directives => {:only => :index}
150
+ }],
151
+ :blocked_guest => [{
152
+ :directives => {:only => :index},
153
+ :block => MethodsTest.instance_method(:do_false)
154
+ }],
155
+ :open_guest => [{
156
+ :directives => {:only => :index},
157
+ :block => MethodsTest.instance_method(:do_true)
158
+ }],
159
+ :complex => [
160
+ {:directives => {:only => :index}},
161
+ {:directives => {:only => :show}}
162
+ ],
163
+ :all => [{
164
+ :directives => {:only => :listing}
165
+ }]
166
+ }
167
+ assert_block_access({
168
+ [:admin, :index] => true,
169
+ [:admin, :show] => true,
170
+ [:admin, :unknown] => true,
171
+ [:editor, :unknown] => false,
172
+ [:editor, :index] => true,
173
+ [:blocked_guest, :index] => false,
174
+ [:blocked_guest, :unknown] => false,
175
+ [:open_guest, :index] => true,
176
+ [:open_guest, :unknown] => false,
177
+ [:all, :index] => false,
178
+ [:all, :unknown] => false,
179
+ [:all, :listing] => true,
180
+ [:complex, :index] => true,
181
+ [:complex, :show] => true,
182
+ [:complex, :unknown] => false
183
+ })
184
+ end
185
+
186
+ def test_block_access_closed
187
+ @access_allowed_for = {}
188
+ assert_equal false, block_access
189
+ end
190
+
191
+ def test_block_access_nil
192
+ @access_allowed_for = nil
193
+ assert_raises(ArgumentError) { block_access }
194
+ end
195
+
196
+ def test_block_access_on_object_with_role_and_accessors_defined
197
+ @access_allowed_for = {:special => [{
198
+ :directives => {}
199
+ }]}
200
+ @authenticated = Resource.new :role => 'user', :'special?' => true
201
+ @params = HashWithIndifferentAccess.new :action => :new
202
+ assert !block_access
203
+ end
204
+
205
+ def test_block_access_on_object_with_multiple_block_accessors_defined
206
+ @access_allowed_for = {:special => [{
207
+ :directives => {}
208
+ }]}
209
+ @authenticated = Resource.new :'special?' => true, :'admin?' => true
210
+ @params = HashWithIndifferentAccess.new :action => :new
211
+ assert !block_access
212
+ end
213
+
214
+ def test_block_access_on_object_with_accessor_dined_on_role
215
+ @access_allowed_for = {:user => [{
216
+ :directives => {}
217
+ }]}
218
+ @authenticated = Resource.new :role => 'user', :'special?' => true
219
+ @params = HashWithIndifferentAccess.new :action => :new
220
+ assert !block_access
221
+ end
222
+
223
+ private
224
+
225
+ def assert_action_allowed(h)
226
+ h.each do |pair, value|
227
+ params = HashWithIndifferentAccess.new(:action => pair.last)
228
+ assert_equal value, action_allowed?(params, pair.first), "For #{pair.inspect} => #{value.inspect}"
229
+ end
230
+ end
231
+
232
+ def assert_resource_allowed(h)
233
+ h.each do |triplet, value|
234
+ params = HashWithIndifferentAccess.new(triplet.first)
235
+ assert_equal value, resource_allowed?(params, triplet[1], triplet.last ? Resource.new(triplet.last) : nil), "For #{triplet.inspect} => #{value.inspect}"
236
+ end
237
+ end
238
+
239
+ def assert_block_allowed(h)
240
+ h.each do |role, value|
241
+ assert_equal value, block_allowed?(role)
242
+ end
243
+ end
244
+
245
+ def assert_block_access(h)
246
+ h.each do |pair, value|
247
+ @authenticated = Resource.new :role => pair.first
248
+ @params = {:action => pair.last}
249
+ assert_equal value, block_access, "For #{pair.inspect} => #{value.inspect}"
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ require 'controllers/application_controller'
4
+ require 'controllers/users_controller'
5
+ require 'models/resource'
6
+
7
+ class StructuralTest < ActionController::TestCase
8
+ tests UsersController
9
+
10
+ def setup
11
+ @controller.authenticated = Resource.new(:role => :admin)
12
+ end
13
+
14
+ test "rules should be in place" do
15
+ assert @controller.__send__(:access_allowed_for)
16
+ end
17
+
18
+ test "role accessors should not be public" do
19
+ assert @acontroller.public_methods.grep(/access_allowed_for/).empty?
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ require 'controllers/application_controller'
2
+ require 'controllers/authenticated_controller'
3
+ require 'controllers/broken_block_controller'
4
+ require 'controllers/complicated_controller'
5
+ require 'controllers/public_controller'
6
+ require 'controllers/multiple_roles_controller'
7
+ require 'controllers/users_controller'
@@ -0,0 +1,16 @@
1
+ class ApplicationController < ActionController::Base
2
+ attr_accessor :authenticated
3
+
4
+ before_filter :block_access
5
+
6
+ def access_forbidden
7
+ head :forbidden
8
+ false
9
+ end
10
+
11
+ def logger
12
+ @logger ||= Logger.new('/dev/null')
13
+ end
14
+
15
+ def rescue_action(e) raise e end;
16
+ end
@@ -0,0 +1,7 @@
1
+ class AuthenticatedController < ApplicationController
2
+ allow_access :authenticated
3
+
4
+ def index
5
+ head :ok
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ class BrokenBlockController < ApplicationController
2
+ allow_access(:only => :index) { nil.unknown_method }
3
+ allow_access(:only => :show) { true }
4
+ allow_access(:authenticated, :only => :edit) { @authenticated.unknown_method }
5
+ allow_access(:admin, :only => :edit) { @authenticated.unknown_method }
6
+
7
+ %w(index show edit).each do |name|
8
+ define_method(name) { head :ok }
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ class ComplicatedController < ApplicationController
2
+ allow_access :all, :only => :index
3
+ allow_access :authenticated, :only => [:show, :edit, :update], :user_resource => true
4
+
5
+ %w(index show edit update).each do |name|
6
+ define_method(name) { head :ok }
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ class MultipleRolesController < ApplicationController
2
+ allow_access :a, :b
3
+ allow_access [:c, :d]
4
+ allow_access [:e, :f], :only => :index
5
+ allow_access :g, :h, :only => :show
6
+
7
+ %w(index show).each do |name|
8
+ define_method(name) { head 200 }
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ class PublicController < ApplicationController
2
+ allow_access
3
+
4
+ def index
5
+ head :ok
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ class UsersController < ApplicationController
2
+ allow_access :admin
3
+ allow_access :editor, :only => [:index, :show]
4
+ allow_access(:guest, :only => :guest) { params[:action] == 'guest' }
5
+ allow_access :tester, :only => :show, :user_resource => true
6
+ allow_access :reader, :only => :show, :scope => :organization
7
+ allow_access :only => :listing
8
+ allow_access :only => :react
9
+
10
+ %w(index show guest listing react).each do |name|
11
+ define_method(name) { head :ok }
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ class Resource
2
+ def initialize(hash={})
3
+ @attributes = {}
4
+ hash.each do |k,v|
5
+ self.send("#{k}=", v)
6
+ end
7
+ end
8
+
9
+ def id
10
+ @attributes['id']
11
+ end
12
+
13
+ def id=(value)
14
+ @attributes['id'] = value
15
+ end
16
+
17
+ def method_missing(m, v=nil)
18
+ if m.to_s =~ /(.*)=$/
19
+ @attributes[$1] = v
20
+ else
21
+ if @attributes.has_key?(m.to_s)
22
+ @attributes[m.to_s]
23
+ else
24
+ raise NoMethodError, "We don't know anything about #{m}"
25
+ end
26
+ end
27
+ end
28
+
29
+ alias_method :old_respond_to?, :respond_to?
30
+ def respond_to?(m)
31
+ old_respond_to?(m) or @attributes.has_key?(m.to_s)
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ module AuthorizationSanTest
2
+ module Initializer
3
+ VENDOR_RAILS = File.expand_path('../../../../rails', __FILE__)
4
+ OTHER_RAILS = File.expand_path('../../../rails', __FILE__)
5
+ PLUGIN_ROOT = File.expand_path('../../', __FILE__)
6
+
7
+ def self.rails_directory
8
+ if File.exist?(VENDOR_RAILS)
9
+ VENDOR_RAILS
10
+ elsif File.exist?(OTHER_RAILS)
11
+ OTHER_RAILS
12
+ end
13
+ end
14
+
15
+ def self.load_dependencies
16
+ $stdout.write('Loading Rails from ')
17
+ if rails_directory
18
+ puts rails_directory
19
+ $:.unshift(File.join(rails_directory, 'activesupport', 'lib'))
20
+ $:.unshift(File.join(rails_directory, 'activerecord', 'lib'))
21
+ else
22
+ puts 'rubygems'
23
+ require 'rubygems' rescue LoadError
24
+ end
25
+
26
+ require 'test/unit'
27
+ require 'active_support'
28
+ require 'active_support/test_case'
29
+ require 'action_controller'
30
+ require 'action_controller/test_process'
31
+
32
+ require File.join(PLUGIN_ROOT, 'rails', 'init')
33
+
34
+ $:.unshift(File.join(PLUGIN_ROOT, 'lib'))
35
+ $:.unshift(File.join(PLUGIN_ROOT, 'test'))
36
+ end
37
+
38
+ def self.start
39
+ load_dependencies
40
+ ActionController::Routing::Routes.reload rescue nil
41
+ end
42
+ end
43
+ end
44
+
45
+ AuthorizationSanTest::Initializer.start
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authorization-san
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Manfred Stienstra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-29 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A plugin for authorization in a ReSTful application.
17
+ email: manfred@fngtps.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - LICENSE
27
+ - README.rdoc
28
+ - lib/authorization.rb
29
+ - lib/authorization/allow_access.rb
30
+ - lib/authorization/block_access.rb
31
+ - rails/init.rb
32
+ has_rdoc: true
33
+ homepage: http://fingertips.github.com
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: A plugin for authorization in a ReSTful application.
60
+ test_files:
61
+ - test/cases/behaviour_test.rb
62
+ - test/cases/internals_test.rb
63
+ - test/cases/structural_test.rb
64
+ - test/controllers/all.rb
65
+ - test/controllers/application_controller.rb
66
+ - test/controllers/authenticated_controller.rb
67
+ - test/controllers/broken_block_controller.rb
68
+ - test/controllers/complicated_controller.rb
69
+ - test/controllers/multiple_roles_controller.rb
70
+ - test/controllers/public_controller.rb
71
+ - test/controllers/users_controller.rb
72
+ - test/models/resource.rb
73
+ - test/test_helper.rb
74
+ - examples/administrations_controller.rb
75
+ - examples/application.rb
76
+ - examples/application_with_multiple_auth_methods.rb
77
+ - examples/authenticated_controller.rb
78
+ - examples/page_controller_with_full_policy.rb
79
+ - examples/pages_controller.rb
80
+ - examples/public_controller.rb
81
+ - examples/users_controller.rb