four_eyes 0.1.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 +103 -0
- data/Rakefile +26 -0
- data/app/assets/javascripts/four_eyes/application.js +13 -0
- data/app/assets/stylesheets/four_eyes/application.css +15 -0
- data/app/controllers/four_eyes/actions_controller.rb +13 -0
- data/app/controllers/four_eyes/application_controller.rb +4 -0
- data/app/helpers/four_eyes/application_helper.rb +4 -0
- data/app/models/four_eyes/action.rb +23 -0
- data/app/views/four_eyes/actions/index.html.erb +30 -0
- data/app/views/four_eyes/actions/show.html.erb +46 -0
- data/config/routes.rb +3 -0
- data/lib/four_eyes/concerns/controllers/actions_controller.rb +170 -0
- data/lib/four_eyes/controller_additions.rb +57 -0
- data/lib/four_eyes/controller_resource.rb +116 -0
- data/lib/four_eyes/engine.rb +11 -0
- data/lib/four_eyes/inherited_resource.rb +4 -0
- data/lib/four_eyes/version.rb +3 -0
- data/lib/four_eyes.rb +4 -0
- data/lib/generators/four_eyes/four_eyes_generator.rb +25 -0
- data/lib/generators/four_eyes/templates/migration.rb +25 -0
- data/lib/tasks/four_eyes_tasks.rake +4 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 164ea5dca880f69d877e8352df5dbaefeb84b016
|
4
|
+
data.tar.gz: 5f7d328e524f1c5fcc0957a172a4488cb26dbbea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d099d08b459a46028d9bffeaa3fb94549da48b580bc8ad1671d576a8ff98665cd291fb922bccf57cedca8aabe271867c96602a3d0ee1b37390b15d73711b0def
|
7
|
+
data.tar.gz: 28e291b6a365392c8ff58f6940198459685d46445f30472a2b0603004d1b7d8a41176fa63867440a7ab2d6ef094c28581da3b941dd3481307f4bad376008756a
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Dennis Ondeng
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# FourEyes
|
2
|
+
|
3
|
+
A gem to implement the maker-checker principle. The 4-eyes principle.
|
4
|
+
|
5
|
+
Maker-checker (or Maker and Checker, or 4-Eyes) is one of the central principles of authorization in the information systems
|
6
|
+
of financial organizations.The principle of maker and checker means that for each transaction, there must be at least two
|
7
|
+
individuals necessary for its completion. While one individual may create a transaction, the other individual should be involved
|
8
|
+
in confirmation/authorization of the same. Here the segregation of duties plays an important role. In this way, strict control
|
9
|
+
is kept over system software and data, keeping in mind functional division of labor between all classes of employees.
|
10
|
+
|
11
|
+
(courtesy Wikipedia)
|
12
|
+
|
13
|
+
Working with institutions such as financial institutions you may be required to restrict some actions to be done by two pairs of eyes.
|
14
|
+
FourEyes makes it easy to do this by adding minimal code to the controller with the actions you would like to have verified.
|
15
|
+
|
16
|
+
For every action that needs to be authorized, an action containing all the details is stored together with the changes required.
|
17
|
+
The new object or changes are stored on JSON format.
|
18
|
+
This is done after a validation check to ensure that the object will pass validation during the authorization stage. You are
|
19
|
+
responsible for performing the validation check to see if the resource is indeed valid. However, this cannot be guaranteed because
|
20
|
+
the system state may change before authorization to a state that renders the action invalid. In such a scenario, the action
|
21
|
+
would need to be cancelled and created again.
|
22
|
+
|
23
|
+
A listing of all pending actions can be availed and depending on any authorization mechanisim that you have implemented, a user can then access a pending
|
24
|
+
action and authorize it.
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
gem 'four_eyes'
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install four_eyes
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
To add maker checker functionality, add the following before_filter to the controller in question.
|
43
|
+
Sets up a before filter which adds maker checker functionality to the controller
|
44
|
+
|
45
|
+
class StudentsController < ApplicationController
|
46
|
+
add_maker_checker_to_resource
|
47
|
+
end
|
48
|
+
|
49
|
+
To exempt any one of the actions
|
50
|
+
|
51
|
+
class StudentsController < ApplicationController
|
52
|
+
add_maker_checker_to_resource, except: :delete
|
53
|
+
end
|
54
|
+
|
55
|
+
To include only a subset of the actions
|
56
|
+
|
57
|
+
class StudentsController < ApplicationController
|
58
|
+
add_maker_checker_to_resource, only: [:create, :update]
|
59
|
+
end
|
60
|
+
|
61
|
+
Once that is done, in the create, update or delete action you would call the following
|
62
|
+
|
63
|
+
maker_create([User resource performaing the action],
|
64
|
+
[ID of resource performing the action],
|
65
|
+
[Class name of the resource being worked on],
|
66
|
+
[Parameters of oject/resource in JSON format])
|
67
|
+
|
68
|
+
For exeample, in a system where the users are called Administrators, and the resource we are trying to create via
|
69
|
+
maker checker is a Student, the call to create a student via maker-checker would look like this.
|
70
|
+
|
71
|
+
|
72
|
+
def create
|
73
|
+
maker_create('Administrator',
|
74
|
+
current_administrator_id,
|
75
|
+
'Student',
|
76
|
+
student_params.to_json)
|
77
|
+
end
|
78
|
+
|
79
|
+
def update
|
80
|
+
maker_update('Administrator',
|
81
|
+
current_administrator_id,
|
82
|
+
'Student',
|
83
|
+
student_params.to_json)
|
84
|
+
end
|
85
|
+
|
86
|
+
def destroy
|
87
|
+
maker_delete('Administrator',
|
88
|
+
'current_administrator_id,
|
89
|
+
'Student',
|
90
|
+
student.to_json)
|
91
|
+
end
|
92
|
+
|
93
|
+
where in the example above, the call has the following format
|
94
|
+
|
95
|
+
## TODO - Write spec tests.
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
1. Fork it ( https://github.com/[my-github-username]/four_eyes/fork )
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
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 = 'FourEyes'
|
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("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= 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 any plugin's vendor/assets/stylesheets directory 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_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module FourEyes
|
2
|
+
# This controller provides restful route handling for Actions.
|
3
|
+
#
|
4
|
+
# == Security:
|
5
|
+
# Only GET requests are supported. You should ensure that your application
|
6
|
+
# controller enforces its own authentication and authorization, which this
|
7
|
+
# controller will inherit.
|
8
|
+
#
|
9
|
+
# @author Dennis Ondeng
|
10
|
+
class ActionsController < ApplicationController
|
11
|
+
include FourEyes::Concerns::Controllers::Actions
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module FourEyes
|
2
|
+
class Action < ActiveRecord::Base
|
3
|
+
validates :action_type, :maker_resource_id, :status, presence: true
|
4
|
+
|
5
|
+
|
6
|
+
def self.between_times(start_time, end_time)
|
7
|
+
Action.where('created_at >= ? AND created_at < ?', start_time, end_time)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initiated?
|
11
|
+
self.status == 'Initiated'
|
12
|
+
end
|
13
|
+
|
14
|
+
def cancelled?
|
15
|
+
self.status == 'Cancelled'
|
16
|
+
end
|
17
|
+
|
18
|
+
def authorized?
|
19
|
+
self.status == 'Authorized'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<div class="four_eyes_container">
|
2
|
+
<h1>Actions</h1>
|
3
|
+
|
4
|
+
<table cellspacing="0">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th class="nobg">ID</th>
|
8
|
+
<th>Date</th>
|
9
|
+
<th>Action</th>
|
10
|
+
<th>Maker ID</th>
|
11
|
+
<th>Object</th>
|
12
|
+
<th>Status</th>
|
13
|
+
<th>Data</th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
<tbody>
|
17
|
+
<% @actions.each do |action| %>
|
18
|
+
<tr class="<%= cycle("even", "odd") -%>">
|
19
|
+
<td><%=link_to(action.id, action_path(action)) %></td>
|
20
|
+
<td><%= action.created_at %></td>
|
21
|
+
<td><%=h action.action_type %></td>
|
22
|
+
<td><%=h action.maker_resource_id %></td>
|
23
|
+
<td><%=h action.object_resource_class_name %></td>
|
24
|
+
<td><%=h action.status %></td>
|
25
|
+
<td><%=h action.data %></td>
|
26
|
+
</tr>
|
27
|
+
<% end %>
|
28
|
+
</tbody>
|
29
|
+
</table>
|
30
|
+
</div>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<div class="four_eyes_container">
|
2
|
+
<h1>Action</h1>
|
3
|
+
|
4
|
+
<table>
|
5
|
+
<tr>
|
6
|
+
<td>ID</td>
|
7
|
+
<td><%= @action.id %></td>
|
8
|
+
</tr>
|
9
|
+
<tr>
|
10
|
+
<td>Date</td>
|
11
|
+
<td><%= @action.created_at %></td>
|
12
|
+
</tr>
|
13
|
+
<tr>
|
14
|
+
<td>Action</td>
|
15
|
+
<td><%= @action.action_type %></td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td>Maker-Checker Resource</td>
|
19
|
+
<td><%= @action.resource_class_name %></td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td>Maker resource ID</td>
|
23
|
+
<td><%= @action.maker_resource_id %></td>
|
24
|
+
</tr>
|
25
|
+
<tr>
|
26
|
+
<td>Checker Resource ID</td>
|
27
|
+
<td><%= @action.checker_resource_id %></td>
|
28
|
+
</tr>
|
29
|
+
<tr>
|
30
|
+
<td>Object</td>
|
31
|
+
<td><%= @action.object_resource_class_name %></td>
|
32
|
+
</tr>
|
33
|
+
<tr>
|
34
|
+
<td>Object ID</td>
|
35
|
+
<td><%= @action.object_resource_id %></td>
|
36
|
+
</tr>
|
37
|
+
<tr>
|
38
|
+
<td>Data</td>
|
39
|
+
<td><%= @action.data.to_s %></td>
|
40
|
+
</tr>
|
41
|
+
<tr>
|
42
|
+
<td>Status</td>
|
43
|
+
<td><%= @action.status %></td>
|
44
|
+
</tr>
|
45
|
+
</table>
|
46
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
module FourEyes
|
2
|
+
module Concerns
|
3
|
+
module Controllers
|
4
|
+
module Actions
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
#included do
|
8
|
+
#end
|
9
|
+
|
10
|
+
# @example
|
11
|
+
# GET /actions
|
12
|
+
# GET /actions.xml
|
13
|
+
# GET /actions.json
|
14
|
+
def index
|
15
|
+
@actions = Action.all
|
16
|
+
|
17
|
+
respond_to do |format|
|
18
|
+
format.html # index.html.erb
|
19
|
+
format.xml { render :xml => @actions }
|
20
|
+
format.json { render :json => @actions }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @example
|
25
|
+
# GET /action/1
|
26
|
+
# GET /action/1.xml
|
27
|
+
# GET /action/1.json
|
28
|
+
def show
|
29
|
+
@action = Action.find(params[:id])
|
30
|
+
respond_to do |format|
|
31
|
+
format.html # show.html.erb
|
32
|
+
format.xml { render :xml => @action }
|
33
|
+
format.json { render :json => @action }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Perform a checker eligibility test.
|
38
|
+
# At the most basic level, the actor doing the initiating cannot be the same person
|
39
|
+
# doing the checking
|
40
|
+
#
|
41
|
+
# @params action - The action to be authorized
|
42
|
+
# @params resource - The id of the actor requesting the authorization
|
43
|
+
#
|
44
|
+
def eligible_to_check(action, resource_id)
|
45
|
+
action.maker_resource_id != resource_id
|
46
|
+
end
|
47
|
+
|
48
|
+
# Perform the checker action for the maker checker actions
|
49
|
+
# Dispatch to function to process the corresponding action (create, upated, delete)
|
50
|
+
#
|
51
|
+
# @param id - The id of the action being authorized
|
52
|
+
# @param checker_resource_id - The id of the actor performing the authorization
|
53
|
+
#
|
54
|
+
def authorize
|
55
|
+
@action = Action.find(params[:id])
|
56
|
+
checker_resource_id = params[:checker_resource_id].to_i
|
57
|
+
if eligible_to_check(@action, checker_resource_id)
|
58
|
+
if @action && @action.initiated? && checker_resource_id
|
59
|
+
self.send(@action.action_type.gsub('action_', 'checker_'), @action, checker_resource_id)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
flash[:notice] = 'You are not eligible to authorize this action'
|
63
|
+
redirect_to action: :index and return
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Retrieve hash of saved parameters and instantiate a new object of type object_resource_class_name
|
68
|
+
#
|
69
|
+
# @param action - The action to authorize
|
70
|
+
# @param resource_id - The id of the actor performing the authorization
|
71
|
+
#
|
72
|
+
def checker_create(action, resource_id)
|
73
|
+
object_resource = action.object_resource_class_name.constantize.new(action.data.deep_symbolize_keys)
|
74
|
+
if object_resource.save
|
75
|
+
action.status = 'Authorized'
|
76
|
+
action.checker_resource_id = resource_id
|
77
|
+
if action.save
|
78
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} authorized and created successfully."
|
79
|
+
redirect_to action: :index and return
|
80
|
+
else
|
81
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} created successfully. Action not updated"
|
82
|
+
redirect_to action: :index and return
|
83
|
+
end
|
84
|
+
else
|
85
|
+
flash[:error] = object_resource.errors.full_messages
|
86
|
+
redirect_to action: :index and return
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Retrieve hash of saved parameters and update the object of type object_resource_class_name
|
91
|
+
#
|
92
|
+
# @param action - The action to authorize
|
93
|
+
# @param resource_id - The id of the actor performing the authorization
|
94
|
+
#
|
95
|
+
def checker_update(action, resource_id)
|
96
|
+
begin
|
97
|
+
object_resource = action.object_resource_class_name.constantize.find(action.object_resource_id)
|
98
|
+
if object_resource.update_attributes(action.data.deep_symbolize_keys)
|
99
|
+
action.status = 'Authorized'
|
100
|
+
action.checker_resource_id = resource_id
|
101
|
+
if action.save
|
102
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} authorized and updated successfully."
|
103
|
+
redirect_to action: :index and return
|
104
|
+
else
|
105
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} updated successfully. Action not updated"
|
106
|
+
redirect_to action: :index and return
|
107
|
+
end
|
108
|
+
else
|
109
|
+
flash[:error] = object_resource.errors.full_messages
|
110
|
+
redirect_to action: :index and return
|
111
|
+
end
|
112
|
+
rescue ActiveRecord::RecordNotFound
|
113
|
+
flash[:error] = 'Record not found'
|
114
|
+
redirect_to action: :index and return
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Retrieve a delete action and call destroy on it
|
119
|
+
#
|
120
|
+
# @param action - The action to authorize
|
121
|
+
# @param resource_id - The id of the actor performing the delete authorization
|
122
|
+
#
|
123
|
+
def checker_delete(action, resource_id)
|
124
|
+
begin
|
125
|
+
object_resource = action.object_resource_class_name.constantize.find(action.object_resource_id)
|
126
|
+
if object_resource.destroy
|
127
|
+
action.status = 'Authorized'
|
128
|
+
action.checker_resource_id = resource_id
|
129
|
+
if action.save
|
130
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} authorized and deleted successfully."
|
131
|
+
redirect_to action: :index and return
|
132
|
+
else
|
133
|
+
flash[:notice] = "#{action.object_resource_class_name.titlecase} deleted successfully. Action not updated"
|
134
|
+
redirect_to action: :index and return
|
135
|
+
end
|
136
|
+
else
|
137
|
+
flash[:error] = object_resource.errors.full_messages
|
138
|
+
redirect_to action: :index and return
|
139
|
+
end
|
140
|
+
rescue ActiveRecord::RecordNotFound
|
141
|
+
flash[:error] = 'Record not found'
|
142
|
+
redirect_to action: :index and return
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Cancel an action that had been previously initiated
|
148
|
+
#
|
149
|
+
# @param id - The id of the action action that is to be cancelled
|
150
|
+
# @param resource_id - The id of the actor performing the cancellation
|
151
|
+
#
|
152
|
+
def cancel
|
153
|
+
@action = Action.find(params[:id])
|
154
|
+
checker_resource_id = params[:checker_resource_id].to_i
|
155
|
+
if @action
|
156
|
+
@action.status = 'Cancelled'
|
157
|
+
@action.checker_resource_id = checker_resource_id
|
158
|
+
if @action.save
|
159
|
+
flash[:notice] = "Action on #{@action.object_resource_class_name.titlecase} cancelled successfully."
|
160
|
+
redirect_to action: :index and return
|
161
|
+
else
|
162
|
+
flash.now[:error] = @action.errors.full_messages
|
163
|
+
redirect_to action: :index and return
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module FourEyes
|
2
|
+
|
3
|
+
# This module is automatically included into all controllers that will be implementing the
|
4
|
+
# Maker-Checker functionality
|
5
|
+
#
|
6
|
+
module ControllerAdditions
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Sets up a before filter which adds maker checker functionality to the controller
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
13
|
+
# class StudentsController < ApplicationController
|
14
|
+
# add_maker_checker_to_resource
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# To exempt any one of the actions
|
18
|
+
#
|
19
|
+
# class StudentsController < ApplicationController
|
20
|
+
# add_maker_checker_to_resource, except: :delete
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# To include only a subset of the actions
|
24
|
+
#
|
25
|
+
# class StudentsController < ApplicationController
|
26
|
+
# add_maker_checker_to_resource, only: [:create, :update]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
def add_maker_checker_to_resource(*args)
|
30
|
+
|
31
|
+
# Add maker functions
|
32
|
+
four_eyes_resource_class.add_maker_create_function(self, :maker_create, *args)
|
33
|
+
four_eyes_resource_class.add_maker_update_function(self, :maker_update, *args)
|
34
|
+
four_eyes_resource_class.add_maker_delete_function(self, :maker_delete, *args)
|
35
|
+
four_eyes_resource_class.add_maker_generic_function(self, :maker_generic, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def four_eyes_resource_class
|
39
|
+
if ancestors.map(&:to_s).include? "InheritedResources::Actions"
|
40
|
+
InheritedResource
|
41
|
+
else
|
42
|
+
ControllerResource
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.included(base)
|
48
|
+
base.extend ClassMethods
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if defined? ActionController::Base
|
54
|
+
ActionController::Base.class_eval do
|
55
|
+
include FourEyes::ControllerAdditions
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module FourEyes
|
2
|
+
class ControllerResource
|
3
|
+
|
4
|
+
# Define the dynamic maker_create function
|
5
|
+
# This handles the storing of the action and it's meta data to the four_eyes_actions
|
6
|
+
# table with the status of 'Initiated'
|
7
|
+
#
|
8
|
+
def self.add_maker_create_function(controller_class, method, *args)
|
9
|
+
options = args.extract_options!
|
10
|
+
controller_class.send :define_method, method do |*args|
|
11
|
+
"#{method} #{args}"
|
12
|
+
resource_class_name = args[0]
|
13
|
+
resource_id = args[1]
|
14
|
+
object_class_name = args[2]
|
15
|
+
data = args[3]
|
16
|
+
|
17
|
+
action = FourEyes::Action.new(resource_class_name: resource_class_name,
|
18
|
+
maker_resource_id: resource_id,
|
19
|
+
resource_class_name: resource_class_name,
|
20
|
+
action_type: 'action_create',
|
21
|
+
object_resource_class_name: object_class_name,
|
22
|
+
status: 'Initiated',
|
23
|
+
data: data)
|
24
|
+
if action.save!
|
25
|
+
true
|
26
|
+
else
|
27
|
+
# TODO dondeng - Better to raise an exception here
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.add_maker_update_function(controller_class, method, *args)
|
34
|
+
options = args.extract_options!
|
35
|
+
controller_class.send :define_method, method do |*args|
|
36
|
+
resource_class_name = args[0]
|
37
|
+
resource_id = args[1]
|
38
|
+
object_class_name = args[2]
|
39
|
+
object_resource_id = args[3]
|
40
|
+
data = args[4]
|
41
|
+
|
42
|
+
action = FourEyes::Action.new(resource_class_name: resource_class_name,
|
43
|
+
maker_resource_id: resource_id,
|
44
|
+
action_type: 'action_update',
|
45
|
+
object_resource_class_name: object_class_name,
|
46
|
+
object_resource_id: object_resource_id,
|
47
|
+
status: 'Initiated',
|
48
|
+
data: data)
|
49
|
+
if action.save
|
50
|
+
true
|
51
|
+
else
|
52
|
+
# TODO - dondeng - Better to raise an exception here
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.add_maker_delete_function(controller_class, method, *args)
|
59
|
+
options = args.extract_options!
|
60
|
+
controller_class.send :define_method, method do |*args|
|
61
|
+
resource_class_name = args[0]
|
62
|
+
resource_id = args[1]
|
63
|
+
object_class_name = args[2]
|
64
|
+
object_resource_id = args[3]
|
65
|
+
data = args[4]
|
66
|
+
|
67
|
+
action = FourEyes::Action.new(resource_class_name: resource_class_name,
|
68
|
+
maker_resource_id: resource_id,
|
69
|
+
action_type: 'action_delete',
|
70
|
+
object_resource_class_name: object_class_name,
|
71
|
+
object_resource_id: object_resource_id,
|
72
|
+
status: 'Initiated',
|
73
|
+
data: data)
|
74
|
+
if action.save
|
75
|
+
true
|
76
|
+
else
|
77
|
+
# TODO - dondeng - Better to raise an exception here
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.add_maker_generic_function(controller_class, method, *args)
|
84
|
+
options = args.extract_options!
|
85
|
+
controller_class.send :define_method, method do |*args|
|
86
|
+
resource_class_name = args[0]
|
87
|
+
resource_id = args[1]
|
88
|
+
object_class_name = args[2]
|
89
|
+
object_resource_id = args[3]
|
90
|
+
action = args[4]
|
91
|
+
data = args[5]
|
92
|
+
|
93
|
+
action = FourEyes::Action.new(resource_class_name: resource_class_name,
|
94
|
+
maker_resource_id: resource_id,
|
95
|
+
action_type: action,
|
96
|
+
object_resource_class_name: object_class_name,
|
97
|
+
object_resource_id: object_resource_id,
|
98
|
+
status: 'Initiated',
|
99
|
+
data: data)
|
100
|
+
if action.save
|
101
|
+
true
|
102
|
+
else
|
103
|
+
# TODO - dondeng - Better to raise an exception here
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize(controller, *args)
|
110
|
+
@controller = controller
|
111
|
+
@params = controller.params
|
112
|
+
@options = args.extract_options!
|
113
|
+
@name = args.first
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'four_eyes/version'
|
2
|
+
require 'four_eyes/controller_resource'
|
3
|
+
require 'four_eyes/controller_additions'
|
4
|
+
require 'four_eyes/inherited_resource'
|
5
|
+
require 'four_eyes/concerns/controllers/actions_controller'
|
6
|
+
|
7
|
+
module FourEyes
|
8
|
+
class Engine < ::Rails::Engine
|
9
|
+
isolate_namespace FourEyes
|
10
|
+
end
|
11
|
+
end
|
data/lib/four_eyes.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class FourEyesGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
def self.source_root
|
8
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
9
|
+
end
|
10
|
+
|
11
|
+
# Implement the required interface for Rails::Generators::Migration.
|
12
|
+
# taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
|
13
|
+
def self.next_migration_number(dirname)
|
14
|
+
if ActiveRecord::Base.timestamped_migrations
|
15
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
16
|
+
else
|
17
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_migration_file
|
22
|
+
migration_template 'migration.rb', 'db/migrate/create_four_eyes_tables.rb'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class CreateFourEyesTables < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :four_eyes_actions do |t|
|
4
|
+
t.string :action_type
|
5
|
+
t.string :resource_class_name
|
6
|
+
t.integer :maker_resource_id
|
7
|
+
t.integer :checker_resource_id
|
8
|
+
t.integer :maker_resource_role_id
|
9
|
+
t.integer :checker_resource_role_id
|
10
|
+
t.string :object_resource_class_name
|
11
|
+
t.integer :object_resource_id
|
12
|
+
t.json :data
|
13
|
+
t.string :status
|
14
|
+
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
|
18
|
+
add_index :four_eyes_actions, :maker_resource_id
|
19
|
+
add_index :four_eyes_actions, :checker_resource_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
drop_table :four_eyes_actions
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: four_eyes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dennis Ondeng
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: sqlite3
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec-rails
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
description: 'courtesy - Wikipedia: Maker-checker (or Maker and Checker, or 4-Eyes)
|
76
|
+
is one of the central principles of authorization in the information systems of
|
77
|
+
financial organizations.The principle of maker and checker means that for each transaction,
|
78
|
+
there must be at least two individuals necessary for its completion. While one individual
|
79
|
+
may create a transaction, the other individual should be involved in confirmation/authorization
|
80
|
+
of the same. Here the segregation of duties plays an important role. In this way,
|
81
|
+
strict control is kept over system software and data, keeping in mind functional
|
82
|
+
division of labor between all classes of employees.'
|
83
|
+
email:
|
84
|
+
- dondeng2@gmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- MIT-LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- app/assets/javascripts/four_eyes/application.js
|
93
|
+
- app/assets/stylesheets/four_eyes/application.css
|
94
|
+
- app/controllers/four_eyes/actions_controller.rb
|
95
|
+
- app/controllers/four_eyes/application_controller.rb
|
96
|
+
- app/helpers/four_eyes/application_helper.rb
|
97
|
+
- app/models/four_eyes/action.rb
|
98
|
+
- app/views/four_eyes/actions/index.html.erb
|
99
|
+
- app/views/four_eyes/actions/show.html.erb
|
100
|
+
- config/routes.rb
|
101
|
+
- lib/four_eyes.rb
|
102
|
+
- lib/four_eyes/concerns/controllers/actions_controller.rb
|
103
|
+
- lib/four_eyes/controller_additions.rb
|
104
|
+
- lib/four_eyes/controller_resource.rb
|
105
|
+
- lib/four_eyes/engine.rb
|
106
|
+
- lib/four_eyes/inherited_resource.rb
|
107
|
+
- lib/four_eyes/version.rb
|
108
|
+
- lib/generators/four_eyes/four_eyes_generator.rb
|
109
|
+
- lib/generators/four_eyes/templates/migration.rb
|
110
|
+
- lib/tasks/four_eyes_tasks.rake
|
111
|
+
homepage: https://github.com/dondeng/four_eyes
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.4.8
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: A gem to implement the maker-checker principle. The 4-eyes principle
|
135
|
+
test_files: []
|