adva_rbac 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/NOTES +98 -0
  6. data/README.md +29 -0
  7. data/Rakefile +2 -0
  8. data/adva_rbac.gemspec +17 -0
  9. data/app/controllers/roles_controller.rb +28 -0
  10. data/app/helpers/roles_helper.rb +64 -0
  11. data/app/views/admin/sections/settings/_permissions.html.erb +16 -0
  12. data/app/views/roles/index.js.erb +3 -0
  13. data/config/initializers/base_controller.rb +4 -0
  14. data/config/initializers/rbac.rb +60 -0
  15. data/config/initializers/user.rb +80 -0
  16. data/db/migrate/20080402000006_create_role_tables.rb +13 -0
  17. data/db/migrate/20090720132900_migrate_roles_table_to_new_rbac.rb +15 -0
  18. data/lib/action_controller/guards_permissions.rb +77 -0
  19. data/lib/adva_rbac.rb +18 -0
  20. data/lib/adva_rbac/version.rb +3 -0
  21. data/lib/permission_map.rb +70 -0
  22. data/lib/rbac.rb +26 -0
  23. data/lib/rbac/acts_as_role_context.rb +44 -0
  24. data/lib/rbac/acts_as_role_subject.rb +65 -0
  25. data/lib/rbac/context.rb +85 -0
  26. data/lib/rbac/role.rb +10 -0
  27. data/lib/rbac/role_type.rb +73 -0
  28. data/lib/rbac/role_type/active_record.rb +47 -0
  29. data/lib/rbac/role_type/static.rb +144 -0
  30. data/lib/rbac/subject.rb +52 -0
  31. data/test/functional/roles_controller_test.rb +21 -0
  32. data/test/integration/user_rbac_test.rb +34 -0
  33. data/test/rbac/all.rb +3 -0
  34. data/test/rbac/database.rb +155 -0
  35. data/test/rbac/database.yml +3 -0
  36. data/test/rbac/implementation/active_record_test.rb +17 -0
  37. data/test/rbac/implementation/static_test.rb +14 -0
  38. data/test/rbac/static.rb +25 -0
  39. data/test/rbac/test_helper.rb +62 -0
  40. data/test/rbac/tests/acts_as_role_context.rb +37 -0
  41. data/test/rbac/tests/context.rb +35 -0
  42. data/test/rbac/tests/group.rb +40 -0
  43. data/test/rbac/tests/has_role.rb +126 -0
  44. data/test/rbac/tests/role_type.rb +110 -0
  45. data/test/test_helper.rb +1 -0
  46. data/test/unit/helpers/roles_helper_test.rb +69 -0
  47. data/test/unit/models/rbac_context_test.rb +37 -0
  48. data/test/unit/models/rbac_user_test.rb +100 -0
  49. data/test/unit/models/role_test.rb +185 -0
  50. metadata +110 -0
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in adva_rbac.gemspec
4
+ gemspec
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
+
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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,3 @@
1
+ $(document).ready(function() {
2
+ authorize_elements([<%= quoted_role_names(@roles, :separator => ', ', :quote => true) %>]);
3
+ });
@@ -0,0 +1,4 @@
1
+ ActionDispatch::Callbacks.to_prepare do
2
+ BaseController.class_eval { helper :roles }
3
+ Admin::BaseController.class_eval { helper :roles }
4
+ end
@@ -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