aki-operations 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +87 -0
  4. data/Rakefile +27 -0
  5. data/app/controllers/admin/operations/actions_controller.rb +28 -0
  6. data/app/controllers/admin/operations/users_controller.rb +28 -0
  7. data/app/controllers/admin/operations_controller.rb +12 -0
  8. data/app/controllers/operations/enforced_controller.rb +45 -0
  9. data/app/helpers/operations_helper.rb +15 -0
  10. data/app/views/admin/operations/actions/index.html.erb +20 -0
  11. data/app/views/admin/operations/actions/index/_edit.html.erb +0 -0
  12. data/app/views/admin/operations/actions/index/_view.html.erb +156 -0
  13. data/app/views/admin/operations/index.html.erb +88 -0
  14. data/app/views/admin/operations/users/index.html.erb +20 -0
  15. data/app/views/admin/operations/users/index/_edit.html.erb +4 -0
  16. data/app/views/admin/operations/users/index/_view.html.erb +158 -0
  17. data/config/routes.rb +13 -0
  18. data/lib/generators/operations_generator.rb +10 -0
  19. data/lib/generators/templates/operations.rb +33 -0
  20. data/lib/operations.rb +152 -0
  21. data/lib/operations/act_as_operationable.rb +26 -0
  22. data/lib/operations/config.rb +64 -0
  23. data/lib/operations/core_ext.rb +28 -0
  24. data/lib/operations/enforcer.rb +71 -0
  25. data/lib/operations/engine.rb +5 -0
  26. data/lib/operations/errors.rb +6 -0
  27. data/lib/operations/errors/base_exception.rb +12 -0
  28. data/lib/operations/errors/invalid_field_error.rb +9 -0
  29. data/lib/operations/errors/invalid_operation_error.rb +26 -0
  30. data/lib/operations/errors/not_authorized_error.rb +11 -0
  31. data/lib/operations/errors/not_implemented_error.rb +9 -0
  32. data/lib/operations/errors/not_logged_in_error.rb +10 -0
  33. data/lib/operations/operation.rb +167 -0
  34. data/lib/operations/railtie.rb +17 -0
  35. data/lib/operations/utils.rb +10 -0
  36. data/lib/operations/version.rb +3 -0
  37. data/lib/tasks/operations_tasks.rake +6 -0
  38. 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,4 @@
1
+ <p class="very lighly padded">
2
+ Manage here all user roles that are defined on the system. You can
3
+ add, remove or edit these roles on this page.
4
+ </p>
@@ -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>
@@ -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
@@ -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