adva_rbac 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|