adva_rbac 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/NOTES +98 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/adva_rbac.gemspec +17 -0
- data/app/controllers/roles_controller.rb +28 -0
- data/app/helpers/roles_helper.rb +64 -0
- data/app/views/admin/sections/settings/_permissions.html.erb +16 -0
- data/app/views/roles/index.js.erb +3 -0
- data/config/initializers/base_controller.rb +4 -0
- data/config/initializers/rbac.rb +60 -0
- data/config/initializers/user.rb +80 -0
- data/db/migrate/20080402000006_create_role_tables.rb +13 -0
- data/db/migrate/20090720132900_migrate_roles_table_to_new_rbac.rb +15 -0
- data/lib/action_controller/guards_permissions.rb +77 -0
- data/lib/adva_rbac.rb +18 -0
- data/lib/adva_rbac/version.rb +3 -0
- data/lib/permission_map.rb +70 -0
- data/lib/rbac.rb +26 -0
- data/lib/rbac/acts_as_role_context.rb +44 -0
- data/lib/rbac/acts_as_role_subject.rb +65 -0
- data/lib/rbac/context.rb +85 -0
- data/lib/rbac/role.rb +10 -0
- data/lib/rbac/role_type.rb +73 -0
- data/lib/rbac/role_type/active_record.rb +47 -0
- data/lib/rbac/role_type/static.rb +144 -0
- data/lib/rbac/subject.rb +52 -0
- data/test/functional/roles_controller_test.rb +21 -0
- data/test/integration/user_rbac_test.rb +34 -0
- data/test/rbac/all.rb +3 -0
- data/test/rbac/database.rb +155 -0
- data/test/rbac/database.yml +3 -0
- data/test/rbac/implementation/active_record_test.rb +17 -0
- data/test/rbac/implementation/static_test.rb +14 -0
- data/test/rbac/static.rb +25 -0
- data/test/rbac/test_helper.rb +62 -0
- data/test/rbac/tests/acts_as_role_context.rb +37 -0
- data/test/rbac/tests/context.rb +35 -0
- data/test/rbac/tests/group.rb +40 -0
- data/test/rbac/tests/has_role.rb +126 -0
- data/test/rbac/tests/role_type.rb +110 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/helpers/roles_helper_test.rb +69 -0
- data/test/unit/models/rbac_context_test.rb +37 -0
- data/test/unit/models/rbac_user_test.rb +100 -0
- data/test/unit/models/role_test.rb +185 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8601befb94e314af526920008bc499db4c524607
|
4
|
+
data.tar.gz: c067b09f9c1171b501156c809bf795520b2095fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ff6836c3ebd7b261f67cf1a6a8d6806bb68c8113ae5e625c02546c981c0f9fa8eb2cda52c33797a1033036e36737dbb377d182a8095b0a6f15f3930f8dcff34
|
7
|
+
data.tar.gz: d5a020b59751c73437dc5473d94873d452034097f55ce0be09564bb7e0edfa3ff97ad1d18d52dcc6057b2278c0fee9f71e43fa828ddc6703143b97ccc9f3cc5c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Micah Geisel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NOTES
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
1. negotiating required vs. granted roles
|
2
|
+
|
3
|
+
required_roles = context.required_roles_for(action)
|
4
|
+
has_role?(required_roles, context)
|
5
|
+
|
6
|
+
2. expand roles/contexts so we can create css classes from it
|
7
|
+
|
8
|
+
roles = context.required_role_for('edit article').expand
|
9
|
+
quoted_role_names(roles)
|
10
|
+
# => "article-1-author blog-1-moderator site-1-moderator site-1-admin"
|
11
|
+
|
12
|
+
"article-1-author blog-1-author site-1-author
|
13
|
+
article-1-moderator blog-1-moderator site-1-moderator
|
14
|
+
article-1-admin blog-1-admin site-1-admin"
|
15
|
+
|
16
|
+
=> brauchen wir konzept "minimum context type" für roles?
|
17
|
+
|
18
|
+
3. displaying roles per virtual actions per context type
|
19
|
+
|
20
|
+
Blog permissions settings form:
|
21
|
+
[moderator] is allowed to [create] an article [+]
|
22
|
+
[author] is allowed to [edit] an article [+][-]
|
23
|
+
|
24
|
+
=> kein problem
|
25
|
+
|
26
|
+
|
27
|
+
Manage roles
|
28
|
+
|
29
|
+
Role name: ...
|
30
|
+
Role context: (o) Site (o) Section <= ???
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
RBAC
|
36
|
+
|
37
|
+
# things to improve
|
38
|
+
|
39
|
+
- we must be able to define "everything" (roles, contexts, hierarchies) more
|
40
|
+
flexibly. i.e.: provide sensible defaults in adva-cms, overwrite/extend them
|
41
|
+
at startup time (initializer) in adva-best or facility-best
|
42
|
+
|
43
|
+
- we must be able to define additional/custom roles at runtime dynamically,
|
44
|
+
these must behave the same way as predefined roles do
|
45
|
+
|
46
|
+
- roles should not be tied to particular contexts as they are now
|
47
|
+
|
48
|
+
- the system should be able to grant users roles/permissions based on their
|
49
|
+
membership in a group
|
50
|
+
|
51
|
+
# notes
|
52
|
+
|
53
|
+
- acl9's wording of roles being "scoped" to contexts makes sense. they should
|
54
|
+
not be scoped/tied to particular types of contexts though. why not just let
|
55
|
+
the user choose? because potentially not all roles make sense in all contexts?
|
56
|
+
|
57
|
+
# concepts we need
|
58
|
+
|
59
|
+
- contexts require roles for actions to be allowed. e.g. a Blog requires users
|
60
|
+
to be at least a :moderator in the scope of the Site when they want to create
|
61
|
+
an Article
|
62
|
+
|
63
|
+
- roles can include other roles. e.g. the :superuser role includes all other
|
64
|
+
roles. the site :admin role includes all other roles in the context of the
|
65
|
+
site
|
66
|
+
|
67
|
+
# use cases
|
68
|
+
|
69
|
+
- saas adva-cms: manager has an account and 2 sites. he adds a role :editor
|
70
|
+
to his sites, makes two members of his account editor in the context of each
|
71
|
+
site and defines article create/edit permissions for editors. the editors should
|
72
|
+
then be able to create/edit articles on the sites they are editors on.
|
73
|
+
|
74
|
+
- facility-best: manager owns an account and has tons of renters. the application
|
75
|
+
defines that each renter has view access to his own contracts
|
76
|
+
|
77
|
+
- facility-best: manager owns an account and has an accountant. the admin defines
|
78
|
+
a role :accountant and defines that only accountants may view + manage accounts
|
79
|
+
|
80
|
+
- facility-best: manager owns an account and adds an owner to a facility or unit.
|
81
|
+
the owner can not update attributes on the facility or unit and related objects.
|
82
|
+
|
83
|
+
- facility-best: renter can only view the units (and associated objects) he has
|
84
|
+
rented.
|
85
|
+
|
86
|
+
- facility-best: renter can view tickets that belong to his unit or his unit's
|
87
|
+
facility. owner can view all tickets for his facilities. manager can view all
|
88
|
+
tickets for all facilities belonging to his account.
|
89
|
+
|
90
|
+
|
91
|
+
# adva-cms
|
92
|
+
|
93
|
+
ohne saas: superuser >
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# AdvaRbac
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'adva_rbac'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install adva_rbac
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/adva_rbac.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/adva_rbac/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Micah Geisel"]
|
6
|
+
gem.email = ["micah@botandrose.com"]
|
7
|
+
gem.description = %q{Adva RBAC}
|
8
|
+
gem.summary = %q{Engine for role-based authorization in Adva CMS}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "adva_rbac"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = AdvaRbac::VERSION
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class RolesController < BaseController
|
2
|
+
layout false
|
3
|
+
helper :users, :roles
|
4
|
+
before_filter :set_section # ?!
|
5
|
+
before_filter :set_user, :set_object, :set_roles
|
6
|
+
|
7
|
+
def index
|
8
|
+
respond_to do |format|
|
9
|
+
format.js
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def set_user
|
16
|
+
@user = User.find(params[:user_id])
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_object
|
20
|
+
@object = params[:object_type].classify.constantize.find(params[:object_id]) if params[:object_type]
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_roles
|
24
|
+
@roles = @user.roles.by_context(@object || @site)
|
25
|
+
@roles << Role.new(:name => 'user')
|
26
|
+
# @roles.create!(:name => 'user')
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RolesHelper
|
2
|
+
def role_to_default_css_class(role)
|
3
|
+
return unless role
|
4
|
+
role.has_context? ? [role.context_type, role.context_id, role.name.to_s].join('-').downcase : role.name.to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def role_to_css_class(role)
|
8
|
+
return unless role
|
9
|
+
roles = ''
|
10
|
+
if role.name == 'author' && role.context.author_type
|
11
|
+
roles << [role.context.author_type.underscore, role.context.author_id].join('-') + ' '
|
12
|
+
end
|
13
|
+
roles << role_to_default_css_class(role)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Includes a javascript tag that will load a javascript snippet
|
17
|
+
# generated by the RolesController. This snippet will contain roles data
|
18
|
+
# for the current user and toggle visibility for authorized elements.
|
19
|
+
def authorize_elements(object = nil)
|
20
|
+
object_path = object ? "/#{object.class.name.downcase.pluralize}/#{object.id}" : ''
|
21
|
+
javascript_tag <<-js
|
22
|
+
var uid = Cookie.get('uid');
|
23
|
+
if(uid) {
|
24
|
+
$.ajax({
|
25
|
+
url: '/users/' + uid + '/roles#{object_path}.js',
|
26
|
+
type: 'get',
|
27
|
+
async: false,
|
28
|
+
dataType: 'script'
|
29
|
+
});
|
30
|
+
}
|
31
|
+
var aid = Cookie.get('aid');
|
32
|
+
if(aid) {
|
33
|
+
$(document).ready(function() { authorize_elements(['anonymous-' + aid]); });
|
34
|
+
}
|
35
|
+
js
|
36
|
+
end
|
37
|
+
|
38
|
+
def authorized_tag(name, action, object, options = {}, &block)
|
39
|
+
add_authorizing_css_classes! options, action, object
|
40
|
+
content_tag(name, options, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def authorized_link_to(text, url, action, object, options = {})
|
44
|
+
add_authorizing_css_classes!(options, action, object)
|
45
|
+
link_to(text, url, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds the css class required-roles as well as a couple of css classes that
|
49
|
+
# can be matched with the current user's roles in order to toggle the visibility
|
50
|
+
# of an element
|
51
|
+
def add_authorizing_css_classes!(options, action, object)
|
52
|
+
action = :"#{action} #{object.class.name.underscore.downcase}"
|
53
|
+
roles = object.role_context.expand_roles_for(action)
|
54
|
+
options[:class] ||= ''
|
55
|
+
options[:class] = options[:class].split(/ /)
|
56
|
+
options[:class] << 'visible_for' << roles # roles.map { |role| role_to_css_class(role) }.join(' ')
|
57
|
+
options[:class] = options[:class].flatten.uniq.join(' ')
|
58
|
+
end
|
59
|
+
|
60
|
+
def quoted_role_names(roles, options = {})
|
61
|
+
separator = options[:separator] || ''
|
62
|
+
roles.map { |role| options[:quote] ? "'#{role.name}'" : role.name }.join(separator)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!-- <h2><%= t(:'adva.titles.permissions') %></h2>
|
2
|
+
<fieldset class="clearfix">
|
3
|
+
<div class="col">
|
4
|
+
<%# @section.permissions.sorted.each do |type, permissions| %>
|
5
|
+
<% [].each do |type, permissions| %>
|
6
|
+
<h4><%= type.to_s.camelize.pluralize %></h4>
|
7
|
+
<% permissions.each do |action, role| %>
|
8
|
+
<%# permissions.reject { |action, role| action.to_sym == :show }.each do |action, role| # <-- maybe this is cleaner? %>
|
9
|
+
<% next if action.to_sym == :show # TODO :show permissions do not make any sense right now, so we completely deactivate them %>
|
10
|
+
<%= select_tag "section[permissions][#{type}][#{action}]", options_for_select(Role.names.zip(Role.names.map(&:downcase)), role.to_s), :id => "section_required_roles_#{action}" %>
|
11
|
+
can <%= action %>.
|
12
|
+
<%#= label_tag "section_permissions_#{permission}", permission.to_s.titleize %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
</fieldset> -->
|
@@ -0,0 +1,60 @@
|
|
1
|
+
ActionDispatch::Callbacks.to_prepare do
|
2
|
+
Site.acts_as_role_context
|
3
|
+
Section.acts_as_role_context :parent => :site
|
4
|
+
Content.acts_as_role_context :parent => :section
|
5
|
+
# Comment.acts_as_role_context :parent => :commentable
|
6
|
+
|
7
|
+
# CalendarEvent.acts_as_role_context :parent => :section if Rails.plugin?(:adva_calendar)
|
8
|
+
# Photo.acts_as_role_context :parent => :section if Rails.plugin?(:adva_photos)
|
9
|
+
|
10
|
+
# if Rails.plugin?(:adva_forum)
|
11
|
+
# Board.acts_as_role_context :parent => :section
|
12
|
+
# Topic.acts_as_role_context :parent => :section
|
13
|
+
# end
|
14
|
+
|
15
|
+
Rbac::Role.class_eval do
|
16
|
+
belongs_to :ancestor_context, :polymorphic => true
|
17
|
+
|
18
|
+
before_save do |role|
|
19
|
+
role.ancestor_context = role.context.owners.detect do |context|
|
20
|
+
context.is_a?(Site) || context.is_a?(Account)
|
21
|
+
end if role.context
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Account.class_eval do
|
26
|
+
def members
|
27
|
+
User.members_of(self).exclude_role_types('author', 'user')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Site.class_eval do
|
32
|
+
def members
|
33
|
+
User.members_of(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
User.class_eval do
|
38
|
+
scope :members_of, lambda { |context|
|
39
|
+
{
|
40
|
+
:include => :roles,
|
41
|
+
:conditions => "(roles.ancestor_context_type = '#{context.class}' AND roles.ancestor_context_id = #{context.id}) OR
|
42
|
+
(roles.context_type = '#{context.class}' AND roles.context_id = #{context.id})"
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
scope :by_role_types, lambda { |*role_types|
|
47
|
+
{
|
48
|
+
:include => :roles,
|
49
|
+
:conditions => ["roles.name IN (?)", role_types]
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
scope :exclude_role_types, lambda { |*role_types|
|
54
|
+
{
|
55
|
+
:include => :roles,
|
56
|
+
:conditions => ["roles.name NOT IN (?)", role_types]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
Role = Rbac::Role
|
2
|
+
|
3
|
+
ActionDispatch::Callbacks.to_prepare do
|
4
|
+
User.class_eval do
|
5
|
+
acts_as_role_subject
|
6
|
+
|
7
|
+
has_many :roles, :dependent => :delete_all, :class_name => 'Rbac::Role' do
|
8
|
+
def by_context(object)
|
9
|
+
roles = by_site object
|
10
|
+
# TODO in theory we could skip the implicit roles here if roles were already found
|
11
|
+
# ... assuming that any site roles always include any implicit roles.
|
12
|
+
# roles += object.implicit_roles(proxy_owner) if object.respond_to? :implicit_roles
|
13
|
+
roles
|
14
|
+
end
|
15
|
+
|
16
|
+
def by_site(object)
|
17
|
+
site = object.is_a?(Site) ? object : object.site
|
18
|
+
sql = "name = 'superuser' OR
|
19
|
+
context_id = ? AND context_type = 'Site' OR
|
20
|
+
context_id IN (?) AND context_type = 'Section'"
|
21
|
+
where([sql, site.id, site.section_ids])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def admins_and_superusers
|
27
|
+
includes(:roles).where(['roles.name IN (?)', ['superuser', 'admin']])
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_superuser(params)
|
31
|
+
user = User.new(params)
|
32
|
+
user.verified_at = Time.zone.now
|
33
|
+
|
34
|
+
user.email = 'admin@example.org' if user.email.blank?
|
35
|
+
user.password = 'admin' if user.password.blank?
|
36
|
+
user.first_name = user.first_name_from_email
|
37
|
+
|
38
|
+
user.send(:assign_password) # necessary because we bypass the validation hook
|
39
|
+
user.save(validate: false)
|
40
|
+
user.roles.create!(:name => 'superuser') # FIXME?
|
41
|
+
user
|
42
|
+
end
|
43
|
+
|
44
|
+
def by_role_and_context(role, context = nil)
|
45
|
+
type = Rbac::RoleType.build(role)
|
46
|
+
conditions = if type.requires_context?
|
47
|
+
["roles.context_type = ? AND roles.context_id = ? AND roles.name = ?", context.class.to_s, context.id, type.name]
|
48
|
+
else
|
49
|
+
["roles.name = ?", type.name]
|
50
|
+
end
|
51
|
+
includes(:roles).where(conditions)
|
52
|
+
end
|
53
|
+
|
54
|
+
def role_matches_attributes?(attrs, role)
|
55
|
+
# FIXME remove symbolize_keys here
|
56
|
+
keys = [:name, :context_type, :context_id]
|
57
|
+
attrs.symbolize_keys.values_at(*keys).compact.map(&:to_s) == role.attributes.symbolize_keys.values_at(*keys).compact.map(&:to_s)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def roles_attributes=(roles_attributes)
|
62
|
+
selected_roles(roles_attributes).each { |role| self.roles << role }
|
63
|
+
unselected_roles(roles_attributes).each { |role| role.destroy }
|
64
|
+
end
|
65
|
+
|
66
|
+
def selected_roles(roles_attributes = [])
|
67
|
+
# FIXME deep_stringify roles_attributes here
|
68
|
+
roles_attributes.collect do |attrs|
|
69
|
+
next unless attrs['selected'].to_i == 1
|
70
|
+
Rbac::Role.new(attrs.except('selected')) unless roles.any? { |role| self.class.role_matches_attributes?(attrs, role) }
|
71
|
+
end.compact
|
72
|
+
end
|
73
|
+
|
74
|
+
def unselected_roles(roles_attributes = [])
|
75
|
+
roles.select do |role|
|
76
|
+
roles_attributes.any? { |attrs| attrs['selected'].to_i == 0 && self.class.role_matches_attributes?(attrs, role) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|