aki-operations 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +87 -0
- data/Rakefile +27 -0
- data/app/controllers/admin/operations/actions_controller.rb +28 -0
- data/app/controllers/admin/operations/users_controller.rb +28 -0
- data/app/controllers/admin/operations_controller.rb +12 -0
- data/app/controllers/operations/enforced_controller.rb +45 -0
- data/app/helpers/operations_helper.rb +15 -0
- data/app/views/admin/operations/actions/index.html.erb +20 -0
- data/app/views/admin/operations/actions/index/_edit.html.erb +0 -0
- data/app/views/admin/operations/actions/index/_view.html.erb +156 -0
- data/app/views/admin/operations/index.html.erb +88 -0
- data/app/views/admin/operations/users/index.html.erb +20 -0
- data/app/views/admin/operations/users/index/_edit.html.erb +4 -0
- data/app/views/admin/operations/users/index/_view.html.erb +158 -0
- data/config/routes.rb +13 -0
- data/lib/generators/operations_generator.rb +10 -0
- data/lib/generators/templates/operations.rb +33 -0
- data/lib/operations.rb +152 -0
- data/lib/operations/act_as_operationable.rb +26 -0
- data/lib/operations/config.rb +64 -0
- data/lib/operations/core_ext.rb +28 -0
- data/lib/operations/enforcer.rb +71 -0
- data/lib/operations/engine.rb +5 -0
- data/lib/operations/errors.rb +6 -0
- data/lib/operations/errors/base_exception.rb +12 -0
- data/lib/operations/errors/invalid_field_error.rb +9 -0
- data/lib/operations/errors/invalid_operation_error.rb +26 -0
- data/lib/operations/errors/not_authorized_error.rb +11 -0
- data/lib/operations/errors/not_implemented_error.rb +9 -0
- data/lib/operations/errors/not_logged_in_error.rb +10 -0
- data/lib/operations/operation.rb +167 -0
- data/lib/operations/railtie.rb +17 -0
- data/lib/operations/utils.rb +10 -0
- data/lib/operations/version.rb +3 -0
- data/lib/tasks/operations_tasks.rake +6 -0
- metadata +252 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
<div class="container">
|
2
|
+
<div class="padded">
|
3
|
+
<div class="justify-content-between d-flex w-100">
|
4
|
+
<h1>Operations Management</h1>
|
5
|
+
<h6>You are a: <%= user_role_badge(current_user) %></h6>
|
6
|
+
</div>
|
7
|
+
<div class="text-center">
|
8
|
+
<div class="small text-muted">
|
9
|
+
Operations count: <%= @operations.size %>
|
10
|
+
</div>
|
11
|
+
<div class="small text-muted">
|
12
|
+
User permissions count: <%= @permissions.size %>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<p class="very lighly padded">
|
18
|
+
You view can find below a new way of managing your operations for this application! This
|
19
|
+
will be most useful for determining what types of users are allowed to do. Adding exceptions
|
20
|
+
is also be a possibility. Each section will features on how to use them.
|
21
|
+
</p>
|
22
|
+
|
23
|
+
<div class="row">
|
24
|
+
<div class="col-md-6">
|
25
|
+
<div class="list-group">
|
26
|
+
<a href="<%= admin_operations_users_path %>" class="list-group-item list-group-item-action flex-column align-items-start">
|
27
|
+
<div class="d-flex w-100 justify-content-between">
|
28
|
+
<h3 class="mb-1">
|
29
|
+
View User permissions
|
30
|
+
</h3>
|
31
|
+
<h1 class=""><span style="vertical-align: top;" class="material-icons md-36">supervised_user_circle</span> </h1>
|
32
|
+
</div>
|
33
|
+
<p>
|
34
|
+
View all users permissions types defined on the system.
|
35
|
+
</p>
|
36
|
+
</a>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<div class="col-md-6">
|
40
|
+
<div class="list-group">
|
41
|
+
<a href="<%= admin_operations_actions_path %>" class="list-group-item list-group-item-action flex-column align-items-start">
|
42
|
+
<div class="d-flex w-100 justify-content-between">
|
43
|
+
<h3 class="mb-1">
|
44
|
+
View Actions
|
45
|
+
</h3>
|
46
|
+
<h1 class=""><span style="vertical-align: top;" class="material-icons md-36">offline_bolt</span> </h1>
|
47
|
+
</div>
|
48
|
+
<p>
|
49
|
+
View all users permissions types defined on the system.
|
50
|
+
</p>
|
51
|
+
</a>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
<!--
|
56
|
+
<div style="margin-top: 15px;" class="row">
|
57
|
+
<div class="col-md-6">
|
58
|
+
<div class="list-group">
|
59
|
+
<a href="<%= admin_operations_users_path(params: {edit: true}) %>" class="list-group-item list-group-item-action flex-column align-items-start">
|
60
|
+
<div class="d-flex w-100 justify-content-between">
|
61
|
+
<h3 class="mb-1">
|
62
|
+
Edit User permissions
|
63
|
+
</h3>
|
64
|
+
<h1 class=""><span style="vertical-align: top;" class="material-icons md-36">supervised_user_circle</span> </h1>
|
65
|
+
</div>
|
66
|
+
<p>
|
67
|
+
View all users permissions types defined on the system.
|
68
|
+
</p>
|
69
|
+
</a>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
<div class="col-md-6">
|
73
|
+
<div class="list-group">
|
74
|
+
<a href="<%= admin_operations_actions_path(params: {edit: true}) %>" class="list-group-item list-group-item-action flex-column align-items-start">
|
75
|
+
<div class="d-flex w-100 justify-content-between">
|
76
|
+
<h3 class="mb-1">
|
77
|
+
Edit Actions
|
78
|
+
</h3>
|
79
|
+
<h1 class=""><span style="vertical-align: top;" class="material-icons md-36">offline_bolt</span> </h1>
|
80
|
+
</div>
|
81
|
+
<p>
|
82
|
+
View all users permissions types defined on the system.
|
83
|
+
</p>
|
84
|
+
</a>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
</div> -->
|
88
|
+
</div>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="container">
|
2
|
+
<div class="padded">
|
3
|
+
<div class="justify-content-between d-flex w-100">
|
4
|
+
<h1><%= @editing_title %></h1>
|
5
|
+
<h6>You are a: <%= user_role_badge(current_user) %></h6>
|
6
|
+
</div>
|
7
|
+
<div class="text-center">
|
8
|
+
<div class="small text-muted">
|
9
|
+
User permissions count: <%= @permissions.size %>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<% if @editing %>
|
15
|
+
<%= render 'admin/operations/users/index/edit' %>
|
16
|
+
<% else %>
|
17
|
+
<%= render 'admin/operations/users/index/view' %>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
</div>
|
@@ -0,0 +1,158 @@
|
|
1
|
+
<p>
|
2
|
+
View here all user roles that are defined on the system. <!-- Should you need
|
3
|
+
adding, removing or editing these roles, please click
|
4
|
+
<%= link_to('here', admin_operations_users_path(params: {edit: true})) %>. -->
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<div class="row">
|
8
|
+
<div class="col-md-6">
|
9
|
+
<div class="very lightly padded text-center">
|
10
|
+
<b class="text-muted">User roles</b>
|
11
|
+
</div>
|
12
|
+
<div class="list-group" style="overflow-y: auto; height: 62vh;">
|
13
|
+
<% @permissions.each do |permission| %>
|
14
|
+
<div permission="<%= permission[:name] %>" class="list-group-item list-group-item-action <%= permission[:value].nil? ? "disabled" : 'clickable' %>">
|
15
|
+
<% if permission[:description] && permission[:name] %>
|
16
|
+
<div class="d-flex w-100 justify-content-between">
|
17
|
+
<%= permission[:description] %>
|
18
|
+
<small class="text-muted"><%= permission[:name] %></small>
|
19
|
+
</div>
|
20
|
+
<% else %>
|
21
|
+
<%= permission[:description] || permission[:name] %>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<div class="col-md-6">
|
28
|
+
<div class="very lightly padded">
|
29
|
+
<div class="text-center">
|
30
|
+
<b class="text-muted">More details</b>
|
31
|
+
</div>
|
32
|
+
<div style="margin-top: 10px;">
|
33
|
+
<div class="cards">
|
34
|
+
<div class="text-center">
|
35
|
+
<div before-getting="description" class="card-body">
|
36
|
+
You can click on one of the roles on the right. Doing that
|
37
|
+
will display more details about the role right here.
|
38
|
+
</div>
|
39
|
+
<div before-getting="loading" class="card-body">
|
40
|
+
Please wait while we're fetching the latest information for
|
41
|
+
this operation...
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
<div after-getting="description" class="card-bodys">
|
45
|
+
<p class="very lightly padded">
|
46
|
+
<div class="d-flex w-100 justify-content-between">
|
47
|
+
<span><b>Name: </b> <span after-getting="field" data-field="name"></span></span>
|
48
|
+
<div id="operation-id">
|
49
|
+
<b>Operation ID: </b> <span after-getting="field" data-field="operation_id"></span>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</p>
|
53
|
+
<div class="small">
|
54
|
+
<p class="very lightly padded">
|
55
|
+
Required category to execute this action: <b><span after-getting="field" data-field="scope"></span></b>
|
56
|
+
</p>
|
57
|
+
<div id="operations-list-cont">
|
58
|
+
<p class="very lightly padded">
|
59
|
+
The following <b><span after-getting="field" data-field="operations_count"></span></b> are associated to execute this action:
|
60
|
+
</p>
|
61
|
+
<div id="operations-list" class="list-group"></div>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
|
71
|
+
<script type="text/javascript">
|
72
|
+
$(document).ready(function() {
|
73
|
+
$('[before-getting]').hide();
|
74
|
+
$('[after-getting]').hide();
|
75
|
+
$('[before-getting="description"]').show();
|
76
|
+
$('#operations-list-cont').hide();
|
77
|
+
|
78
|
+
$('[permission]').on('click', function(e) {
|
79
|
+
$('[permission]').removeClass('active');
|
80
|
+
$('[after-getting]').show();
|
81
|
+
$('[before-getting]').hide();
|
82
|
+
$('[before-getting="loading"]').show();
|
83
|
+
|
84
|
+
let target = $(this);
|
85
|
+
$('[permission]').addClass('disabled');
|
86
|
+
let value = target.attr('permission');
|
87
|
+
if (value) {
|
88
|
+
$.ajax({
|
89
|
+
url: '/admin/operations/users/' + value + '/details',
|
90
|
+
success: function(response) {
|
91
|
+
$('[before-getting]').hide();
|
92
|
+
$('#before-getting').hide();
|
93
|
+
$('[permission]').removeClass('disabled');
|
94
|
+
if (response.success) {
|
95
|
+
target.addClass('active');
|
96
|
+
let operation = response.operation;
|
97
|
+
let operations = response.operations;
|
98
|
+
|
99
|
+
$('[after-getting="field"][data-field="name"]')
|
100
|
+
.text(operation.name);
|
101
|
+
$('[after-getting="field"][data-field="scope"]')
|
102
|
+
.text(operation.description);
|
103
|
+
if (operation.value >= 0) {
|
104
|
+
$('#operation-id').show();
|
105
|
+
} else {
|
106
|
+
$('#operation-id').hide();
|
107
|
+
}
|
108
|
+
$('[after-getting="field"][data-field="operation_id"]')
|
109
|
+
.text(operation.value);
|
110
|
+
|
111
|
+
const operations_count = count => {
|
112
|
+
return operations.length == 1 ?
|
113
|
+
'one operation' :
|
114
|
+
operations.length + " operations";
|
115
|
+
}
|
116
|
+
$('#operations-list').text('');
|
117
|
+
if (operations.length > 0) {
|
118
|
+
$('#operations-list-cont').show();
|
119
|
+
$('[after-getting="field"][data-field="operations_count"]')
|
120
|
+
.text(operations_count(operations.length));
|
121
|
+
let sousa_html = [];
|
122
|
+
const users_description = description => {
|
123
|
+
if (!description || !description.trim()) {
|
124
|
+
return "No description is available."
|
125
|
+
}
|
126
|
+
return description;
|
127
|
+
}
|
128
|
+
|
129
|
+
// sousa = 操作 (means "operation"). Didn't want to override the
|
130
|
+
// "operation" var already defined. And mostly to be original...かな〜
|
131
|
+
[].forEach.call(operations, function(sousa) {
|
132
|
+
sousa_html += [
|
133
|
+
'<div class="list-group">',
|
134
|
+
'<div class="list-group-item">',
|
135
|
+
'<div class="d-flex w-100 justify-content-between">',
|
136
|
+
'<span class="bold">' + sousa.name + '</span>',
|
137
|
+
'<small class="text-muted">' + sousa.scope + '</small>',
|
138
|
+
'</div>',
|
139
|
+
'<p class="mb-1">',
|
140
|
+
users_description(sousa.description),
|
141
|
+
'</p>',
|
142
|
+
'</div>',
|
143
|
+
'</div>'
|
144
|
+
].join('');
|
145
|
+
});
|
146
|
+
$('#operations-list').html(sousa_html);
|
147
|
+
} else {
|
148
|
+
operations_list_text = "No operations can have been associated."
|
149
|
+
$('#operations-list-cont').hide();
|
150
|
+
$('[after-getting="field"][data-field="operations_count"]').text('');
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}
|
154
|
+
});
|
155
|
+
}
|
156
|
+
});
|
157
|
+
});
|
158
|
+
</script>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
namespace :admin do
|
3
|
+
resources :operations, only: [ :index ]
|
4
|
+
namespace :operations do
|
5
|
+
resources :users, param: :value, only: [ :index, :create, :update, :delete ] do
|
6
|
+
get :details, on: :member
|
7
|
+
end
|
8
|
+
resources :actions, param: :uuid, only: [ :index, :create, :update, :delete ] do
|
9
|
+
get :details, on: :member
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
class OperationsGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../templates", __FILE__)
|
5
|
+
desc "Creates a Operations initializer to your application."
|
6
|
+
|
7
|
+
def copy_initializer
|
8
|
+
template 'operations.rb', 'config/initializers/operations.rb'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Operations::Config.setup do |config|
|
2
|
+
|
3
|
+
# Add here which operations to restrict on the app.
|
4
|
+
config.operations_list += []
|
5
|
+
|
6
|
+
# Add here what types of users will be able to execute anything
|
7
|
+
# on your app. A user type not defined won't even be able to load
|
8
|
+
# the application, so please be careful.
|
9
|
+
# config.user_roles += []
|
10
|
+
|
11
|
+
# Define what format should operations take.
|
12
|
+
config.operation_name_regex = %r{\w}
|
13
|
+
|
14
|
+
# Defines which hashing algorithm should be used for computing the
|
15
|
+
# operation's UUID. Must be a callable Class instance
|
16
|
+
config.operation_uuid_algorithm = OpenSSL::Digest::SHA256
|
17
|
+
|
18
|
+
# Enforcing the operations to your app can be done by extending the
|
19
|
+
# ApplicationController by Operations::EnforcedController. You also
|
20
|
+
# need to define which routes are associated with which operation.
|
21
|
+
#
|
22
|
+
# Caution: Any route that is not defined here will be ignored and thus
|
23
|
+
# allowed. To ensure you're not forgetting anything, you can use the "*"
|
24
|
+
# as a wildcard symbol.
|
25
|
+
#
|
26
|
+
# Example: associating all actions in the UsersController to the
|
27
|
+
# users_action operation can be done with:
|
28
|
+
# config.enforcements << {controller: :user, action: '*', operation: :users_action}
|
29
|
+
config.enforcements = [
|
30
|
+
# {controller: :application, action: '*', operation: :your_operation}
|
31
|
+
]
|
32
|
+
|
33
|
+
end
|
data/lib/operations.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require "operations/railtie"
|
2
|
+
require "operations/utils"
|
3
|
+
require "operations/core_ext"
|
4
|
+
require "operations/engine"
|
5
|
+
require "operations/config"
|
6
|
+
require "operations/enforcer"
|
7
|
+
require "operations/act_as_operationable"
|
8
|
+
require "operations/errors"
|
9
|
+
require "operations/operation"
|
10
|
+
require "operations/version"
|
11
|
+
|
12
|
+
module Operations
|
13
|
+
class << self
|
14
|
+
def operations_list
|
15
|
+
Operations::Config.get_operations_list
|
16
|
+
end
|
17
|
+
|
18
|
+
def user_roles
|
19
|
+
Operations::Config.user_roles
|
20
|
+
end
|
21
|
+
|
22
|
+
def named_user_roles(all: false)
|
23
|
+
roles = Operations::Config.user_roles
|
24
|
+
roles = roles.reject{|role| role[:db_value].nil?} unless all
|
25
|
+
roles.map{|role| role[:name]}
|
26
|
+
end
|
27
|
+
|
28
|
+
def operations_list_by_uuid
|
29
|
+
operations_list.map{|op| {operation: op, uuid: op.uuid}}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the operation associated with the passed in UUID
|
33
|
+
def from_uuid(uuid)
|
34
|
+
operations_list.select{|operation| operation.uuid == uuid}[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the operation associated with the passed in name
|
38
|
+
def from_string(name)
|
39
|
+
operations_list.select{|operation| operation.name.to_s == name.to_s}[0]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the operations that have scope scope
|
43
|
+
def allowing(scope)
|
44
|
+
return nil if scope.blank?
|
45
|
+
operations_list.select{|operation| operation > scope}
|
46
|
+
end
|
47
|
+
|
48
|
+
def allowed_named_roles_for(scope)
|
49
|
+
current_value = user_role_value_from(scope)
|
50
|
+
user_roles
|
51
|
+
.select{|role| role[:name] == :all ||
|
52
|
+
!role[:value].nil? && role[:value] <= current_value}
|
53
|
+
.map{|role| role[:name]}
|
54
|
+
end
|
55
|
+
|
56
|
+
def from_user_type(scope)
|
57
|
+
value = allowed_named_roles_for(scope.to_sym)
|
58
|
+
scopes = value.reject{|role| role.nil?}
|
59
|
+
end
|
60
|
+
|
61
|
+
def allowed_int_roles_for(scope)
|
62
|
+
allowed_named_roles_for(scope)
|
63
|
+
.map{|named_scope| user_role_int_from(named_scope)}
|
64
|
+
end
|
65
|
+
|
66
|
+
def allowed_value_roles_for(scope)
|
67
|
+
allowed_named_roles_for(scope)
|
68
|
+
.map{|named_scope| user_role_value_from(named_scope)}
|
69
|
+
end
|
70
|
+
|
71
|
+
# Determines if the user is allowed to execute the operation name
|
72
|
+
def allows?(user, name)
|
73
|
+
operation = from_string(name)
|
74
|
+
return false if operation.nil?
|
75
|
+
operation.users.map{|u| u.id}.include?(user.id)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a list of user roles names straight from the config file
|
79
|
+
def user_roles_names
|
80
|
+
user_roles.map{|ur| ur[:name]}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a list of users roles that qualify as role_name
|
84
|
+
def user_roles_from(role_name, select_only: false)
|
85
|
+
value = user_role_value_from(role_name)
|
86
|
+
return [] if value.nil?
|
87
|
+
compare =-> (role, value) do
|
88
|
+
select_only ? role[:value] == value : role[:value] <= value
|
89
|
+
end
|
90
|
+
user_roles
|
91
|
+
.select{|role| role[:value] && compare.call(role, value)}
|
92
|
+
.map{|role| role[:db_value]}
|
93
|
+
.reject{|res| res.nil?}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns a list of users roles that do not qualify as role_name
|
97
|
+
def user_roles_until(role_name)
|
98
|
+
value = user_role_value_from(role_name)
|
99
|
+
return [] if value.nil?
|
100
|
+
user_roles
|
101
|
+
.select{|role| role[:value] && role[:value] > value}
|
102
|
+
.map{|role| role[:db_value]}
|
103
|
+
end
|
104
|
+
|
105
|
+
def scopes_as(scope)
|
106
|
+
value = user_role_value_from(scope)
|
107
|
+
return [] if value.nil?
|
108
|
+
return [scope] if %w{all nobody}.include?(scope.to_s)
|
109
|
+
user_roles
|
110
|
+
.select{|role| role[:value] && role[:value] >= value}
|
111
|
+
.map{|role| role[:name]}
|
112
|
+
end
|
113
|
+
|
114
|
+
def operations_for(scope)
|
115
|
+
scopes = scopes_as(scope)
|
116
|
+
list = operations_list
|
117
|
+
if %w{all nobody}.include?(scope.to_s)
|
118
|
+
list.select{|op| op == scope}.uniq
|
119
|
+
else
|
120
|
+
list.select{|op| op >= scope}.uniq
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns a list of users that have roles user_roles_from
|
125
|
+
def users_acting_as(role_name, select_only: false)
|
126
|
+
User.where(role: user_roles_from(role_name, select_only: select_only))
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a list of users that have roles user_roles_until
|
130
|
+
def users_denied_from(role_name)
|
131
|
+
User.where(role: user_roles_until(role_name))
|
132
|
+
end
|
133
|
+
|
134
|
+
def user_role_int_from(role_name)
|
135
|
+
result = user_roles.select{|role| role[:name] == role_name}[0]
|
136
|
+
return nil if result.nil?
|
137
|
+
result[:db_value]
|
138
|
+
end
|
139
|
+
|
140
|
+
def user_role_value_from(role_name)
|
141
|
+
result = user_roles.select{|role| role[:name] == role_name}[0]
|
142
|
+
return nil if result.nil?
|
143
|
+
result[:value]
|
144
|
+
end
|
145
|
+
|
146
|
+
def user_role_name_from(role_int)
|
147
|
+
result = user_roles.select{|role| role[:db_value] == role_int}[0]
|
148
|
+
return nil if result.nil?
|
149
|
+
result[:name]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|