has_roles 0.0.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.rdoc +46 -0
  2. data/{MIT-LICENSE → LICENSE} +2 -2
  3. data/README.rdoc +92 -0
  4. data/Rakefile +49 -39
  5. data/app/models/permission.rb +48 -31
  6. data/app/models/role.rb +32 -24
  7. data/app/models/role_assignment.rb +5 -6
  8. data/app/models/role_permission.rb +25 -0
  9. data/db/migrate/{002_create_permissions.rb → 001_create_permissions.rb} +4 -5
  10. data/db/migrate/{003_create_roles.rb → 002_create_roles.rb} +1 -3
  11. data/db/migrate/003_create_role_permissions.rb +11 -0
  12. data/db/migrate/{005_create_role_assignments.rb → 004_create_role_assignments.rb} +2 -3
  13. data/lib/has_roles/authorization_helper.rb +66 -47
  14. data/lib/has_roles/url_helper.rb +38 -0
  15. data/lib/has_roles.rb +29 -56
  16. data/test/app_root/app/controllers/admin/base_controller.rb +2 -0
  17. data/test/app_root/app/controllers/admin/users_controller.rb +10 -1
  18. data/test/app_root/app/controllers/application_controller.rb +7 -0
  19. data/test/app_root/config/environment.rb +6 -19
  20. data/test/app_root/config/routes.rb +1 -0
  21. data/test/app_root/db/migrate/001_create_users.rb +1 -1
  22. data/test/app_root/db/migrate/002_migrate_has_roles_to_version_4.rb +13 -0
  23. data/test/factory.rb +63 -0
  24. data/test/functional/application_controller_test.rb +45 -0
  25. data/test/functional/has_roles_test.rb +68 -0
  26. data/test/helpers/authorization_helper_test.rb +92 -0
  27. data/test/helpers/url_helper_test.rb +35 -0
  28. data/test/test_helper.rb +13 -4
  29. data/test/unit/permission_test.rb +92 -27
  30. data/test/unit/role_assignment_test.rb +46 -15
  31. data/test/unit/role_permission_test.rb +34 -0
  32. data/test/unit/role_test.rb +122 -20
  33. metadata +93 -78
  34. data/CHANGELOG +0 -19
  35. data/README +0 -83
  36. data/app/models/controller.rb +0 -82
  37. data/db/migrate/001_create_controllers.rb +0 -13
  38. data/db/migrate/004_create_permissions_roles.rb +0 -13
  39. data/test/app_root/app/controllers/payment/pay_pal/transactions_controller.rb +0 -2
  40. data/test/app_root/app/controllers/payment/transactions_controller.rb +0 -2
  41. data/test/fixtures/controllers.yml +0 -19
  42. data/test/fixtures/permissions.yml +0 -27
  43. data/test/fixtures/permissions_roles.yml +0 -15
  44. data/test/fixtures/role_assignments.yml +0 -17
  45. data/test/fixtures/roles.yml +0 -11
  46. data/test/fixtures/users.yml +0 -11
  47. data/test/unit/authorization_helper_test.rb +0 -40
  48. data/test/unit/controller_test.rb +0 -95
  49. data/test/unit/has_roles_test.rb +0 -49
@@ -0,0 +1,38 @@
1
+ module HasRoles
2
+ # Provides helper methods for conditionally making links depending on
3
+ # whether the current user is authorized to access the url being linked to.
4
+ #
5
+ # In order to determine who the "current user" is, the helper methods
6
+ # assume that a +current_user+ helper has been defined. An example of an
7
+ # implementation of +current_user+ could be:
8
+ #
9
+ # class ApplicationController < ActionController::Base
10
+ # ...
11
+ #
12
+ # protected
13
+ # def current_user
14
+ # @current_user ||= session[:user_id] ? User.find_by_id(session[:user_id]) : nil
15
+ # end
16
+ # helper_method :current_user
17
+ # end
18
+ module UrlHelper
19
+ # Only link to the url if the current user is authorized to access it. In
20
+ # addition to the options normally available in +link_to+, the following
21
+ # options can be specified:
22
+ # * +show_text+ - If set to true, will only display the text if the user
23
+ # is not authorized for the link. (Default is +false+).
24
+ def link_to_if_authorized(name, options = {}, html_options = {}, &block)
25
+ text_on_no_authorization = html_options.delete(:show_text) ? name : ''
26
+
27
+ if authorized_for?(options)
28
+ link_to(name, options, html_options, &block)
29
+ else
30
+ text_on_no_authorization
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ ActionController::Base.class_eval do
37
+ helper HasRoles::UrlHelper
38
+ end
data/lib/has_roles.rb CHANGED
@@ -1,65 +1,38 @@
1
1
  require 'has_roles/authorization_helper'
2
+ require 'has_roles/url_helper'
2
3
 
3
- module PluginAWeek #:nodoc:
4
- module Has #:nodoc:
5
- # Provides dead simple role management
6
- module Roles
7
- def self.included(base) #:nodoc:
8
- base.extend(MacroMethods)
9
- end
4
+ # Adds a generic implementation for dealing with role management
5
+ module HasRoles
6
+ module MacroMethods
7
+ # Indicates that the model has roles. This will create the following
8
+ # associations:
9
+ # * +role_assignments+ - The join association for roles that have been
10
+ # assigned to a record in this model
11
+ # * +roles+ - The actual roles through the join association
12
+ def has_roles
13
+ has_many :role_assignments, :class_name => 'RoleAssignment', :as => :assignee, :dependent => :destroy
14
+ has_many :roles, :through => :role_assignments
10
15
 
11
- module MacroMethods
12
- # Indicates that the model has roles
13
- def has_roles
14
- has_many :role_assignments,
15
- :class_name => 'RoleAssignment',
16
- :as => :assignee,
17
- :dependent => :destroy
18
- has_many :roles,
19
- :through => :role_assignments
20
-
21
- include PluginAWeek::Has::Roles::InstanceMethods
22
- end
23
- end
24
-
25
- module InstanceMethods
26
- # Checks whether this user is authorized to access the given url.
27
- # Caching of authorizations is baked into the method. So long as the
28
- # same user object is used, an authorization for the same path will be
29
- # cached and returned.
30
- #
31
- # See <tt>Controller#recognize_path</tt> for more information about the possible
32
- # options that can be used.
33
- def authorized_for?(options = '')
34
- @authorizations ||= {}
35
-
36
- controller_path, action = Controller.recognize_path(options)
37
- options = {:controller => controller_path, :action => action}
38
- @authorizations["#{controller_path}/#{action}"] ||= Permission.restricts?(options) ? roles.find_all_authorized_for(options).any? : true
39
- end
40
-
41
- # Gets the ids of all roles associated with this user
42
- def role_ids
43
- roles.map(&:id)
44
- end
45
-
46
- # Sets the topics to associate with this fact.
47
- def role_ids=(ids)
48
- removed_ids = role_ids - ids
49
- new_ids = ids - role_ids
50
-
51
- transaction do
52
- role_assignments.delete(role_assignments.select {|assignment| removed_ids.include?(assignment.role_id)})
53
- new_ids.each {|id| role_assignments.create!(:role_id => id)}
54
- end
55
-
56
- roles.reload
57
- end
58
- end
16
+ include HasRoles::InstanceMethods
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ # Checks whether this user is authorized to access the given url.
22
+ #
23
+ # == Examples
24
+ #
25
+ # user = User.find(1)
26
+ # user.authorized_for?(:controller => 'admin/messages')
27
+ # user.authorized_for?(:controller => 'admin/messages', :action => 'destroy')
28
+ # user.authorized_for?('admin/messages')
29
+ # user.authorized_for?('http://localhost:3000/admin/messages')
30
+ def authorized_for?(options = '')
31
+ !Permission.restricts?(options) || roles.authorized_for(options).exists?
59
32
  end
60
33
  end
61
34
  end
62
35
 
63
36
  ActiveRecord::Base.class_eval do
64
- include PluginAWeek::Has::Roles
37
+ extend HasRoles::MacroMethods
65
38
  end
@@ -0,0 +1,2 @@
1
+ class Admin::BaseController < ApplicationController
2
+ end
@@ -1,2 +1,11 @@
1
- class Admin::UsersController < ApplicationController
1
+ class Admin::UsersController < Admin::BaseController
2
+ before_filter :authorization_required, :only => :destroy
3
+
4
+ def index
5
+ render :text => 'Welcome!'
6
+ end
7
+
8
+ def destroy
9
+ render :text => 'Destroyed!'
10
+ end
2
11
  end
@@ -0,0 +1,7 @@
1
+ class ApplicationController < ActionController::Base
2
+ protected
3
+ def current_user
4
+ @current_user ||= session[:user_id] ? User.find_by_id(session[:user_id]) : nil
5
+ end
6
+ helper_method :current_user
7
+ end
@@ -1,25 +1,12 @@
1
1
  require 'config/boot'
2
2
 
3
- $:.unshift("#{RAILS_ROOT}/../../../../../rails/plugin_dependencies/lib")
4
- begin
5
- require 'plugin_dependencies'
6
- rescue Exception => e
7
- end
8
-
9
3
  Rails::Initializer.run do |config|
10
- config.plugin_paths.concat([
11
- "#{RAILS_ROOT}/../../..",
12
- "#{RAILS_ROOT}/../../../../migrations",
13
- "#{RAILS_ROOT}/../../../../../rails",
14
- "#{RAILS_ROOT}/../../../../../test"
15
- ])
16
- config.plugins = [
17
- 'loaded_plugins',
18
- 'appable_plugins',
19
- 'plugin_migrations',
20
- File.basename(File.expand_path("#{RAILS_ROOT}/../..")),
21
- 'dry_validity_assertions'
22
- ]
4
+ config.plugin_paths << '..'
5
+ config.plugins = %w(enumerate_by has_roles)
23
6
  config.cache_classes = false
24
7
  config.whiny_nils = true
8
+ config.action_controller.session = {:key => 'rails_session', :secret => 'd229e4d22437432705ab3985d4d246'}
9
+ config.after_initialize do
10
+ EnumerateBy.perform_caching = false
11
+ end
25
12
  end
@@ -1,3 +1,4 @@
1
1
  ActionController::Routing::Routes.draw do |map|
2
+ map.connect '', :controller => 'home', :action => 'index'
2
3
  map.connect ':controller/:action/:id'
3
4
  end
@@ -1,7 +1,7 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.column :login, :string, :null => false
4
+ t.string :login, :null => false
5
5
  end
6
6
  end
7
7
 
@@ -0,0 +1,13 @@
1
+ class MigrateHasRolesToVersion4 < ActiveRecord::Migration
2
+ def self.up
3
+ ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
4
+ migration.migrate(:up)
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
10
+ migration.migrate(:down)
11
+ end
12
+ end
13
+ end
data/test/factory.rb ADDED
@@ -0,0 +1,63 @@
1
+ module Factory
2
+ # Build actions for the model
3
+ def self.build(model, &block)
4
+ name = model.to_s.underscore
5
+
6
+ define_method("#{name}_attributes", block)
7
+ define_method("valid_#{name}_attributes") {|*args| valid_attributes_for(model, *args)}
8
+ define_method("new_#{name}") {|*args| new_record(model, *args)}
9
+ define_method("create_#{name}") {|*args| create_record(model, *args)}
10
+ end
11
+
12
+ # Get valid attributes for the model
13
+ def valid_attributes_for(model, attributes = {})
14
+ name = model.to_s.underscore
15
+ send("#{name}_attributes", attributes)
16
+ attributes.stringify_keys!
17
+ attributes
18
+ end
19
+
20
+ # Build an unsaved record
21
+ def new_record(model, *args)
22
+ attributes = valid_attributes_for(model, *args)
23
+ record = model.new(attributes)
24
+ attributes.each {|attr, value| record.send("#{attr}=", value) if model.accessible_attributes && !model.accessible_attributes.include?(attr) || model.protected_attributes && model.protected_attributes.include?(attr)}
25
+ record
26
+ end
27
+
28
+ # Build and save/reload a record
29
+ def create_record(model, *args)
30
+ record = new_record(model, *args)
31
+ record.save!
32
+ record.reload
33
+ record
34
+ end
35
+
36
+ build Permission do |attributes|
37
+ attributes.reverse_merge!(
38
+ :controller => 'admin/users'
39
+ )
40
+ end
41
+
42
+ build Role do |attributes|
43
+ attributes.reverse_merge!(
44
+ :name => 'developer'
45
+ )
46
+ end
47
+
48
+ build RoleAssignment do |attributes|
49
+ attributes[:role] = create_role unless attributes.include?(:role)
50
+ attributes[:assignee] = create_user unless attributes.include?(:assignee)
51
+ end
52
+
53
+ build RolePermission do |attributes|
54
+ attributes[:permission] = create_permission unless attributes.include?(:permission)
55
+ attributes[:role] = create_role unless attributes.include?(:role)
56
+ end
57
+
58
+ build User do |attributes|
59
+ attributes.reverse_merge!(
60
+ :login => 'admin'
61
+ )
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class ControllerLoggedOutTest < ActionController::TestCase
4
+ tests Admin::UsersController
5
+
6
+ attr_reader :session
7
+
8
+ def setup
9
+ @request.session[:user_id] = nil
10
+
11
+ post :destroy, :id => 1
12
+ end
13
+
14
+ def test_should_not_be_successful
15
+ assert_equal '401', @response.code
16
+ end
17
+ end
18
+
19
+ class ControllerLoggedInTest < ActionController::TestCase
20
+ tests Admin::UsersController
21
+
22
+ attr_reader :session
23
+
24
+ def setup
25
+ @user = create_user
26
+ @request.session[:user_id] = @user.id
27
+
28
+ @role = create_role(:name => 'developer')
29
+ create_role_permission(:role => @role, :permission => create_permission(:controller => 'admin/users'))
30
+ end
31
+
32
+ def test_should_be_successful_if_authorized
33
+ create_role_assignment(:role => @role, :assignee => @user)
34
+
35
+ post :destroy, :id => 1
36
+
37
+ assert_response :success
38
+ end
39
+
40
+ def test_should_not_be_successful_if_unauthorized
41
+ post :destroy, :id => 1
42
+
43
+ assert_equal '401', @response.code
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class UserAfterBeingCreatedTest < ActiveRecord::TestCase
4
+ def setup
5
+ @user = create_user
6
+ end
7
+
8
+ def test_should_not_have_any_role_assignments
9
+ assert @user.role_assignments.empty?
10
+ end
11
+
12
+ def test_should_not_have_any_roles
13
+ assert @user.roles.empty?
14
+ end
15
+ end
16
+
17
+ class UserWithoutRoleAssignmentsTest < ActiveRecord::TestCase
18
+ def setup
19
+ @user = create_user
20
+ end
21
+
22
+ def test_should_be_authorized_if_path_is_not_permissioned
23
+ assert @user.authorized_for?('/users/create')
24
+ end
25
+
26
+ def test_should_not_be_authorized_if_path_is_permissioned
27
+ create_permission(:controller => 'users', :action => 'create')
28
+ assert !@user.authorized_for?('/users/create')
29
+ end
30
+ end
31
+
32
+ class UserWithRoleAssignmentsTest < ActiveRecord::TestCase
33
+ def setup
34
+ @user = create_user
35
+ @administrator = create_role(:name => 'administrator')
36
+ create_role_permission(:role => @administrator, :permission => create_permission(:controller => 'admin/users'))
37
+ create_role_assignment(:assignee => @user, :role => @administrator)
38
+ end
39
+
40
+ def test_should_have_roles
41
+ assert_equal [@administrator], @user.roles
42
+ end
43
+
44
+ def test_should_be_authorized_if_path_is_not_permissioned
45
+ assert @user.authorized_for?('')
46
+ end
47
+
48
+ def test_should_be_authorized_if_role_has_permission
49
+ assert @user.authorized_for?('/admin/users')
50
+ end
51
+
52
+ def test_should_not_be_authorized_if_no_roles_have_permission
53
+ create_permission(:controller => 'users')
54
+ assert !@user.authorized_for?('/users')
55
+ end
56
+ end
57
+
58
+ class UserAfterBeingDestroyedTest < ActiveRecord::TestCase
59
+ def setup
60
+ @user = create_user
61
+ @administrator = create_role_assignment(:assignee => @user)
62
+ @user.destroy
63
+ end
64
+
65
+ def test_should_destroy_associated_role_assignments
66
+ assert_nil RoleAssignment.find_by_id(@administrator.id)
67
+ end
68
+ end
@@ -0,0 +1,92 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class AuthorizationHelperTest < ActionController::TestCase
4
+ tests Admin::UsersController
5
+
6
+ include ActionController::Base.master_helper_module
7
+
8
+ def test_should_respond_to_authorized?
9
+ assert respond_to?(:authorized?)
10
+ end
11
+
12
+ def test_should_respond_to_authorized_for?
13
+ assert respond_to?(:authorized_for?)
14
+ end
15
+ end
16
+
17
+ class AuthorizationHelperLoggedOutTest < ActionController::TestCase
18
+ tests Admin::UsersController
19
+
20
+ include ActionController::Base.master_helper_module
21
+
22
+ attr_reader :controller, :session
23
+
24
+ def setup
25
+ @session = @controller.session = ActionController::TestSession.new
26
+ session[:user_id] = nil
27
+ end
28
+
29
+ def test_should_not_be_authorized_for_current_page
30
+ assert !authorized?
31
+ end
32
+
33
+ def test_should_not_be_authorized_for_custom_url
34
+ assert !authorized_for?('admin/users/destroy')
35
+ end
36
+ end
37
+
38
+ class AuthorizationHelperWithoutPermissionsTest < ActionController::TestCase
39
+ tests Admin::UsersController
40
+
41
+ include ActionController::Base.master_helper_module
42
+
43
+ attr_reader :controller, :session
44
+
45
+ def setup
46
+ get :index
47
+
48
+ @session = @controller.session = ActionController::TestSession.new
49
+
50
+ @user = create_user
51
+ session[:user_id] = @user.id
52
+
53
+ create_permission(:controller => 'admin/users')
54
+ end
55
+
56
+ def test_should_not_be_authorized_for_current_page
57
+ assert !authorized?
58
+ end
59
+
60
+ def test_should_not_be_authorized_for_custom_url
61
+ assert !authorized_for?('/admin/users/destroy/1')
62
+ end
63
+ end
64
+
65
+ class AuthorizationHelperWithPermissionsTest < ActionController::TestCase
66
+ tests Admin::UsersController
67
+
68
+ include ActionController::Base.master_helper_module
69
+
70
+ attr_reader :controller, :session
71
+
72
+ def setup
73
+ get :index
74
+
75
+ @session = @controller.session = ActionController::TestSession.new
76
+
77
+ @user = create_user
78
+ session[:user_id] = @user.id
79
+
80
+ role = create_role(:name => 'developer')
81
+ create_role_permission(:role => role, :permission => create_permission(:controller => 'admin/users'))
82
+ create_role_assignment(:role => role, :assignee => @user)
83
+ end
84
+
85
+ def test_should_be_authorized_for_current_page_if_user_has_proper_permissions
86
+ assert authorized?
87
+ end
88
+
89
+ def test_should_be_authorized_for_custom_url_if_user_has_proper_permissions
90
+ assert authorized_for?('/admin/users/destroy/1')
91
+ end
92
+ end
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class UrlHelperUnauthorizedTest < ActionView::TestCase
4
+ tests HasRoles::UrlHelper
5
+
6
+ def test_should_link_to_nothing_if_not_showing_text
7
+ assert_equal '', link_to_if_authorized('Destroy User', '/admin/users/destroy', :show_text => false)
8
+ end
9
+
10
+ def test_should_display_text_if_showing_text
11
+ assert_equal 'Destroy User', link_to_if_authorized('Destroy User', '/admin/users/destroy', :show_text => true)
12
+ end
13
+
14
+ private
15
+ def authorized_for?(url)
16
+ false
17
+ end
18
+ end
19
+
20
+ class UrlHelperAuthorizedTest < ActionView::TestCase
21
+ tests HasRoles::UrlHelper
22
+
23
+ def setup
24
+ @controller = ActionView::TestCase::TestController.new
25
+ end
26
+
27
+ def test_should_link_to_url
28
+ assert_equal '<a href="/admin/users/destroy">Destroy User</a>', link_to_if_authorized('Destroy User', '/admin/users/destroy', :show_text => false)
29
+ end
30
+
31
+ private
32
+ def authorized_for?(url)
33
+ true
34
+ end
35
+ end
data/test/test_helper.rb CHANGED
@@ -1,9 +1,18 @@
1
1
  # Load the plugin testing framework
2
- $:.unshift("#{File.dirname(__FILE__)}/../../../../test/plugin_test_helper/lib")
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
3
3
  require 'rubygems'
4
4
  require 'plugin_test_helper'
5
5
 
6
- PluginAWeek::PluginMigrations.migrate('has_roles')
7
-
8
6
  # Run the migrations
9
- ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")
7
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
8
+
9
+ # Mixin the factory helper
10
+ require File.expand_path("#{File.dirname(__FILE__)}/factory")
11
+ Test::Unit::TestCase.class_eval do
12
+ include Factory
13
+ end
14
+
15
+ # Remove defaults for testing
16
+ RolePermission.delete_all
17
+ Permission.delete_all
18
+ Role.delete_all