radiant-rbac_base-extension 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +1 -0
  2. data/HELP_developer.md +18 -0
  3. data/README.markdown +30 -0
  4. data/Rakefile +137 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/admin/roles_controller.rb +91 -0
  7. data/app/helpers/admin/alterations_helper.rb +9 -0
  8. data/app/helpers/admin/roles_helper.rb +5 -0
  9. data/app/models/role.rb +33 -0
  10. data/app/models/role_action_observer.rb +13 -0
  11. data/app/models/role_user.rb +2 -0
  12. data/app/views/admin/roles/_add_role_form.html.haml +4 -0
  13. data/app/views/admin/roles/index.html.haml +30 -0
  14. data/app/views/admin/roles/show.html.haml +29 -0
  15. data/app/views/admin/users/preferences.html.haml +34 -0
  16. data/config/locales/en.yml +3 -0
  17. data/config/routes.rb +8 -0
  18. data/cucumber.yml +1 -0
  19. data/db/migrate/001_create_roles.rb +11 -0
  20. data/db/migrate/002_create_role_users.rb +16 -0
  21. data/db/migrate/003_setup_standard_roles.rb +29 -0
  22. data/db/migrate/004_alter_roles.rb +10 -0
  23. data/db/migrate/005_add_standard_role_details.rb +20 -0
  24. data/db/migrate/006_add_user_info.rb +10 -0
  25. data/db/migrate/20100705182511_rename_role_developer_to_designer.rb +13 -0
  26. data/features/support/env.rb +16 -0
  27. data/features/support/paths.rb +14 -0
  28. data/lib/rbac_support.rb +19 -0
  29. data/lib/tasks/rbac_base_extension_tasks.rake +55 -0
  30. data/public/javascripts/rbac/admin/role_details.js +162 -0
  31. data/public/stylesheets/rbac/rbac.css +20 -0
  32. data/radiant-rbac_base-extension.gemspec +91 -0
  33. data/rbac_base_extension.rb +28 -0
  34. data/spec/controllers/admin/roles_controller_spec.rb +142 -0
  35. data/spec/controllers/admin/roles_routing_spec.rb +27 -0
  36. data/spec/helpers/admin/roles_helper_spec.rb +12 -0
  37. data/spec/models/role_spec.rb +82 -0
  38. data/spec/models/user_spec.rb +9 -0
  39. data/spec/spec.opts +6 -0
  40. data/spec/spec_helper.rb +36 -0
  41. data/spec/views/admin/roles/index_spec.rb +17 -0
  42. data/spec/views/admin/roles/show_spec.rb +14 -0
  43. metadata +129 -0
@@ -0,0 +1 @@
1
+ .svn
@@ -0,0 +1,18 @@
1
+ With RBAC Base you can develop extensions that require their own roles.
2
+
3
+ ## Developing for RBAC Base
4
+ To create an extension that uses it's own role, simply add fields to the
5
+ database:
6
+
7
+ Role.create(:role_name => 'Finance', :allow_empty => false, :description => 'Only users in the Finance role may view financial data')
8
+
9
+ Then, your extension will automatically be able to use `current_user.finance?`
10
+ to return a boolean value based on the user being in that role.
11
+
12
+ By setting `allow_empty` to `false`, the role management interface will
13
+ not allow the last user to be removed from your role.
14
+
15
+ Once you have the role you need, you can even set the visibility of any
16
+ tabs that you create in your extension with your new role:
17
+
18
+ admin.tabs.add "Finance", "/admin/finance", :after => "Pages", :visibility => [:finance]
@@ -0,0 +1,30 @@
1
+ # RBAC (Role Based Access Control) Base
2
+
3
+ This extension is used by authors of other extensions to hide those
4
+ extensions from users based on admin defined groups. Standard Radiant
5
+ groups consist of admin and developer. This adds the ability
6
+ to create groups such as finance.
7
+
8
+ Installing:
9
+ Run 'rake radiant:extensions:rbac_base:migrate'
10
+
11
+ Installing the public files:
12
+ Run 'rake radiant:extensions:rbac_base:update'
13
+
14
+ RBAC Base adds a `roles` table, a `roles_users` table, and creates
15
+ the `has_and_belongs_to_many` relationship between users and roles.
16
+
17
+ By default, a configuration setting will allow Admin users to see
18
+ everything. You may change this by setting
19
+
20
+ Radiant::Config['roles.admin.sees_everything'] = 'false'
21
+
22
+ Then you can, for example, use extensions that require their own roles but
23
+ prevent your client from seeing unimportant technical details or areas that
24
+ may be beyond his or her understanding. So your client may be in the 'Admin'
25
+ role so that they can manage users, but would be restricted from seeing
26
+ details from your extension.
27
+
28
+ See more details in HELP_developer.md
29
+
30
+ Built by Saturn Flyer http://www.saturnflyer.com
@@ -0,0 +1,137 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "radiant-rbac_base-extension"
5
+ gem.summary = %Q{RBAC Base Extension for Radiant CMS}
6
+ gem.description = %Q{Flexible user role management for Radiant.}
7
+ gem.email = "jim@saturnflyer.com"
8
+ gem.homepage = "http://github.com/saturnflyer/radiant-rbac_base-extension"
9
+ gem.authors = ["Jim Gay"]
10
+ gem.add_dependency 'radiant', '>= 0.9'
11
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
12
+ end
13
+ rescue LoadError
14
+ puts "Jeweler (or a dependency) not available. This is only required if you plan to package rbac_base as a gem."
15
+ end
16
+
17
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
18
+ # Check to see if the rspec plugin is installed first and require
19
+ # it if it is. If not, use the gem version.
20
+
21
+ # Determine where the RSpec plugin is by loading the boot
22
+ unless defined? RADIANT_ROOT
23
+ ENV["RAILS_ENV"] = "test"
24
+ case
25
+ when ENV["RADIANT_ENV_FILE"]
26
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
27
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
28
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
29
+ else
30
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
31
+ end
32
+ end
33
+
34
+ require 'rake'
35
+ require 'rake/rdoctask'
36
+ require 'rake/testtask'
37
+
38
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
39
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
40
+ require 'spec/rake/spectask'
41
+ require 'cucumber'
42
+ require 'cucumber/rake/task'
43
+
44
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
45
+ Object.send(:remove_const, :RADIANT_ROOT)
46
+
47
+ extension_root = File.expand_path(File.dirname(__FILE__))
48
+
49
+ task :default => :spec
50
+ task :stats => "spec:statsetup"
51
+
52
+ desc "Run all specs in spec directory"
53
+ Spec::Rake::SpecTask.new(:spec) do |t|
54
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
55
+ t.spec_files = FileList['spec/**/*_spec.rb']
56
+ end
57
+
58
+ task :features => 'spec:integration'
59
+
60
+ namespace :spec do
61
+ desc "Run all specs in spec directory with RCov"
62
+ Spec::Rake::SpecTask.new(:rcov) do |t|
63
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
64
+ t.spec_files = FileList['spec/**/*_spec.rb']
65
+ t.rcov = true
66
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
67
+ end
68
+
69
+ desc "Print Specdoc for all specs"
70
+ Spec::Rake::SpecTask.new(:doc) do |t|
71
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
72
+ t.spec_files = FileList['spec/**/*_spec.rb']
73
+ end
74
+
75
+ [:models, :controllers, :views, :helpers].each do |sub|
76
+ desc "Run the specs under spec/#{sub}"
77
+ Spec::Rake::SpecTask.new(sub) do |t|
78
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
79
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
80
+ end
81
+ end
82
+
83
+ desc "Run the Cucumber features"
84
+ Cucumber::Rake::Task.new(:integration) do |t|
85
+ t.fork = true
86
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
87
+ # t.feature_pattern = "#{extension_root}/features/**/*.feature"
88
+ t.profile = "default"
89
+ end
90
+
91
+ # Setup specs for stats
92
+ task :statsetup do
93
+ require 'code_statistics'
94
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
95
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
96
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
97
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
98
+ ::CodeStatistics::TEST_TYPES << "Model specs"
99
+ ::CodeStatistics::TEST_TYPES << "View specs"
100
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
101
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
102
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
103
+ end
104
+
105
+ namespace :db do
106
+ namespace :fixtures do
107
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
108
+ task :load => :environment do
109
+ require 'active_record/fixtures'
110
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
111
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
112
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ desc 'Generate documentation for the rbac_base extension.'
120
+ Rake::RDocTask.new(:rdoc) do |rdoc|
121
+ rdoc.rdoc_dir = 'rdoc'
122
+ rdoc.title = 'RbacBaseExtension'
123
+ rdoc.options << '--line-numbers' << '--inline-source'
124
+ rdoc.rdoc_files.include('README')
125
+ rdoc.rdoc_files.include('lib/**/*.rb')
126
+ end
127
+
128
+ # For extensions that are in transition
129
+ desc 'Test the rbac_base extension.'
130
+ Rake::TestTask.new(:test) do |t|
131
+ t.libs << 'lib'
132
+ t.pattern = 'test/**/*_test.rb'
133
+ t.verbose = true
134
+ end
135
+
136
+ # Load any custom rakefiles for extension
137
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.0
@@ -0,0 +1,91 @@
1
+ class Admin::RolesController < ApplicationController
2
+ only_allow_access_to :index, :show, :new, :create, :edit, :update, :remove_user, :add_user, :users, :destroy,
3
+ :when => :admin,
4
+ :denied_url => { :controller => 'pages', :action => 'index' },
5
+ :denied_message => 'You must have administrative privileges to edit Roles.'
6
+ skip_before_filter :verify_authenticity_token, :only => [:users, :remove_user, :add_user]
7
+ def index
8
+ @roles = Role.find(:all)
9
+ @role = Role.new
10
+ end
11
+
12
+ def show
13
+ @role = Role.find(params[:id])
14
+ end
15
+
16
+ def edit
17
+
18
+ end
19
+ def new
20
+
21
+ end
22
+ def update
23
+ @role = Role.find(params[:id])
24
+ if @role.update_attributes(params[:role])
25
+ redirect_to admin_role_path(@role)
26
+ else
27
+ render :action => 'show'
28
+ end
29
+ end
30
+
31
+ def create
32
+ @role = Role.new(params[:role])
33
+ @role.save!
34
+ redirect_to admin_roles_path
35
+ rescue ActiveRecord::RecordInvalid => invalid
36
+ flash[:error] = invalid.record.errors.full_messages
37
+ render :action => :index
38
+ end
39
+
40
+ def destroy
41
+ @role = Role.find(params[:id])
42
+ @role.destroy unless @role.standard?
43
+ redirect_to admin_roles_path()
44
+ rescue ActiveRecord::RecordNotFound
45
+ flash[:error] = 'The specified Role could not be found.'
46
+ redirect_to :action => :index
47
+ end
48
+
49
+ def users
50
+ role = Role.find(params[:role_id])
51
+
52
+ available_users = User.find(:all, :conditions => ['id NOT IN (SELECT user_id FROM roles_users WHERE role_id = ?)', role.id])
53
+ taken_users = role.users
54
+
55
+ result = {:available => [], :taken => []}
56
+
57
+ available_users.each do | usr |
58
+ result[:available] << [usr.id, usr.name]
59
+ end
60
+
61
+ taken_users.each do | usr |
62
+ result[:taken] << [usr.id, usr.name]
63
+ end
64
+
65
+ respond_to do |format|
66
+ format.js { render :json => result.to_json }
67
+ end
68
+ end
69
+
70
+ def add_user
71
+ role = Role.find(params[:role_id])
72
+ user = User.find(params[:id])
73
+
74
+ if role.users << user
75
+ render :json => {:status => "Ok", :role_id => role.id, :user_id => user.id }.to_json
76
+ else
77
+ render :json => {:status => "Error", :user_id => user.id }.to_json
78
+ end
79
+ end
80
+
81
+ def remove_user
82
+ role = Role.find(params[:role_id])
83
+ user = User.find(params[:id])
84
+
85
+ if role.remove_user(user)
86
+ render :json => {:status => "Ok", :role_id => role.id, :user_id => user.id }.to_json
87
+ else
88
+ render :json => {:status => "Error", :user_id => user.id }.to_json
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,9 @@
1
+ module Admin::AlterationsHelper
2
+ def roles(user)
3
+ list = []
4
+ user.roles.each do |role|
5
+ list << role.role_name
6
+ end
7
+ list.join(', ')
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Admin::RolesHelper
2
+ def role_spinner(which)
3
+ image('spinner.gif', :alt => "loading...")
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+ class Role < ActiveRecord::Base
2
+ has_and_belongs_to_many :users
3
+ validates_uniqueness_of :role_name
4
+ belongs_to :created_by, :class_name => 'User'
5
+ belongs_to :updated_by, :class_name => 'User'
6
+
7
+ default_scope :order => ["role_name"]
8
+
9
+ class ProtectedRoleError < StandardError; end
10
+
11
+ before_destroy :verify_non_standard
12
+
13
+ RADIANT_STANDARDS = ['admin', 'designer']
14
+
15
+ def verify_non_standard
16
+ if standard?
17
+ raise Role::ProtectedRoleError, "`#{self[:role_name]}' is a protected role and may not be removed."
18
+ end
19
+ end
20
+
21
+ def remove_user(user)
22
+ if users.size <= 1 && allow_empty == false
23
+ return false
24
+ else
25
+ users.delete(user)
26
+ return true
27
+ end
28
+ end
29
+
30
+ def standard?
31
+ RADIANT_STANDARDS.include?(role_name.downcase)
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ class RoleActionObserver < ActiveRecord::Observer
2
+ observe Role
3
+
4
+ cattr_accessor :current_user
5
+
6
+ def before_create(model)
7
+ model.created_by = @@current_user
8
+ end
9
+
10
+ def before_update(model)
11
+ model.updated_by = @@current_user
12
+ end
13
+ end
@@ -0,0 +1,2 @@
1
+ class RoleUser < ActiveRecord::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ - form_for @role, :url => admin_roles_path do |form|
2
+ %p
3
+ = form.text_field :role_name
4
+ = save_model_button(@role)
@@ -0,0 +1,30 @@
1
+ %h1 Role Management
2
+ %p Use this screen to manage your user roles.
3
+ %p
4
+ %strong Caution:
5
+ Roles are defined as a security mechanism for the extensions that are restricted by user role. User roles are normally added by these extensions at installation time. Removing roles may cause problems with the role based security.
6
+ %strong Only remove roles if you absolutely must.
7
+ %h2 Roles
8
+ %table.index
9
+ %thead
10
+ %tr
11
+ %th Role
12
+ %th # Users
13
+ %th Description
14
+ %th Modify
15
+ %tbody
16
+ - unless @roles.blank?
17
+ - @roles.each do |role|
18
+ %tr
19
+ %td= link_to role.role_name, admin_role_path(role)
20
+ %td= role.users.count
21
+ %td= role.description
22
+ %td.remove
23
+ - unless role.standard?
24
+ = link_to 'Remove', admin_role_path(role), :confirm => "Are you sure?", :method => :delete
25
+ - else
26
+ %tr
27
+ %td{ :colspan => 4} No Roles yet created.
28
+ #actions
29
+ = render :partial => 'add_role_form'
30
+ - include_stylesheet 'rbac/rbac'
@@ -0,0 +1,29 @@
1
+ - include_javascript 'admin/dragdrop'
2
+ - include_javascript 'rbac/admin/role_details'
3
+ - include_stylesheet 'rbac/rbac'
4
+ - @body_classes ||= [].tap{|a| a << 'reversed'}
5
+ %h1= @role.role_name
6
+ %script{:language => "javascript"}
7
+ = "var role_id = #{@role.id};"
8
+ %p Drag and drop users between the lists below to add or remove them from this role. The changes that you make will take effect immediately.
9
+ %h2 Active Users
10
+ %ul#taken_users.UserList{:multiple => "multiple"}
11
+ %li#busy_taken.rbac_busy_marker= role_spinner 'taken'
12
+
13
+ %h2 Available Users
14
+ %ul#available_users.UserList{:multiple => "multiple"}
15
+ %li#busy_available.rbac_busy_marker= role_spinner 'available'
16
+
17
+ %h3 Edit Role Details
18
+ .form-area
19
+ - form_for @role, :url => admin_role_path(@role) do |f|
20
+ %p.title
21
+ = f.label :description
22
+ = f.text_field :description, :class => 'textbox'
23
+ %p
24
+ = f.check_box :allow_empty, :class => 'checkbox'
25
+ = f.label :allow_empty, "Allow this role to have no users.", :class => 'checkbox'
26
+ %p
27
+ = f.submit
28
+ or
29
+ = link_to "Cancel", admin_roles_path()
@@ -0,0 +1,34 @@
1
+ %h1 User Preferences
2
+
3
+ - form_tag do
4
+ %table.fieldset{:cellpadding=>0, :cellspacing=>0, :border=>0}
5
+ %tr
6
+ %th.label
7
+ %label{:for=>"user_password"} Password
8
+ %td.field
9
+ = password_field "user", "password", :class => 'textbox', :value => '', :maxlength => 40
10
+ %td.help{:rowspan=>2}
11
+ At least 5 characters. Leave password blank for it to remain unchanged.
12
+ %tr
13
+ %th.label
14
+ %label{:for=>"user_password_confirmation"} Confirm Password
15
+ %td.field
16
+ = password_field "user", "password_confirmation", :class => 'textbox', :value => '', :maxlength => 40
17
+ %tr
18
+ %th.label
19
+ %label{:for=>"user_email"} E-mail
20
+ %td.field
21
+ = text_field "user", "email", :class => 'textbox', :maxlength => 255
22
+ %td.help Optional.
23
+ - unless @user.roles.blank?
24
+ %tr
25
+ %th.label Your Roles
26
+ %td
27
+ = roles(@user)
28
+ %td.help Your roles are assigned to you by an Admin and control your access to parts of this interface.
29
+ %p.buttons
30
+ = save_model_button @user
31
+ or
32
+ = link_to 'Cancel', admin_url
33
+
34
+ = javascript_tag "$('user_password').activate();"