api_engine_base 0.1.1 → 0.2.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 +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/inbox/message_blast_controller.rb +89 -0
- data/app/controllers/api_engine_base/inbox/message_controller.rb +79 -0
- 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/message.rb +30 -0
- data/app/models/message_blast.rb +27 -0
- data/app/models/user.rb +15 -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/inbox_service/blast/delete.rb +23 -0
- data/app/services/api_engine_base/inbox_service/blast/metadata.rb +26 -0
- data/app/services/api_engine_base/inbox_service/blast/new_user_blaster.rb +24 -0
- data/app/services/api_engine_base/inbox_service/blast/retrieve.rb +30 -0
- data/app/services/api_engine_base/inbox_service/blast/upsert.rb +67 -0
- data/app/services/api_engine_base/inbox_service/message/metadata.rb +35 -0
- data/app/services/api_engine_base/inbox_service/message/modify.rb +44 -0
- data/app/services/api_engine_base/inbox_service/message/retrieve.rb +36 -0
- data/app/services/api_engine_base/inbox_service/message/send.rb +33 -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/login_strategy/plain_text/create.rb +1 -0
- 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 +32 -0
- data/db/migrate/20241117043720_create_api_engine_base_users.rb +2 -0
- data/db/migrate/20250223023306_create_api_engine_base_messages.rb +12 -0
- data/db/migrate/20250223023313_create_api_engine_base_message_blasts.rb +14 -0
- data/lib/api_engine_base/authorization/default.yml +42 -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/inbox/blast_request.rb +15 -0
- data/lib/api_engine_base/schema/inbox/blast_response.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_blast_entity.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_blast_metadata.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_entity.rb +14 -0
- data/lib/api_engine_base/schema/inbox/metadata.rb +18 -0
- data/lib/api_engine_base/schema/inbox/modified.rb +13 -0
- 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 +13 -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 +44 -5
- data/app/controllers/concerns/api_engine_base/schematizable.rb +0 -5
@@ -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,36 @@ 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 "inbox" do
|
30
|
+
scope "messages" do
|
31
|
+
get "/", to: "api_engine_base/inbox/message#metadata", as: :"#{append_to_ass}_inbox_metadata"
|
32
|
+
|
33
|
+
get "/:id", to: "api_engine_base/inbox/message#message", as: :"#{append_to_ass}_inbox_message"
|
34
|
+
delete "/:id", to: "api_engine_base/inbox/message#delete", as: :"#{append_to_ass}_inbox_message_del"
|
35
|
+
|
36
|
+
post "/ack", to: "api_engine_base/inbox/message#ack", as: :"#{append_to_ass}_inbox_ack"
|
37
|
+
post "/delete", to: "api_engine_base/inbox/message#delete", as: :"#{append_to_ass}_inbox_delete"
|
38
|
+
end
|
39
|
+
|
40
|
+
scope "blast" do
|
41
|
+
get "/", to: "api_engine_base/inbox/message_blast#metadata", as: :"#{append_to_ass}_blast_metadata"
|
42
|
+
post "/", to: "api_engine_base/inbox/message_blast#create", as: :"#{append_to_ass}_blast_create"
|
43
|
+
|
44
|
+
get "/:id", to: "api_engine_base/inbox/message_blast#blast", as: :"#{append_to_ass}_blast_blast"
|
45
|
+
patch "/:id", to: "api_engine_base/inbox/message_blast#modify", as: :"#{append_to_ass}_blast_modify"
|
46
|
+
delete "/:id", to: "api_engine_base/inbox/message_blast#delete", as: :"#{append_to_ass}_blast_delete"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
scope "admin" do
|
51
|
+
get "/", to: "api_engine_base/admin#show", as: :"#{append_to_ass}_admin_show_get"
|
52
|
+
post "/modify", to: "api_engine_base/admin#modify", as: :"#{append_to_ass}_admin_modify_post"
|
53
|
+
post "/modify/role", to: "api_engine_base/admin#modify_role", as: :"#{append_to_ass}_admin_modify_role_post"
|
54
|
+
end
|
23
55
|
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,12 @@
|
|
1
|
+
class CreateApiEngineBaseMessages < ActiveRecord::Migration[7.2]
|
2
|
+
def change
|
3
|
+
create_table :messages do |t|
|
4
|
+
t.timestamps
|
5
|
+
t.references :user, null: false, foreign_key: true # Required
|
6
|
+
t.text :text
|
7
|
+
t.string :title
|
8
|
+
t.boolean :viewed, default: false
|
9
|
+
t.boolean :pushed, default: false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateApiEngineBaseMessageBlasts < ActiveRecord::Migration[7.2]
|
2
|
+
def change
|
3
|
+
create_table :message_blasts do |t|
|
4
|
+
t.timestamps
|
5
|
+
t.references :user, null: false, foreign_key: true # Required
|
6
|
+
t.text :text
|
7
|
+
t.string :title
|
8
|
+
t.boolean :existing_users, default: false
|
9
|
+
t.boolean :new_users, default: false
|
10
|
+
end
|
11
|
+
|
12
|
+
add_reference :messages, :message_blast, foreign_key: true, null: true
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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
|
+
- message-blast
|
13
|
+
admin-without-impersonation:
|
14
|
+
description: |
|
15
|
+
This group defines permissions for Admin Read and Write operations. Users with this role will have
|
16
|
+
the ability to view and update other users states. However, impersonation is not permitted with this role
|
17
|
+
entities:
|
18
|
+
- admin-without-impersonate
|
19
|
+
- message-blast
|
20
|
+
admin-read-only:
|
21
|
+
description: |
|
22
|
+
This group defines permissions for Admin Read interface only.
|
23
|
+
entities:
|
24
|
+
- read-admin
|
25
|
+
- message-blast-read-only
|
26
|
+
entities:
|
27
|
+
- name: message-blast
|
28
|
+
controller: ApiEngineBase::Inbox::MessageBlastController
|
29
|
+
- name: message-blast-read-only
|
30
|
+
controller: ApiEngineBase::Inbox::MessageBlastController
|
31
|
+
only: metadata
|
32
|
+
- name: read-admin
|
33
|
+
controller: ApiEngineBase::AdminController
|
34
|
+
only: show
|
35
|
+
- name: admin
|
36
|
+
controller: ApiEngineBase::AdminController
|
37
|
+
- name: admin-without-impersonate
|
38
|
+
controller: ApiEngineBase::AdminController
|
39
|
+
except: impersonate
|
40
|
+
|
41
|
+
|
42
|
+
|
@@ -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
|
@@ -9,6 +9,9 @@ require "api_engine_base/configuration/login/config"
|
|
9
9
|
require "api_engine_base/configuration/otp/config"
|
10
10
|
require "api_engine_base/configuration/username/config"
|
11
11
|
require "api_engine_base/configuration/application/config"
|
12
|
+
require "api_engine_base/configuration/admin/config"
|
13
|
+
require "api_engine_base/configuration/authorization/config"
|
14
|
+
require "api_engine_base/configuration/user/config"
|
12
15
|
|
13
16
|
module ApiEngineBase
|
14
17
|
module Configuration
|
@@ -48,9 +51,24 @@ module ApiEngineBase
|
|
48
51
|
# allow shorthand to be used
|
49
52
|
alias_method :app, :application
|
50
53
|
|
54
|
+
add_composer :authorization,
|
55
|
+
desc: "Authorization via rbac configurations",
|
56
|
+
allowed: Configuration::Authorization::Config,
|
57
|
+
default: Configuration::Authorization::Config.new
|
58
|
+
|
59
|
+
add_composer :user,
|
60
|
+
desc: "User configuration for the app. Includes what to display and what attributes can be changed",
|
61
|
+
allowed: Configuration::User::Config,
|
62
|
+
default: Configuration::User::Config.new
|
63
|
+
|
64
|
+
add_composer :admin,
|
65
|
+
desc: "Admin configuration for the app",
|
66
|
+
allowed: Configuration::Admin::Config,
|
67
|
+
default: Configuration::Admin::Config.new
|
68
|
+
|
51
69
|
# To be Deleted
|
52
70
|
add_composer :otp,
|
53
|
-
desc: "One Time Password
|
71
|
+
desc: "One Time Password generation is used for ease in quickly validating a users actions. This is good for short term validation requirements as opposed to UserSecrets",
|
54
72
|
allowed: Configuration::Otp::Config,
|
55
73
|
default: Configuration::Otp::Config.new
|
56
74
|
end
|