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.
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,26 @@
1
+ module Operations
2
+ module ActAsOperationable extend ActiveSupport::Concern
3
+ class_methods do
4
+ def acts_as_operationable(options={})
5
+ end
6
+ end
7
+
8
+ def get_operations
9
+ unless respond_to?(:operations)
10
+ raise Operations::Errors::NotImplementedError.new(self.class, :operations)
11
+ end
12
+ unless self.operations.nil? || self.operations.class == String
13
+ raise Operations::Errors::InvalidFieldError.new(:operations, self.class, String)
14
+ end
15
+ self.operations.to_s.to_operations
16
+ end
17
+
18
+ def named_scope
19
+ Operations.user_role_name_from(role || 2)
20
+ end
21
+
22
+ def as_json(options={})
23
+ super.as_json(options).merge({scope: named_scope, operations: get_operations})
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ module Operations
2
+ module Config
3
+ # Returns a list of user roles straight from the config file
4
+ mattr_accessor :user_roles
5
+ @@user_roles = [
6
+ {name: :admin, description: 'Administrator', value: 0, db_value: 0},
7
+ {name: :guest, description: 'Anyone', value: 3, db_value: 1},
8
+ {name: :regular, description: 'Regular user', value: 2, db_value: 2},
9
+ {name: :technician, description: 'Technician', value: 1, db_value: 3},
10
+ {name: :all, description: 'Anyone on the system', value: -1},
11
+ {name: :nobody, description: 'No one on the system', value: -2}
12
+ ]
13
+
14
+ mattr_accessor :operation_name_regex
15
+ @@operation_name_regex = %r{\w}
16
+
17
+ mattr_accessor :operation_uuid_algorithm
18
+ @@operation_uuid_algorithm = OpenSSL::Digest::SHA256
19
+
20
+ # Returns a list of Operation instances for convenience
21
+ mattr_accessor :operations_list
22
+ @@operations_list = []
23
+
24
+ # Specifies how the operations should be enforced within the app
25
+ mattr_accessor :enforcements
26
+ @@enforcements = []
27
+
28
+ # Defines the default path to go to should the user not be logged in
29
+ mattr_accessor :sign_in_path
30
+ @@sign_in_path = nil
31
+
32
+ class << self
33
+ def operation_scope_regex
34
+ %r{\A(#{user_roles.map{|o| o[:name]}.join('|')})\z}
35
+ end
36
+
37
+ def operation_scope_regex=(*args)
38
+ raise 'Operations::Config does not allow to set the variable operation_name_regex'
39
+ end
40
+
41
+ def get_sign_in_path
42
+ if sign_in_path.nil?
43
+ warn "Warning! You have not set the Operations::Config.sign_in_path variable!"
44
+ return nil
45
+ end
46
+ Rails.application.routes.recognize_path sign_in_path
47
+ rescue ActionController::RoutingError
48
+ {controller: nil, action: nil}
49
+ end
50
+ end
51
+
52
+ # A simple way to setup Operations.
53
+ def self.setup
54
+ yield self
55
+ end
56
+
57
+ def self.get_operations_list
58
+ operations_list
59
+ .map{|operation|
60
+ Operations::Operation.new(**operation)}
61
+ .select{|operation| operation.is_valid?}
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ class String
2
+ def to_operations
3
+ real_size = self.blank? ? 0 : self.strip.size
4
+ return [] if real_size == 0
5
+
6
+ by_uuid = Operations.from_uuid(self)
7
+ return by_uuid unless by_uuid.nil?
8
+
9
+ operations_list = []
10
+
11
+ begin
12
+ json_operations = JSON.parse(self)
13
+ rescue JSON::ParserError
14
+ raise Operations::Errors::InvalidOperationError.new('Could not parsel this String into a Operations::Operation object')
15
+ end
16
+ if json_operations.class == Array
17
+ json_operations.each do |json_value|
18
+ operations_list.append(json_value.to_s.to_operations)
19
+ end
20
+ elsif json_operations.class == Hash # OK
21
+ json_operations.deep_symbolize_keys!
22
+ operations_list = Operations::Operation.new(**json_operations)
23
+ end
24
+
25
+ operations_list
26
+ end
27
+ alias :to_operation :to_operations
28
+ end
@@ -0,0 +1,71 @@
1
+ module Operations
2
+ module Enforcer
3
+ class << self
4
+ def default_operation
5
+ Operations::Operation.new do |operation|
6
+ operation.name = :default_enforced_operation
7
+ operation.scope = :admin
8
+ end
9
+ end
10
+
11
+ def application_actions
12
+
13
+ end
14
+
15
+ def check_for_pattern(rule, value)
16
+ rule = rule.to_s; value = value.to_s
17
+ value_ok = rule.nil? \
18
+ || rule == '*' \
19
+ || rule.to_s == value.to_s
20
+ unless value_ok
21
+ rule = rule.to_s
22
+ if rule.include?('*')
23
+ regex = Operations::Utils.parse_to_regex(rule)
24
+ value_ok = regex === value
25
+ end
26
+ end
27
+ value_ok
28
+ end
29
+
30
+ def get_operation(controller, action)
31
+ result = Operations::Config.enforcements.select do |rule|
32
+ check_for_pattern(rule[:controller], controller) && check_for_pattern(rule[:action], action)
33
+ end
34
+ rule = result[0]
35
+ return default_operation if rule.nil?
36
+ Operations.from_string(rule[:operation])
37
+ end
38
+
39
+ def enforce(controller, action, user)
40
+ operation = get_operation(controller, action)
41
+
42
+ if operation == :nobody
43
+ raise Operations::Errors::NotAuthorizedError.new(operation, 'no one is allowed to execute this action!')
44
+ end
45
+
46
+ return if operation == :all
47
+
48
+ if user.nil?
49
+ # Check if we are not already on the sign in page
50
+ sign_in_path = Operations::Config.get_sign_in_path
51
+ if sign_in_path && sign_in_path[:controller] && sign_in_path[:action]
52
+ return if sign_in_path[:controller].to_s == controller.to_s \
53
+ && sign_in_path[:action].to_s == action.to_s
54
+ end
55
+
56
+ # Case 1: There is no user and the operation was found
57
+ raise Operations::Errors::NotLoggedInError.new(operation)
58
+ end
59
+
60
+ # Case 2: The operation was found
61
+ if operation
62
+ unless operation.accepts_scope? user.named_scope
63
+ raise Operations::Errors::NotAuthorizedError.new(operation, 'insufficient privileges')
64
+ else
65
+ warn "Access granted for User##{user.id} (#{controller}:#{action})"; return
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ module Operations
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Operations
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require "operations/errors/base_exception"
2
+
3
+ module Operations
4
+ module Errors
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ module Operations
2
+ module Errors
3
+ class BaseException < StandardError
4
+ end
5
+ end
6
+ end
7
+
8
+ require "operations/errors/not_implemented_error"
9
+ require "operations/errors/invalid_operation_error"
10
+ require "operations/errors/invalid_field_error"
11
+ require "operations/errors/not_authorized_error"
12
+ require "operations/errors/not_logged_in_error"
@@ -0,0 +1,9 @@
1
+ module Operations
2
+ module Errors
3
+ class InvalidFieldError < BaseException
4
+ def initialize(missing_method, klass, expected_class)
5
+ super("The field #{missing_method} for class #{klass} is not a #{expected_class}.")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module Operations
2
+ module Errors
3
+ class InvalidOperationError < BaseException
4
+ def initialize(operation, msg=nil)
5
+ if operation.class == Operations::Operation
6
+ cause = "Cause:"
7
+ has_cause = false
8
+ unless operation.has_valid_name?
9
+ cause += " invalid name"
10
+ has_cause = true
11
+ end
12
+ unless operation.has_valid_scope?
13
+ cause += " invalid scope `#{operation.invalid_scope}'"
14
+ has_cause = true
15
+ end
16
+ cause "Unknown cause." unless has_cause
17
+ message = "The operation #{operation} is not valid. #{cause}"
18
+ else
19
+ message = "Invalid operation object. Cause: `#{operation}'."
20
+ end
21
+ message += " Additional info: #{msg}" if msg
22
+ super(message)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Operations
2
+ module Errors
3
+ class NotAuthorizedError < BaseException
4
+ def initialize(operation=nil, msg=nil)
5
+ cause = msg || "unknown"
6
+ operation_name = operation ? operation.name : 'insufficient privileges'
7
+ super("Operation access denied: #{operation_name}. Cause: #{cause}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Operations
2
+ module Errors
3
+ class NotImplementedError < BaseException
4
+ def initialize(klass, missing_method)
5
+ super("The class #{klass} does not implement the method #{missing_method}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Operations
2
+ module Errors
3
+ class NotLoggedInError < BaseException
4
+ def initialize(operation=nil)
5
+ operation_name = operation.nil? ? 'unknown' : (operation.description || operation.name)
6
+ super("You must log in before running this action. Attempted operation `#{operation_name}'.")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,167 @@
1
+ module Operations
2
+ class Operation
3
+ OperationValue = Struct.new :name, :scope, :description
4
+
5
+ def initialize(**options, &block)
6
+ if block_given?
7
+ yield values
8
+ else
9
+ options.each do |key, value|
10
+ if values.respond_to?(key)
11
+ values.send(key.to_s + '=', value)
12
+ end
13
+ end
14
+ end
15
+ ensure_operation_is_valid!
16
+ self
17
+ end
18
+
19
+ def values
20
+ @values ||= OperationValue.new(nil, :nobody)
21
+ end
22
+
23
+ def name
24
+ has_valid_name? ? values.name : 'N/A (Invalid operation name)'
25
+ end
26
+
27
+ def description
28
+ values.description
29
+ end
30
+
31
+ def users
32
+ return User.where(id: []) if scope == :nobody
33
+ return User.all if scope == :all
34
+ @users ||= Operations.users_acting_as(scope)
35
+ end
36
+
37
+ # def user
38
+ # return nil if values.user_id.nil?
39
+ # @user ||= User.find_by(id: values.user_id)
40
+ # end
41
+
42
+ # def save
43
+ # return false if user.nil?
44
+ # ensure_operation_is_valid!("Cannot save user #{user}")
45
+ # result = user.update_attribute :operations, self.to_json
46
+ # end
47
+
48
+ def is_valid?
49
+ has_valid_name? && has_valid_scope? # && !user.nil?
50
+ end
51
+
52
+ def has_valid_name?
53
+ Operations::Config.operation_name_regex === values.name
54
+ end
55
+
56
+ def has_valid_scope?
57
+ Operations::Config.operation_scope_regex === scope
58
+ end
59
+
60
+ def invalid_scope
61
+ return scope unless has_valid_scope?
62
+ end
63
+
64
+ def is_scope?(scope_name)
65
+ scope.to_s == scope_name.to_s
66
+ end
67
+
68
+ def accepts_scope?(scope_name)
69
+ scope_name = scope_name.to_sym
70
+ return true if scope == :all
71
+ return false if scope == :nobody
72
+ return true if is_scope?(scope_name)
73
+ Operations.allowed_named_roles_for(scope).include?(scope_name)
74
+ end
75
+
76
+ def allowed_roles
77
+ Operations.user_roles
78
+ end
79
+
80
+ def denied_roles
81
+ end
82
+
83
+ def as_json(options={})
84
+ additional_hash = {uuid: uuid, users_count: users.count}
85
+ if (methods = options[:methods])
86
+ if methods.class != Array
87
+ methods = [methods]
88
+ end
89
+ methods.each do |method|
90
+ begin
91
+ additional_hash[method] = send(method)
92
+ rescue NoMethodError
93
+ # ignore
94
+ next
95
+ end
96
+ end
97
+ end
98
+ values
99
+ .as_json(options)
100
+ .merge(additional_hash)
101
+ .as_json
102
+ end
103
+
104
+ def uuid
105
+ alg = Operations::Config.operation_uuid_algorithm
106
+ return Operations::Errors::BaseException("The UUID Algorithm has be a class") unless alg.class == Class
107
+ alg.new(name.to_s).to_s
108
+ end
109
+
110
+ def to_json
111
+ as_json.to_json
112
+ end
113
+
114
+ def inspect
115
+ "\#<#{self.class.name} `#{self.name}' allowed for `#{scope}'>"
116
+ end
117
+
118
+ def ==(sc_op)
119
+ if sc_op.class == self.class
120
+ sc_op.name == name && sc_op.uuid == uuid
121
+ else
122
+ is_scope?(sc_op)
123
+ end
124
+ end
125
+
126
+ def >(sc_op)
127
+ if sc_op.class == self.class
128
+ return false if self == sc_op
129
+ return !sc_op.accepts_scope?(scope)
130
+ elsif sc_op.class == Symbol
131
+ accepts_scope?(sc_op)
132
+ else
133
+ false
134
+ end
135
+ end
136
+
137
+ def <(sc_op)
138
+ if sc_op.class == self.class
139
+ return false if self == sc_op
140
+ return sc_op.accepts_scope?(scope)
141
+ elsif sc_op.class == Symbol
142
+ !accepts_scope?(sc_op)
143
+ else
144
+ false
145
+ end
146
+ end
147
+
148
+ def >=(sc_op)
149
+ self == sc_op || self > sc_op
150
+ end
151
+
152
+ def <=(sc_op)
153
+ self == sc_op || self < sc_op
154
+ end
155
+
156
+ private
157
+
158
+ def ensure_operation_is_valid!(msg=nil)
159
+ raise Operations::Errors::InvalidOperationError.new(self, nil) unless is_valid?
160
+ end
161
+
162
+ # Hide the scope for security reasons
163
+ def scope
164
+ self.values.scope || :nobody
165
+ end
166
+ end
167
+ end