api_engine_base 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +37 -6
- data/app/controllers/api_engine_base/admin_controller.rb +104 -0
- data/app/controllers/api_engine_base/application_controller.rb +45 -11
- data/app/controllers/api_engine_base/auth/plain_text_controller.rb +1 -1
- data/app/controllers/api_engine_base/user_controller.rb +49 -0
- data/app/models/api_engine_base/application_record.rb +38 -0
- data/app/models/user.rb +13 -4
- data/app/services/api_engine_base/README.md +49 -0
- data/app/services/api_engine_base/argument_validation/README.md +192 -0
- data/app/services/api_engine_base/argument_validation/class_methods.rb +2 -3
- data/app/services/api_engine_base/argument_validation/instance_methods.rb +13 -1
- data/app/services/api_engine_base/authorize/validate.rb +49 -0
- data/app/services/api_engine_base/jwt/authenticate_user.rb +22 -7
- data/app/services/api_engine_base/jwt/login_create.rb +1 -1
- data/app/services/api_engine_base/service_base.rb +4 -5
- data/app/services/api_engine_base/user_attributes/modify.rb +68 -0
- data/app/services/api_engine_base/user_attributes/roles.rb +27 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20241117043720_create_api_engine_base_users.rb +2 -0
- data/lib/api_engine_base/authorization/default.yml +34 -0
- data/lib/api_engine_base/authorization/entity.rb +101 -0
- data/lib/api_engine_base/authorization/role.rb +101 -0
- data/lib/api_engine_base/authorization.rb +85 -0
- data/lib/api_engine_base/configuration/admin/config.rb +18 -0
- data/lib/api_engine_base/configuration/application/config.rb +2 -2
- data/lib/api_engine_base/configuration/authorization/config.rb +24 -0
- data/lib/api_engine_base/configuration/config.rb +19 -1
- data/lib/api_engine_base/configuration/user/config.rb +56 -0
- data/lib/api_engine_base/engine.rb +38 -6
- data/lib/api_engine_base/error.rb +5 -0
- data/lib/api_engine_base/schema/admin/users.rb +15 -0
- data/lib/api_engine_base/schema/error/invalid_argument_response.rb +1 -1
- data/lib/api_engine_base/schema/page.rb +14 -0
- data/lib/api_engine_base/schema/user.rb +28 -0
- data/lib/api_engine_base/schema.rb +5 -0
- data/lib/api_engine_base/spec_helper.rb +4 -3
- data/lib/api_engine_base/version.rb +1 -1
- data/lib/api_engine_base.rb +2 -2
- metadata +22 -4
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase::Authorize
|
4
|
+
class Validate < ApiEngineBase::ServiceBase
|
5
|
+
on_argument_validation :fail_early
|
6
|
+
|
7
|
+
validate :user, is_a: User, required: true
|
8
|
+
validate :controller, is_a: [ActionController::Base, ActionController::API], required: true
|
9
|
+
validate :method, is_a: String, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
context.authorization_required = authorization_required?
|
13
|
+
unless context.authorization_required
|
14
|
+
log_info("controller:#{controller}; method:#{method} -- No Authorization required")
|
15
|
+
context.msg = "Authorization not required at this time"
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
# At this point we know authorization on the route is required
|
20
|
+
# Iterate through the users roles to find a matching role that allows authorization
|
21
|
+
# If at least 1 of the users roles passes validation, we can allow access to the path
|
22
|
+
log_info("User Roles: #{user.roles}")
|
23
|
+
auhorization_result = user_role_objects.any? do |_role_name, role_object|
|
24
|
+
result = role_object.authorized?(controller:, method:, user:)
|
25
|
+
log_info("Role:#{result[:role]};Authorized:[#{result[:authorized]}];Reason:[#{result[:reason]}]")
|
26
|
+
result[:authorized] == true
|
27
|
+
end
|
28
|
+
|
29
|
+
if auhorization_result
|
30
|
+
context.msg = "User is Authorized for action"
|
31
|
+
else
|
32
|
+
context.fail!(msg: "Unauthorized Access. Incorrect User Privileges")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def authorization_required?
|
37
|
+
controller_mapping = ApiEngineBase::Authorization.mapped_controllers[controller]
|
38
|
+
return false if controller_mapping.nil?
|
39
|
+
|
40
|
+
controller_mapping.include?(method.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def user_role_objects
|
44
|
+
ApiEngineBase::Authorization::Role.roles.select do |role_name, _|
|
45
|
+
user.roles.include?(role_name.to_s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -5,6 +5,7 @@ module ApiEngineBase::Jwt
|
|
5
5
|
|
6
6
|
validate :token, is_a: String, required: true, sensitive: true
|
7
7
|
validate :bypass_email_validation, is_one: [true, false], default: false
|
8
|
+
validate :with_reset, is_one: [true, false], default: false
|
8
9
|
|
9
10
|
def call
|
10
11
|
result = Decode.(token:)
|
@@ -14,7 +15,7 @@ module ApiEngineBase::Jwt
|
|
14
15
|
end
|
15
16
|
payload = result.payload
|
16
17
|
|
17
|
-
|
18
|
+
expires_at = validate_generated_at!(generated_at: payload[:generated_at])
|
18
19
|
|
19
20
|
user = User.find(payload[:user_id]) rescue nil
|
20
21
|
if user.nil?
|
@@ -29,25 +30,39 @@ module ApiEngineBase::Jwt
|
|
29
30
|
end
|
30
31
|
|
31
32
|
email_validation_required!(user:)
|
33
|
+
|
34
|
+
if with_reset
|
35
|
+
context.generated_token = ApiEngineBase::Jwt::LoginCreate.(user:).token
|
36
|
+
expires_at = ApiEngineBase.config.jwt.ttl.from_now.to_time
|
37
|
+
end
|
38
|
+
|
39
|
+
context.expires_at = expires_at.to_s
|
32
40
|
end
|
33
41
|
|
34
|
-
def
|
35
|
-
if
|
36
|
-
log_warn("
|
42
|
+
def validate_generated_at!(generated_at:)
|
43
|
+
if generated_at.nil?
|
44
|
+
log_warn("generated_at payload is missing from the JWT token. Cannot continue")
|
37
45
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
38
46
|
end
|
39
47
|
|
40
|
-
expires_time =
|
48
|
+
expires_time = begin
|
49
|
+
time = Time.at(generated_at)
|
50
|
+
time + ApiEngineBase.config.jwt.ttl
|
51
|
+
rescue
|
52
|
+
nil
|
53
|
+
end
|
41
54
|
|
42
55
|
if expires_time.nil?
|
43
|
-
log_warn("
|
56
|
+
log_warn("generated_at payload cannot be parsed. Cannot continue")
|
44
57
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
45
58
|
end
|
46
59
|
|
47
60
|
if expires_time < Time.now
|
48
|
-
log_warn("
|
61
|
+
log_warn("generated_at is no longer valid. Must request new token")
|
49
62
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
50
63
|
end
|
64
|
+
|
65
|
+
expires_time
|
51
66
|
end
|
52
67
|
|
53
68
|
def email_validation_required!(user:)
|
@@ -41,9 +41,8 @@ class ApiEngineBase::ServiceBase
|
|
41
41
|
def internal_validate(interactor)
|
42
42
|
# call validate that is overridden from child
|
43
43
|
begin
|
44
|
-
validate!
|
45
|
-
#
|
46
|
-
run_validations!
|
44
|
+
validate! # custom validations defined on the child class
|
45
|
+
run_validations! # ArgumentValidation's based on defined settings on child
|
47
46
|
rescue StandardError => e
|
48
47
|
log_error("Error during validation. #{e.message}")
|
49
48
|
raise
|
@@ -72,8 +71,8 @@ class ApiEngineBase::ServiceBase
|
|
72
71
|
|
73
72
|
# Re-raise to let the core Interactor handle this
|
74
73
|
raise
|
75
|
-
# Capture exception explicitly
|
76
|
-
rescue ::Exception => e
|
74
|
+
# Capture exception explicitly for logging purposes, then reraise
|
75
|
+
rescue ::Exception => e
|
77
76
|
# set status for use in ensure block
|
78
77
|
status = :error
|
79
78
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase::UserAttributes
|
4
|
+
class Modify < ApiEngineBase::ServiceBase
|
5
|
+
on_argument_validation :fail_early
|
6
|
+
DEFAULT = {
|
7
|
+
verifier_token: [true, false]
|
8
|
+
}
|
9
|
+
validate :user, is_a: User, required: true
|
10
|
+
validate :admin_user, is_a: User, required: false
|
11
|
+
|
12
|
+
# Gets assigned during configuration phase via
|
13
|
+
# lib/api_engine_base/configuration/user/config.rb
|
14
|
+
def self.assign!
|
15
|
+
attributes = ApiEngineBase.config.user.default_attributes_for_change + ApiEngineBase.config.user.additional_attributes_for_change
|
16
|
+
one_of(:modify_attribute, required: true) do
|
17
|
+
attributes.uniq.each do |attribute|
|
18
|
+
if metadata = User.attribute_to_type_mapping[attribute]
|
19
|
+
arguments = {}
|
20
|
+
if default = DEFAULT[attribute.to_sym]
|
21
|
+
arguments[:is_one] = default
|
22
|
+
else
|
23
|
+
if allowed_types = metadata[:allowed_types]
|
24
|
+
arguments[:is_one] = allowed_types
|
25
|
+
else
|
26
|
+
arguments[:is_a] = metadata[:ruby_type]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
validate(attribute, **arguments)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
case modify_attribute_key
|
38
|
+
when :email
|
39
|
+
unless email =~ URI::MailTo::EMAIL_REGEXP
|
40
|
+
inline_argument_failure!(errors: { email: "Invalid email address" })
|
41
|
+
end
|
42
|
+
when :username
|
43
|
+
username_validity = ApiEngineBase::Username::Available.(username:)
|
44
|
+
unless username_validity.valid
|
45
|
+
inline_argument_failure!(errors: { username: "Username is invalid. #{ApiEngineBase.config.username.username_failure_message}" })
|
46
|
+
end
|
47
|
+
when :verifier_token
|
48
|
+
if verifier_token
|
49
|
+
verifier_token!
|
50
|
+
else
|
51
|
+
inline_argument_failure!(errors: { verifier_token: "verifier_token is invalid. Expected [true] when value present" })
|
52
|
+
end
|
53
|
+
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
update!
|
58
|
+
end
|
59
|
+
|
60
|
+
def verifier_token!
|
61
|
+
user.reset_verifier_token!
|
62
|
+
end
|
63
|
+
|
64
|
+
def update!
|
65
|
+
user.update!(modify_attribute_key => modify_attribute)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase::UserAttributes
|
4
|
+
class Roles < ApiEngineBase::ServiceBase
|
5
|
+
on_argument_validation :fail_early
|
6
|
+
|
7
|
+
validate :user, is_a: User, required: true
|
8
|
+
validate :admin_user, is_a: User, required: true
|
9
|
+
validate :roles, is_a: Array, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
if valid_roles?
|
13
|
+
user.update!(roles:)
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
|
17
|
+
inline_argument_failure!(errors: { roles: "Invalid roles provided" })
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_roles?
|
21
|
+
return true if roles.empty?
|
22
|
+
|
23
|
+
available_roles = ApiEngineBase::Authorization::Role.roles.keys
|
24
|
+
roles.all? { available_roles.include?(_1) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/config/routes.rb
CHANGED
@@ -20,4 +20,15 @@ Rails.application.routes.draw do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
scope "user" do
|
25
|
+
get "/", to: "api_engine_base/user#show", as: :"#{append_to_ass}_user_show_get"
|
26
|
+
post "/modify", to: "api_engine_base/user#modify", as: :"#{append_to_ass}_user_modify_post"
|
27
|
+
end
|
28
|
+
|
29
|
+
scope "admin" do
|
30
|
+
get "/", to: "api_engine_base/admin#show", as: :"#{append_to_ass}_admin_show_get"
|
31
|
+
post "/modify", to: "api_engine_base/admin#modify", as: :"#{append_to_ass}_admin_modify_post"
|
32
|
+
post "/modify/role", to: "api_engine_base/admin#modify_role", as: :"#{append_to_ass}_admin_modify_role_post"
|
33
|
+
end
|
23
34
|
end
|
@@ -12,6 +12,8 @@ class CreateApiEngineBaseUsers < ActiveRecord::Migration[7.2]
|
|
12
12
|
t.string :last_login_strategy
|
13
13
|
t.datetime :last_login
|
14
14
|
|
15
|
+
t.string :roles, default: ""
|
16
|
+
|
15
17
|
###
|
16
18
|
# Database token to verify JWT
|
17
19
|
# Token will allow JWT values to expire/reset all devices
|
@@ -0,0 +1,34 @@
|
|
1
|
+
---
|
2
|
+
groups:
|
3
|
+
owner:
|
4
|
+
description: The owner of the application will have full access to all components
|
5
|
+
entities: true
|
6
|
+
admin:
|
7
|
+
description: |
|
8
|
+
This group defines permissions for Admin Read and Write operations. Users with this role will have
|
9
|
+
the ability to view and update other users states.
|
10
|
+
entities:
|
11
|
+
- admin
|
12
|
+
admin-without-impersonation:
|
13
|
+
description: |
|
14
|
+
This group defines permissions for Admin Read and Write operations. Users with this role will have
|
15
|
+
the ability to view and update other users states. However, impersonation is not permitted with this role
|
16
|
+
entities:
|
17
|
+
- admin-without-impersonate
|
18
|
+
admin-read-only:
|
19
|
+
description: |
|
20
|
+
This group defines permissions for Admin Read interface only.
|
21
|
+
entities:
|
22
|
+
- read-admin
|
23
|
+
entities:
|
24
|
+
- name: read-admin
|
25
|
+
controller: ApiEngineBase::AdminController
|
26
|
+
only: show
|
27
|
+
- name: admin
|
28
|
+
controller: ApiEngineBase::AdminController
|
29
|
+
- name: admin-without-impersonate
|
30
|
+
controller: ApiEngineBase::AdminController
|
31
|
+
except: impersonate
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
module Authorization
|
5
|
+
class Entity
|
6
|
+
class << self
|
7
|
+
def create_entity(name:, controller:, only: nil, except: nil)
|
8
|
+
if entities[name]
|
9
|
+
Rails.logger.warn("Warning: Authorization entity #{name} duplicated. Only the most recent one will persist")
|
10
|
+
end
|
11
|
+
|
12
|
+
entities[name] = new(name:, controller:, only:, except:)
|
13
|
+
|
14
|
+
entities[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def entities
|
18
|
+
@entities ||= ActiveSupport::HashWithIndifferentAccess.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def entities_reset!
|
22
|
+
@entities = ActiveSupport::HashWithIndifferentAccess.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :name, :controller, :only, :except
|
27
|
+
def initialize(name:, controller:, only: nil, except: nil)
|
28
|
+
@controller = controller
|
29
|
+
@except = except.nil? ? nil : Array(except).map(&:to_sym)
|
30
|
+
@only = only.nil? ? nil : Array(only).map(&:to_sym)
|
31
|
+
|
32
|
+
validate!
|
33
|
+
end
|
34
|
+
|
35
|
+
def humanize
|
36
|
+
"name:[#{name}]; controller:[#{controller}]; only:[#{only}]; except:[#{except}]"
|
37
|
+
end
|
38
|
+
|
39
|
+
# controller will be the class object
|
40
|
+
# method will be the string of the route method
|
41
|
+
def matches?(controller:, method:)
|
42
|
+
# Return early if the controller does not match the existing entity controller
|
43
|
+
return nil if @controller != controller
|
44
|
+
|
45
|
+
# We are in the correct controller
|
46
|
+
|
47
|
+
# if inclusions are not present, the check is on the entire contoller and we can return true
|
48
|
+
if only.nil? && except.nil?
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
|
52
|
+
## `only` or `except` is present at this point
|
53
|
+
if only
|
54
|
+
# If method is included in only, accept otherwise return reject
|
55
|
+
return only.include?(method.to_sym)
|
56
|
+
else
|
57
|
+
# If method is included in except, reject otherwise return accept
|
58
|
+
return !except.include?(method.to_sym)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# This is a custom method that can get overridden by a child class for custom
|
63
|
+
# authorization logic beyond grouping
|
64
|
+
def authorized?(user:)
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def validate!
|
71
|
+
if @only && @except
|
72
|
+
raise Error, "kwargs `only` and `except` passed in. At most 1 can be passed in."
|
73
|
+
end
|
74
|
+
|
75
|
+
validate_controller!
|
76
|
+
validate_methods!(@only, :only)
|
77
|
+
validate_methods!(@except, :except)
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_controller!
|
81
|
+
return true if Class === @controller
|
82
|
+
|
83
|
+
@controller = @controller.constantize
|
84
|
+
rescue NameError => e
|
85
|
+
raise Error, "Controller [#{@controller}] was not found. Please validate spelling or ensure it is loaded earlier"
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_methods!(array_of_methods, string)
|
89
|
+
return if array_of_methods.nil?
|
90
|
+
|
91
|
+
missing_methods = array_of_methods.select do |method|
|
92
|
+
!@controller.instance_methods.include?(method)
|
93
|
+
end
|
94
|
+
|
95
|
+
return true if missing_methods.empty?
|
96
|
+
|
97
|
+
raise Error, "#{string} parameter is invalid. Controller [#{@controller}] is missing methods:[#{missing_methods}]"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
module Authorization
|
5
|
+
class Role
|
6
|
+
class << self
|
7
|
+
def create_role(name:, description:, entities: nil, allow_everything: false)
|
8
|
+
if roles[name]
|
9
|
+
raise Error, "Role [#{name}] already exists. Must use different name"
|
10
|
+
end
|
11
|
+
|
12
|
+
if allow_everything
|
13
|
+
Rails.logger.info { "Authorization Role: #{name} is granted authorization to all roles" }
|
14
|
+
else
|
15
|
+
unless Array(entities).all? { Entity === _1 }
|
16
|
+
raise Error, "Parameter :entities must include objects of or inherited by ApiEngineBase::Authorization::Entity"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
roles[name] = new(name:, description:, entities:, allow_everything:)
|
21
|
+
# A role is `intended` to be immutable (attr_reader)
|
22
|
+
# Once the role is defined it will not get changed
|
23
|
+
# After it is created, add the mapping to the source of truth list of mapped method names to their controllers
|
24
|
+
ApiEngineBase::Authorization.add_mapping!(role: roles[name])
|
25
|
+
|
26
|
+
roles[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def roles
|
30
|
+
@roles ||= ActiveSupport::HashWithIndifferentAccess.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def roles_reset!
|
34
|
+
@roles = ActiveSupport::HashWithIndifferentAccess.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :entities, :name, :description, :allow_everything
|
39
|
+
def initialize(name:, description:, entities:, allow_everything: false)
|
40
|
+
@name = name
|
41
|
+
@entities = Array(entities)
|
42
|
+
@description = description
|
43
|
+
@allow_everything = allow_everything
|
44
|
+
end
|
45
|
+
|
46
|
+
def authorized?(controller:, method:, user:)
|
47
|
+
return_value = { role: name, description: }
|
48
|
+
return return_value.merge(authorized: true, reason: "#{name} allows all authorizations") if allow_everything
|
49
|
+
|
50
|
+
matched_controllers = controller_entity_mapping[controller]
|
51
|
+
# if Role does not match any of the controllers
|
52
|
+
# explicitly return nil here to ensure upstream knows this role does not care about the route
|
53
|
+
return return_value.merge(authorized: nil, reason: "#{name} does not match") if matched_controllers.nil?
|
54
|
+
|
55
|
+
rejected_entities = matched_controllers.map do |entity|
|
56
|
+
case entity.matches?(controller:, method:)
|
57
|
+
when false, nil
|
58
|
+
{ authorized: false, entity: entity.name, controller:, readable: entity.humanize, status: "Rejected by inclusion" }
|
59
|
+
when true
|
60
|
+
# Entity matches all inclusions
|
61
|
+
if entity.authorized?(user:)
|
62
|
+
# Do nothing! Entity has authorized the user
|
63
|
+
else
|
64
|
+
{ authorized: false, entity: entity.name, controller:, readable: entity.humanize, status: "Rejected via custom Entity Authorization" }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end.compact
|
68
|
+
|
69
|
+
# If there were no entities that rejected authorization, return authorized
|
70
|
+
return return_value.merge(authorized: true, reason: "All entities approve authorization") if rejected_entities.empty?
|
71
|
+
|
72
|
+
return_value.merge(authorized: false, reason: "Subset of Entities Rejected authorization", rejected_entities:)
|
73
|
+
end
|
74
|
+
|
75
|
+
def guards
|
76
|
+
mapping = {}
|
77
|
+
controller_entity_mapping.each do |controller, entities|
|
78
|
+
mapping[controller] ||= []
|
79
|
+
entities.map do |entity|
|
80
|
+
if entity.only
|
81
|
+
# We only care about these methods on the controller
|
82
|
+
mapping[controller] += entity.only
|
83
|
+
elsif entity.except
|
84
|
+
# We care about all methods on the controller except these
|
85
|
+
mapping[controller] += controller.instance_methods(false) - entity.except
|
86
|
+
else
|
87
|
+
# We care about all methods on the controller
|
88
|
+
mapping[controller] += controller.instance_methods(false)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
mapping
|
94
|
+
end
|
95
|
+
|
96
|
+
def controller_entity_mapping
|
97
|
+
@controller_entity_mapping ||= @entities.group_by(&:controller)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "set"
|
5
|
+
require "api_engine_base/error"
|
6
|
+
require "api_engine_base/authorization/entity"
|
7
|
+
require "api_engine_base/authorization/role"
|
8
|
+
|
9
|
+
module ApiEngineBase
|
10
|
+
module Authorization
|
11
|
+
module_function
|
12
|
+
|
13
|
+
class Error < ApiEngineBase::Error; end
|
14
|
+
|
15
|
+
def mapped_controllers
|
16
|
+
@mapped_controllers ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_mapping!(role:)
|
20
|
+
role.guards.each do |controller, methods|
|
21
|
+
mapped_controllers[controller] ||= Set.new
|
22
|
+
mapped_controllers[controller] += methods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def mapped_controllers_reset!
|
27
|
+
@mapped_controllers = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_defined!
|
31
|
+
provision_rbac_default!
|
32
|
+
provision_rbac_user_defined!
|
33
|
+
end
|
34
|
+
|
35
|
+
def provision_rbac_user_defined!
|
36
|
+
path = ApiEngineBase.config.authorization.rbac_group_path
|
37
|
+
rbac_configuration = load_yaml(path)
|
38
|
+
provision_rbac_via_yaml(rbac_configuration)
|
39
|
+
end
|
40
|
+
|
41
|
+
def provision_rbac_default!
|
42
|
+
path = ApiEngineBase::Engine.root.join("lib", "api_engine_base", "authorization", "default.yml")
|
43
|
+
rbac_configuration = load_yaml(path)
|
44
|
+
provision_rbac_via_yaml(rbac_configuration)
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_yaml(path)
|
48
|
+
return nil unless File.exist?(path)
|
49
|
+
|
50
|
+
YAML.load_file(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def provision_rbac_via_yaml(rbac_configuration)
|
54
|
+
return if rbac_configuration.nil?
|
55
|
+
|
56
|
+
rbac_configuration["entities"].each do |entity|
|
57
|
+
ApiEngineBase::Authorization::Entity.create_entity(
|
58
|
+
name: entity["name"],
|
59
|
+
controller: entity["controller"],
|
60
|
+
only: entity["only"],
|
61
|
+
except: entity["except"],
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
rbac_configuration["groups"].each do |name, metadata|
|
66
|
+
entities = nil
|
67
|
+
allow_everything = false
|
68
|
+
description = metadata["description"]
|
69
|
+
|
70
|
+
if metadata["entities"] == true
|
71
|
+
allow_everything = true
|
72
|
+
else
|
73
|
+
entities = ApiEngineBase::Authorization::Entity.entities.map { |k, v| v if metadata["entities"].include?(k) }.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
ApiEngineBase::Authorization::Role.create_role(
|
77
|
+
name:,
|
78
|
+
entities:,
|
79
|
+
description:,
|
80
|
+
allow_everything:,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "class_composer"
|
4
|
+
|
5
|
+
module ApiEngineBase
|
6
|
+
module Configuration
|
7
|
+
module Admin
|
8
|
+
class Config
|
9
|
+
include ClassComposer::Generator
|
10
|
+
|
11
|
+
add_composer :enable,
|
12
|
+
desc: "Allow Admin Capabilities for the application. By default, this is enabled",
|
13
|
+
allowed: [FalseClass, TrueClass],
|
14
|
+
default: true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -27,13 +27,13 @@ module ApiEngineBase
|
|
27
27
|
add_composer :port,
|
28
28
|
allowed: [String, NilClass],
|
29
29
|
default: ENV.fetch("API_ENGINE_BASE_PORT", nil),
|
30
|
-
desc: "When composing SSO's or verification URL's, this is the
|
30
|
+
desc: "When composing SSO's or verification URL's, this is the PORT for the application"
|
31
31
|
|
32
32
|
add_composer :composed_url,
|
33
33
|
allowed: String,
|
34
34
|
dynamic_default: ->(instance) { "#{instance.url}#{ ":#{instance.port}" if instance.port }" },
|
35
35
|
desc: "The fully composed URL including the port number when needed. This Config variable is not needed as it is composed of the `url` and `port` composed values",
|
36
|
-
default_shown: "Composed String of the URL and PORT. Override this with caution"
|
36
|
+
default_shown: "# Composed String of the URL and PORT. Override this with caution"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "class_composer"
|
4
|
+
|
5
|
+
module ApiEngineBase
|
6
|
+
module Configuration
|
7
|
+
module Authorization
|
8
|
+
class Config
|
9
|
+
include ClassComposer::Generator
|
10
|
+
|
11
|
+
add_composer :rbac_default_groups,
|
12
|
+
desc: "The default Group Roles defined by this engine.",
|
13
|
+
allowed: [TrueClass, FalseClass],
|
14
|
+
default: true
|
15
|
+
|
16
|
+
add_composer :rbac_group_path,
|
17
|
+
desc: "If defined, this config points to the users YAML file defining RBAC group roles.",
|
18
|
+
allowed: String,
|
19
|
+
dynamic_default: ->(_) { Rails.root.join("config","rbac_groups.yml").to_s },
|
20
|
+
default_shown: "Rails.root.join(\"config\",\"rbac_groups.yml\")"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|