express_access 1.0.0.a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +87 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/express_access/admin.js +1 -0
  5. data/app/assets/stylesheets/express_access/admin.css +5 -0
  6. data/app/assets/stylesheets/express_access/application.css +15 -0
  7. data/app/assets/stylesheets/express_access/main.sass +3 -0
  8. data/app/assets/stylesheets/express_access/sections/_role_dashboard.sass +29 -0
  9. data/app/assets/stylesheets/express_access.css +4 -0
  10. data/app/controllers/express_access/permissions_controller.rb +4 -0
  11. data/app/controllers/express_access/roles_controller.rb +14 -0
  12. data/app/controllers/express_access/routes_controller.rb +30 -0
  13. data/app/controllers/express_access/users_controller.rb +21 -0
  14. data/app/helpers/express_access/application_helper.rb +4 -0
  15. data/app/helpers/express_access/permissions_helper.rb +4 -0
  16. data/app/helpers/express_access/roles_helper.rb +4 -0
  17. data/app/models/express_access/audit_log.rb +27 -0
  18. data/app/models/express_access/permission.rb +130 -0
  19. data/app/models/express_access/role.rb +75 -0
  20. data/app/models/express_access/role_permission.rb +6 -0
  21. data/app/models/express_access/user_permission.rb +7 -0
  22. data/app/models/express_access/user_role.rb +7 -0
  23. data/app/views/express_access/permissions/index.html.et +13 -0
  24. data/app/views/express_access/permissions/show.html.et +33 -0
  25. data/app/views/express_access/roles/index.html.et +9 -0
  26. data/app/views/express_access/roles/show.html.et +68 -0
  27. data/app/views/express_access/routes/index.html.et +20 -0
  28. data/app/views/express_access/routes/show.html.et +46 -0
  29. data/app/views/express_access/users/index.html.et +26 -0
  30. data/app/views/express_access/users/show.html.et +55 -0
  31. data/app/views/layouts/express_access/admin.html.et +1 -0
  32. data/app/views/layouts/express_access/application.html.erb +14 -0
  33. data/config/initializers/mount_engine.rb +3 -0
  34. data/config/menu.yml +18 -0
  35. data/config/routes.rb +6 -0
  36. data/db/migrate/20141029223053_create_express_access_roles.rb +10 -0
  37. data/db/migrate/20141029223158_create_express_access_permissions.rb +9 -0
  38. data/db/migrate/20141029223233_create_express_access_role_permissions.rb +10 -0
  39. data/db/migrate/20141029223250_create_express_access_user_permissions.rb +10 -0
  40. data/db/migrate/20150528222337_create_express_access_user_roles.rb +9 -0
  41. data/db/migrate/20150609124815_add_description_to_role.rb +5 -0
  42. data/db/migrate/20150914023030_create_express_access_audit_logs.rb +15 -0
  43. data/db/migrate/20150921063153_add_after_sign_in_path_to_role.rb +5 -0
  44. data/lib/express_access/after_sign_in_filter.rb +7 -0
  45. data/lib/express_access/authorization_filter.rb +39 -0
  46. data/lib/express_access/engine.rb +12 -0
  47. data/lib/express_access/route.rb +127 -0
  48. data/lib/express_access/user.rb +79 -0
  49. data/lib/express_access/version.rb +3 -0
  50. data/lib/express_access.rb +51 -0
  51. data/lib/generators/express_access/install/USAGE +8 -0
  52. data/lib/generators/express_access/install/install_generator.rb +10 -0
  53. data/lib/tasks/express_access_tasks.rake +4 -0
  54. data/test/dummy/README.rdoc +28 -0
  55. data/test/dummy/Rakefile +6 -0
  56. data/test/dummy/app/assets/javascripts/application.js +13 -0
  57. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  58. data/test/dummy/app/controllers/application_controller.rb +5 -0
  59. data/test/dummy/app/controllers/posts_controller.rb +4 -0
  60. data/test/dummy/app/helpers/application_helper.rb +2 -0
  61. data/test/dummy/app/models/user.rb +9 -0
  62. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  63. data/test/dummy/app/views/posts/index.html.erb +0 -0
  64. data/test/dummy/bin/bundle +3 -0
  65. data/test/dummy/bin/rails +4 -0
  66. data/test/dummy/bin/rake +4 -0
  67. data/test/dummy/config/application.rb +25 -0
  68. data/test/dummy/config/boot.rb +5 -0
  69. data/test/dummy/config/database.yml +25 -0
  70. data/test/dummy/config/environment.rb +5 -0
  71. data/test/dummy/config/environments/development.rb +37 -0
  72. data/test/dummy/config/environments/production.rb +83 -0
  73. data/test/dummy/config/environments/test.rb +41 -0
  74. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  75. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/test/dummy/config/initializers/devise.rb +259 -0
  77. data/test/dummy/config/initializers/express_access.rb +1 -0
  78. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  79. data/test/dummy/config/initializers/inflections.rb +16 -0
  80. data/test/dummy/config/initializers/mime_types.rb +4 -0
  81. data/test/dummy/config/initializers/session_store.rb +3 -0
  82. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  83. data/test/dummy/config/locales/devise.en.yml +60 -0
  84. data/test/dummy/config/locales/en.yml +23 -0
  85. data/test/dummy/config/routes.rb +7 -0
  86. data/test/dummy/config/secrets.yml +22 -0
  87. data/test/dummy/config.ru +4 -0
  88. data/test/dummy/db/migrate/20150525001419_devise_create_users.rb +42 -0
  89. data/test/dummy/db/schema.rb +82 -0
  90. data/test/dummy/public/404.html +67 -0
  91. data/test/dummy/public/422.html +67 -0
  92. data/test/dummy/public/500.html +66 -0
  93. data/test/dummy/public/favicon.ico +0 -0
  94. data/test/dummy/test/fixtures/express_access/permissions.yml +28 -0
  95. data/test/dummy/test/fixtures/express_access/role_permissions.yml +21 -0
  96. data/test/dummy/test/fixtures/express_access/roles.yml +27 -0
  97. data/test/dummy/test/fixtures/express_access/user_permissions.yml +5 -0
  98. data/test/dummy/test/fixtures/express_access/user_roles.yml +15 -0
  99. data/test/dummy/test/fixtures/users.yml +19 -0
  100. data/test/dummy/test/initializer_test.rb +8 -0
  101. data/test/dummy/test/models/user_test.rb +7 -0
  102. data/test/express_access_test.rb +7 -0
  103. data/test/fixtures/express_access/audit_logs.yml +10 -0
  104. data/test/fixtures/express_access/permissions.yml +28 -0
  105. data/test/fixtures/express_access/role_permissions.yml +21 -0
  106. data/test/fixtures/express_access/roles.yml +34 -0
  107. data/test/fixtures/express_access/user_permissions.yml +5 -0
  108. data/test/fixtures/express_access/user_roles.yml +19 -0
  109. data/test/fixtures/users.yml +22 -0
  110. data/test/helpers/express_access/permissions_helper_test.rb +6 -0
  111. data/test/helpers/express_access/roles_helper_test.rb +6 -0
  112. data/test/integration/navigation_test.rb +33 -0
  113. data/test/lib/authorization_filter_test.rb +64 -0
  114. data/test/lib/generators/express_access/install/install_generator_test.rb +16 -0
  115. data/test/models/express_access/audit_log_test.rb +9 -0
  116. data/test/models/express_access/permission_test.rb +50 -0
  117. data/test/models/express_access/role_permission_test.rb +9 -0
  118. data/test/models/express_access/role_test.rb +36 -0
  119. data/test/models/express_access/user_permission_test.rb +9 -0
  120. data/test/models/express_access/user_role_test.rb +9 -0
  121. data/test/models/express_access/user_test.rb +77 -0
  122. data/test/test_helper.rb +19 -0
  123. metadata +375 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 69226d8e98c1c6312546082c4681bfe66a731c49
4
+ data.tar.gz: 6bdd3bdfc256a6ff39798456ba22eabd48e8740c
5
+ SHA512:
6
+ metadata.gz: 8c59c6da7fa8c8c8e1a9c5f3f5fae02821ade84a5a899f60d9ce057eb838c11030c2916567a939bde579aaf497a8d3a5ea9d43722b29ecbf70f344d038ef1e22
7
+ data.tar.gz: 1ec3617f42dfeba476104f84abc0e754253d30132fe56f2e562a6044959ae89af075ee740f8c9081f62502903b70806fd53620e19654d5b0c829f58aa5160bf6
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # ExpressAccess
2
+
3
+ [![Circle CI](https://circleci.com/gh/aelogica/express_access.svg?style=svg&circle-token=afa108d54cb1c149787f2ffde22d05794f0eee9b)](https://circleci.com/gh/aelogica/express_access)
4
+
5
+ ## Summary
6
+
7
+ ExpressAccess provides Role-Based Authorization and Control for Rails applications using [ExpressAdmin](http://github.com/aelogica/express_admin).
8
+
9
+ ExpressAccess is part of the [AppExpress](http://appexpress.io) suite of add-ons, however it may be used outside of AppExpress. A sample Rails application template is provided to get you up and running quickly.
10
+
11
+ ## Description
12
+
13
+ ExpressAccess allows a developer or other designated security policy administrator (a superadmin or superuser) to create and manage access control and permissions for an application or a group of applications. It accomplishes this associating Users with:
14
+
15
+ * Roles
16
+ * Permissions
17
+ * Groups (optional)
18
+
19
+ Specific permissions are created as needed and are granted to users directly or through the User's association with a Role or Group. Users may belong to one or more Roles or Groups. Roles may be hierarchical, inheriting their permissions through their parent role.
20
+
21
+ ### Example
22
+
23
+ Let us suppose we have the following roles and permissions:
24
+
25
+ * Reader
26
+ - comments#create
27
+ * Author
28
+ - article#create
29
+ - article#update
30
+ * Editor
31
+ - article#publish
32
+ * Admin
33
+ - users#create
34
+ - users\_roles#create
35
+ - users\_roles#delete
36
+
37
+ If we define the hierarchy as:
38
+
39
+ Reader -> Author -> Editor -> Admin
40
+
41
+ then a User with the Editor role, will be able to create, update and publish articles. A User who only has the Author role will be able to create and update articles but will not be able to publish them. Both would also be able to create comments. A User with the Admin role would be able to create other users and assign roles to them.
42
+
43
+ The permissions are named according to convention. It is up to the application developer to interperet and enforce permissions correctly according to the needs of their application.
44
+
45
+ ExpressAccess provides an administrative interface and model for associating a bundle of Permissions with a User through Groups and Roles.
46
+
47
+ In addition, ExpressAccess provides a controller mixin which enforces permissions on a controller/action level based on the convention of naming permissions using either the controller name alone or the controller#action pair.
48
+
49
+ ### Model Level Access Control
50
+
51
+ It may be possible to implement a more fine-grained control of access to specific data objects or fields at a model level, however we have not seen a need for this in the majority of our work. Most Rails applications perform access control functions at the controller level.
52
+
53
+ ### Why Not Define Roles and Permissions in Code?
54
+
55
+ Many applications need the ability to adjust access control policies without triggering an application deployment. In addition, in a small enterprise environment, the security policy administrator may also be a non-technical application owner. He or she not be able to correctly read or interpret a Ruby DSL for access control and may not wish to enter a developer-type workflow or call on a developer in order to change application security policies.
56
+
57
+ ## Dependencies
58
+
59
+ This engine requires [ExpressAdmin](http://github.com/aelogica/express_admin) to function. Please follow the instructions to install ExpressAdmin before installing ExpressAccess.
60
+
61
+ ## Usage
62
+
63
+ To use ExpressAccess add this to your gemfile:
64
+
65
+ gem "express_access"
66
+
67
+ Run bundle install:
68
+
69
+ bundle install
70
+
71
+ Then run the install generator:
72
+
73
+ rails generate express_access:install
74
+
75
+ This will mount the engine in your application at '/admin/access', however you may override the location by changing your routes.rb.
76
+
77
+ ## Sample Application
78
+
79
+ If you want to start a new application that is prepared for ExpressAccess installation, use the ExpressAdmin template. Simply run:
80
+
81
+ rails new my_great_app -t https://github.com/aelogica/express_admin/template.rb
82
+
83
+ Then perform the steps above in Usage.
84
+
85
+ ---
86
+
87
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ExpressAccess'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1 @@
1
+ //= require_tree .
@@ -0,0 +1,5 @@
1
+ /*
2
+ *= require express_admin
3
+ *= require express_access/main
4
+ *= require_self
5
+ */
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require express_admin
14
+ *= require_self
15
+ */
@@ -0,0 +1,3 @@
1
+ // @import 'foundation'
2
+ // @import 'express_admin/globals/variables'
3
+ // @import 'sections/role_dashboard'
@@ -0,0 +1,29 @@
1
+ .role-page
2
+ @include grid-block($orientation: vertical)
3
+
4
+ .role-information
5
+ @include grid-block
6
+
7
+ .resource-access
8
+ @include grid-block
9
+
10
+ .role-tree
11
+ @include grid-block(2)
12
+ border:
13
+ bottom: 1px solid $border-color
14
+
15
+ .additional-information
16
+ @include grid-block(5, $orientation: vertical)
17
+
18
+ .role-form
19
+ @include grid-block(5)
20
+ border:
21
+ bottom: 1px solid $border-color
22
+
23
+ .role-list
24
+ @extend .block-list
25
+
26
+ .role-information
27
+ > .pane
28
+ border:
29
+ bottom: 1px solid $border-color
@@ -0,0 +1,4 @@
1
+ /*
2
+ *= require express_admin
3
+ *= require express_access/main
4
+ */
@@ -0,0 +1,4 @@
1
+ module ExpressAccess
2
+ class PermissionsController < ExpressAdmin::StandardController
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ module ExpressAccess
2
+ class RolesController < ExpressAdmin::StandardController
3
+
4
+ protected
5
+
6
+ def load_collection
7
+ if action_name.eql?('show')
8
+ @roles = super.top_level
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ module ExpressAccess
2
+ class RoutesController < ExpressAdmin::StandardController
3
+
4
+ protected
5
+
6
+ # not used because we don't build routes in the UI
7
+ def build_resource(*args)
8
+ Object.new
9
+ end
10
+
11
+ def search(scope)
12
+ search_results = scope.all.find_all do |route|
13
+ match_substring(scope,route)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def match_substring(scope,route)
20
+ columns = scope.columns.map(&:name)
21
+ columns.find do |col|
22
+ unless route.try(col).nil?
23
+ if route.try(col).include? params[:search_string]
24
+ route
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ module ExpressAccess
2
+ class UsersController < ExpressAdmin::StandardController
3
+
4
+ private
5
+
6
+ def user_params
7
+ user_params = params.require(:user)
8
+ if user_params[:password].blank? && user_params[:password_confirmation].blank?
9
+ user_params.delete(:password)
10
+ user_params.delete(:password_confirmation)
11
+ end
12
+ user_params.permit!
13
+ end
14
+
15
+ protected
16
+
17
+ def resource_class
18
+ ::User
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ module ExpressAccess
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ExpressAccess
2
+ module PermissionsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ExpressAccess
2
+ module RolesHelper
3
+ end
4
+ end
@@ -0,0 +1,27 @@
1
+ module ExpressAccess
2
+ class AuditLog < ActiveRecord::Base
3
+
4
+ belongs_to :user
5
+
6
+ def self.for(endp)
7
+ endpoint = endp.split("#")
8
+ controller_path = endpoint.first
9
+ action_name = endpoint.last
10
+ log_for_endpoint = find_by_endpoint(controller_path, action_name)
11
+ grouped_logs = group_by_date(log_for_endpoint)
12
+ end
13
+
14
+ def self.find_by_endpoint(path, name)
15
+ AuditLog.where("controller_name='#{path}' AND action_name='#{name}'").last(5)
16
+ end
17
+
18
+ def self.group_by_date(unsorted_log)
19
+ sorted_log = []
20
+ sorted_log << unsorted_log.group_by do |log|
21
+ log.created_at.to_date
22
+ end
23
+ sorted_log.reverse
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,130 @@
1
+ module ExpressAccess
2
+ class Permission < ActiveRecord::Base
3
+ has_many :role_permissions
4
+ has_many :roles, through: :role_permissions, source: :role
5
+
6
+ has_many :user_permissions
7
+ has_many :specially_permitted_users, through: :user_permissions,
8
+ source: :user, class_name: "::User"
9
+
10
+ default_scope -> { order(:name) }
11
+
12
+ def self.[] name
13
+ Permission.find_by_name(name.to_s)
14
+ end
15
+
16
+ # Returns roles that have this permission. Includes any Roles that
17
+ # have this permission directly, as well as their children who
18
+ # inherit this permission
19
+ def all_roles
20
+ @roles = roles.map(&:all_children).flatten.to_set
21
+ end
22
+
23
+ def all_routes
24
+ Route.all.select { |r| r.permission.eql?(self) }
25
+ end
26
+
27
+ def user_count
28
+ all_roles.map {|r| r.users.count }.sum + specially_permitted_users.count
29
+ end
30
+
31
+ def controllers
32
+ all_routes.map(&:controller).to_set
33
+ end
34
+
35
+ # Searches for the most specific permission that ought to apply
36
+ # for a request with the specified controller, action and path.
37
+ # Returns nil if no permissions should apply.
38
+ #
39
+ # This mediates between potentially conflicting permissions by
40
+ # establishing a clear priority.
41
+ #
42
+ # For example:
43
+ #
44
+ # admin/posts#edit
45
+ #
46
+ # is more specific than:
47
+ #
48
+ # admin/posts
49
+ # admin
50
+ #
51
+ #
52
+ # A particularly difficult case arises when both path
53
+ # and module/controller#action style permissions exist for a
54
+ # given controller, action and path.
55
+ #
56
+ # In this case, we seek to identify which one is more "specific"
57
+ # based on how long it is. In the case where the matching path and
58
+ # controller path with action both have the same length, the path
59
+ # takes priority. This allows us to protect specific resources
60
+ # within a collection should we desire.
61
+ #
62
+ # For example, a permission named "/posts/999" is more specific
63
+ # than "posts#publish" even though they both consist of two segments.
64
+ # This means the "/posts/999" should apply when someone accesses:
65
+ #
66
+ # /posts/999/publish
67
+ #
68
+ def self.for(controller: nil, action: nil, path: nil)
69
+ controller_permissions = controller_action_permissions_for(controller, action)
70
+ path_permissions = path_permissions_for(path)
71
+
72
+ # use the longer (more specific) list as basis for zip
73
+ possible_permissions = normalize_and_zip( controller_permissions, path_permissions)
74
+
75
+ # return the first non-nil entry of the zipped arrays
76
+ possible_permissions.first
77
+ end
78
+
79
+ private
80
+
81
+ # Return an array containing a list of potentially applicable permissions
82
+ # in order from most specific to least specific.
83
+ def self.normalize_and_zip(longer, shorter)
84
+ longer, shorter = shorter, longer if longer.size < shorter.size
85
+
86
+ longer.reverse.zip(shorter.reverse).flatten.compact.reverse
87
+ end
88
+
89
+ # Return a list of permissions matching the path segments
90
+ # in order of specificity from most specific (matching the entire
91
+ # path segment tuple) to the least specific. (matching only the first
92
+ # path segment)
93
+ #
94
+ # The list will contain nils where no matching Permission exists.
95
+ def self.path_permissions_for(path)
96
+ path_permissions = []
97
+ if path
98
+ path_parts = path.split("/")
99
+ path_permissions << Permission[path]
100
+ while path_parts.pop && !path_parts.empty?
101
+ path_permissions << Permission[path_parts.join("/")]
102
+ end
103
+ end
104
+ path_permissions
105
+ end
106
+
107
+ # Return a list of permissions matching the namespace/controller/action
108
+ # path from most specific (matching the entire tuple) to the
109
+ # least specific. (matching only the top-level namespace or the
110
+ # controller if not namespaced) We create the first, most specific
111
+ # permission name by including the action.
112
+ #
113
+ # The list will contain nils where no matching Permission exists.
114
+ def self.controller_action_permissions_for(controller, action)
115
+ controller_permissions = []
116
+ if controller && action
117
+ controller_modules = controller.split("/")
118
+ # order from most specific to least specific
119
+ controller_permissions << Permission["#{controller}##{action}"]
120
+
121
+ while controller_modules.pop && !controller_modules.empty?
122
+ controller_permissions << Permission[controller_modules.join("/")]
123
+ end
124
+ controller_permissions << Permission["#{controller}"]
125
+ end
126
+ controller_permissions
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,75 @@
1
+ module ExpressAccess
2
+ class Role < ActiveRecord::Base
3
+ belongs_to :parent, class_name: 'ExpressAccess::Role',
4
+ inverse_of: :children
5
+ has_many :role_permissions
6
+ has_many :direct_permissions, through: :role_permissions,
7
+ source: :permission
8
+
9
+ has_many :children, :inverse_of => :parent,
10
+ :foreign_key => :parent_id,
11
+ :class_name => 'ExpressAccess::Role'
12
+
13
+ scope :top_level, -> { where(parent_id: nil) }
14
+
15
+ has_many :user_roles
16
+ has_many :users, through: :user_roles,
17
+ source: :user,
18
+ class_name: "::User"
19
+
20
+ def self.expressers
21
+ find_by(name: 'Expresser').users
22
+ end
23
+
24
+ def childrens_users
25
+ (all_children - [self]).map(&:users).flatten
26
+ end
27
+
28
+ def all_children
29
+ visitor = -> (visitor, role) {
30
+ [role] + role.children.map { |child|
31
+ visitor.call(visitor, child)
32
+ }
33
+ }
34
+ visitor.call(visitor,self).flatten
35
+ end
36
+
37
+ def self.[] name
38
+ Role.find_by_name(name.to_s.titleize)
39
+ end
40
+
41
+ def source_of(permission)
42
+ if direct_permissions.include?(permission)
43
+ "direct"
44
+ else
45
+ current = self
46
+ ancestors = []
47
+ while current = current.parent
48
+ ancestors << current
49
+ end
50
+ source = ancestors.detect {|ancestor| ancestor.direct_permissions.include?(permission)}
51
+ "inherited from #{source.name}"
52
+ end
53
+ end
54
+
55
+ def parent_name
56
+ parent.try(:name) || ''
57
+ end
58
+
59
+ def direct_permissions_count
60
+ direct_permissions.count
61
+ end
62
+
63
+ def total_permissions_count
64
+ permissions.size
65
+ end
66
+
67
+ def permissions
68
+ direct_permissions + inherited_permissions
69
+ end
70
+
71
+ def inherited_permissions
72
+ parent ? parent.permissions : []
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,6 @@
1
+ module ExpressAccess
2
+ class RolePermission < ActiveRecord::Base
3
+ belongs_to :role
4
+ belongs_to :permission
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module ExpressAccess
2
+ class UserPermission < ActiveRecord::Base
3
+ belongs_to :user, class_name: "::User"
4
+ belongs_to :permission
5
+ delegate :name, to: :permission
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ExpressAccess
2
+ class UserRole < ActiveRecord::Base
3
+ belongs_to :user, class_name: '::User'
4
+ belongs_to :role
5
+ delegate :name, to: :role
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ pane(title: "Permissions") {
2
+ smart_table(:permissions,
3
+ columns: {
4
+ "Permission Name" => :name_link,
5
+ "Created" => :created_at_in_words,
6
+ "Updated" => :updated_at_in_words
7
+ })
8
+
9
+ }
10
+
11
+ pane(title: "Create a Permission") {
12
+ smart_form(:permission, exclude: [:users, :roles, :specially_permitted_users, :created_at, :updated_at])
13
+ }
@@ -0,0 +1,33 @@
1
+ v_box {
2
+ pane(title: "<strong>#{permission.name}</strong>".html_safe,
3
+ status: "#{permission.all_roles.size} Roles, #{permission.all_routes.size} Routes, #{permission.user_count} Users") {
4
+ dl {
5
+ dt { "Permission Created:" }
6
+ dd { permission.created_at ? "#{time_ago_in_words(permission.created_at)}" + " ago" : "unknown"}
7
+
8
+ dt { "Roles:" }
9
+ dd { permission.all_roles.map {|role| helpers.link_to role.name, role_path(role)}.join(", ").html_safe}
10
+
11
+ dt { "User Assignments:" }
12
+ dd { permission.specially_permitted_users.any? ? permission.specially_permitted_users.map {|user| helpers.link_to user.name, user_path(user) }.join(", ").html_safe : 'None'}
13
+
14
+ dt { "Controllers:"}
15
+ dd { permission.controllers.any? ? "#{ul { permission.controllers.each { |controller| li { controller } }}}" : "None" }
16
+ }
17
+ }
18
+
19
+ pane(title: 'Role & User Assignments') {
20
+ smart_form(:permission, exclude: [:name, :created_at, :updated_at])
21
+ }
22
+ }
23
+
24
+
25
+ pane(title: 'Resources Protected', class: 'large-table-container') {
26
+ smart_table(:routes, actions: false,
27
+ collection: -> { permission.all_routes.to_a },
28
+ columns: {
29
+ "Path" => :path_link,
30
+ "Endpoint" => :endp_link,
31
+ "Description" => :description_link
32
+ })
33
+ }
@@ -0,0 +1,9 @@
1
+ main_region {
2
+ h2 { 'Roles' }
3
+ smart_table(:roles,
4
+ columns: {"Name" => :name_link,
5
+ "Parent" => :parent_id,
6
+ "Description" => :description,
7
+ "Direct Permissions" => :direct_permissions_count,
8
+ "Total Permissions" => :total_permissions_count})
9
+ }
@@ -0,0 +1,68 @@
1
+ h_box {
2
+ v_box {
3
+ h_box {
4
+ pane(title: "#{role.name} Role") {
5
+ para {
6
+ role.description
7
+ }
8
+ section(class: "role-list") {
9
+ tree_for(:roles, title: "Roles") { |node|
10
+ div(class: (node.eql?(role) ? 'selected' : '')) {
11
+ a(href: role_path(node)) {
12
+ node.name
13
+ }
14
+ }
15
+ }
16
+ }
17
+ }
18
+ v_box {
19
+ pane(title: role.name.pluralize,
20
+ status: "#{role.users.count} users") {
21
+ smart_table(:users, actions: false, resource_class: '::User', resource_path: -> (user) { express_access.user_path(user) },
22
+ collection: -> { role.users.sort_by(&:email) },
23
+ columns: {
24
+ 'Name' => :name_link,
25
+ 'Last signed in' => :last_sign_in_at_in_words
26
+ })
27
+
28
+ }
29
+ pane(title: "Inheritors of #{role.name}",
30
+ status: "#{role.childrens_users.count} users") {
31
+ smart_table(:users, actions: false, resource_class: '::User', resource_path: -> (user) { express_access.user_path(user) },
32
+ collection: -> { role.childrens_users.sort_by(&:email) },
33
+ columns: {
34
+ 'Name' => :name_link,
35
+ 'Last signed in' => :last_sign_in_at_in_words
36
+ })
37
+
38
+ }
39
+
40
+ pane(title: "All Permissions for #{role.name}") {
41
+ smart_table(:permissions, actions: false,
42
+ collection: -> { role.permissions.sort_by(&:name) },
43
+ columns: {
44
+ 'Permissions' => -> (permission) { helpers.link_to permission.name, permission_path(permission) },
45
+ 'Through' => -> (permission) { role.source_of(permission)}
46
+ })
47
+ }
48
+ }
49
+ }
50
+ pane(title: "Resource Access for #{role.name}", class: 'large-table-container') {
51
+ smart_table(:routes, actions: false, scroll_table: true,
52
+ collection: -> { ExpressAccess::Route.all },
53
+ row_class: -> (route) { status = route.status_for_role(role) ; case status when "granted" ; 'green' ; when 'denied' ; 'red' else '' end },
54
+ columns: {
55
+ "Path" => :path_link,
56
+ "Endpoint" => :endp_link,
57
+ "Description" => :description_link,
58
+ "Locked?" => -> (route) { route.status_for_role(role).upcase }
59
+ })
60
+
61
+ }
62
+ }
63
+ pane(title: "Edit #{role.name}") {
64
+ smart_form(:role, exclude: [:users])
65
+ }
66
+ }
67
+
68
+