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.
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