express_access 1.0.0.a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +87 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/express_access/admin.js +1 -0
- data/app/assets/stylesheets/express_access/admin.css +5 -0
- data/app/assets/stylesheets/express_access/application.css +15 -0
- data/app/assets/stylesheets/express_access/main.sass +3 -0
- data/app/assets/stylesheets/express_access/sections/_role_dashboard.sass +29 -0
- data/app/assets/stylesheets/express_access.css +4 -0
- data/app/controllers/express_access/permissions_controller.rb +4 -0
- data/app/controllers/express_access/roles_controller.rb +14 -0
- data/app/controllers/express_access/routes_controller.rb +30 -0
- data/app/controllers/express_access/users_controller.rb +21 -0
- data/app/helpers/express_access/application_helper.rb +4 -0
- data/app/helpers/express_access/permissions_helper.rb +4 -0
- data/app/helpers/express_access/roles_helper.rb +4 -0
- data/app/models/express_access/audit_log.rb +27 -0
- data/app/models/express_access/permission.rb +130 -0
- data/app/models/express_access/role.rb +75 -0
- data/app/models/express_access/role_permission.rb +6 -0
- data/app/models/express_access/user_permission.rb +7 -0
- data/app/models/express_access/user_role.rb +7 -0
- data/app/views/express_access/permissions/index.html.et +13 -0
- data/app/views/express_access/permissions/show.html.et +33 -0
- data/app/views/express_access/roles/index.html.et +9 -0
- data/app/views/express_access/roles/show.html.et +68 -0
- data/app/views/express_access/routes/index.html.et +20 -0
- data/app/views/express_access/routes/show.html.et +46 -0
- data/app/views/express_access/users/index.html.et +26 -0
- data/app/views/express_access/users/show.html.et +55 -0
- data/app/views/layouts/express_access/admin.html.et +1 -0
- data/app/views/layouts/express_access/application.html.erb +14 -0
- data/config/initializers/mount_engine.rb +3 -0
- data/config/menu.yml +18 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20141029223053_create_express_access_roles.rb +10 -0
- data/db/migrate/20141029223158_create_express_access_permissions.rb +9 -0
- data/db/migrate/20141029223233_create_express_access_role_permissions.rb +10 -0
- data/db/migrate/20141029223250_create_express_access_user_permissions.rb +10 -0
- data/db/migrate/20150528222337_create_express_access_user_roles.rb +9 -0
- data/db/migrate/20150609124815_add_description_to_role.rb +5 -0
- data/db/migrate/20150914023030_create_express_access_audit_logs.rb +15 -0
- data/db/migrate/20150921063153_add_after_sign_in_path_to_role.rb +5 -0
- data/lib/express_access/after_sign_in_filter.rb +7 -0
- data/lib/express_access/authorization_filter.rb +39 -0
- data/lib/express_access/engine.rb +12 -0
- data/lib/express_access/route.rb +127 -0
- data/lib/express_access/user.rb +79 -0
- data/lib/express_access/version.rb +3 -0
- data/lib/express_access.rb +51 -0
- data/lib/generators/express_access/install/USAGE +8 -0
- data/lib/generators/express_access/install/install_generator.rb +10 -0
- data/lib/tasks/express_access_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/posts_controller.rb +4 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/user.rb +9 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/posts/index.html.erb +0 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +41 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/devise.rb +259 -0
- data/test/dummy/config/initializers/express_access.rb +1 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/devise.en.yml +60 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20150525001419_devise_create_users.rb +42 -0
- data/test/dummy/db/schema.rb +82 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/express_access/permissions.yml +28 -0
- data/test/dummy/test/fixtures/express_access/role_permissions.yml +21 -0
- data/test/dummy/test/fixtures/express_access/roles.yml +27 -0
- data/test/dummy/test/fixtures/express_access/user_permissions.yml +5 -0
- data/test/dummy/test/fixtures/express_access/user_roles.yml +15 -0
- data/test/dummy/test/fixtures/users.yml +19 -0
- data/test/dummy/test/initializer_test.rb +8 -0
- data/test/dummy/test/models/user_test.rb +7 -0
- data/test/express_access_test.rb +7 -0
- data/test/fixtures/express_access/audit_logs.yml +10 -0
- data/test/fixtures/express_access/permissions.yml +28 -0
- data/test/fixtures/express_access/role_permissions.yml +21 -0
- data/test/fixtures/express_access/roles.yml +34 -0
- data/test/fixtures/express_access/user_permissions.yml +5 -0
- data/test/fixtures/express_access/user_roles.yml +19 -0
- data/test/fixtures/users.yml +22 -0
- data/test/helpers/express_access/permissions_helper_test.rb +6 -0
- data/test/helpers/express_access/roles_helper_test.rb +6 -0
- data/test/integration/navigation_test.rb +33 -0
- data/test/lib/authorization_filter_test.rb +64 -0
- data/test/lib/generators/express_access/install/install_generator_test.rb +16 -0
- data/test/models/express_access/audit_log_test.rb +9 -0
- data/test/models/express_access/permission_test.rb +50 -0
- data/test/models/express_access/role_permission_test.rb +9 -0
- data/test/models/express_access/role_test.rb +36 -0
- data/test/models/express_access/user_permission_test.rb +9 -0
- data/test/models/express_access/user_role_test.rb +9 -0
- data/test/models/express_access/user_test.rb +77 -0
- data/test/test_helper.rb +19 -0
- 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,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,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,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,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,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,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
|
+
|