radiant-rbac_base-extension 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/HELP_developer.md +18 -0
- data/README.markdown +30 -0
- data/Rakefile +137 -0
- data/VERSION +1 -0
- data/app/controllers/admin/roles_controller.rb +91 -0
- data/app/helpers/admin/alterations_helper.rb +9 -0
- data/app/helpers/admin/roles_helper.rb +5 -0
- data/app/models/role.rb +33 -0
- data/app/models/role_action_observer.rb +13 -0
- data/app/models/role_user.rb +2 -0
- data/app/views/admin/roles/_add_role_form.html.haml +4 -0
- data/app/views/admin/roles/index.html.haml +30 -0
- data/app/views/admin/roles/show.html.haml +29 -0
- data/app/views/admin/users/preferences.html.haml +34 -0
- data/config/locales/en.yml +3 -0
- data/config/routes.rb +8 -0
- data/cucumber.yml +1 -0
- data/db/migrate/001_create_roles.rb +11 -0
- data/db/migrate/002_create_role_users.rb +16 -0
- data/db/migrate/003_setup_standard_roles.rb +29 -0
- data/db/migrate/004_alter_roles.rb +10 -0
- data/db/migrate/005_add_standard_role_details.rb +20 -0
- data/db/migrate/006_add_user_info.rb +10 -0
- data/db/migrate/20100705182511_rename_role_developer_to_designer.rb +13 -0
- data/features/support/env.rb +16 -0
- data/features/support/paths.rb +14 -0
- data/lib/rbac_support.rb +19 -0
- data/lib/tasks/rbac_base_extension_tasks.rake +55 -0
- data/public/javascripts/rbac/admin/role_details.js +162 -0
- data/public/stylesheets/rbac/rbac.css +20 -0
- data/radiant-rbac_base-extension.gemspec +91 -0
- data/rbac_base_extension.rb +28 -0
- data/spec/controllers/admin/roles_controller_spec.rb +142 -0
- data/spec/controllers/admin/roles_routing_spec.rb +27 -0
- data/spec/helpers/admin/roles_helper_spec.rb +12 -0
- data/spec/models/role_spec.rb +82 -0
- data/spec/models/user_spec.rb +9 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/views/admin/roles/index_spec.rb +17 -0
- data/spec/views/admin/roles/show_spec.rb +14 -0
- metadata +129 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.svn
|
data/HELP_developer.md
ADDED
@@ -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]
|
data/README.markdown
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/app/models/role.rb
ADDED
@@ -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,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();"
|