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,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