aki-operations 1.0.0
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.
- 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
|