has_roles 0.0.2 → 0.3.0

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