command_tower 0.10.0 → 0.11.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 +7 -3
- data/app/auth/command_tower/auth/auth_context.rb +10 -4
- data/app/controllers/command_tower/admin/application_controller.rb +27 -0
- data/app/controllers/command_tower/admin/audit/events_controller.rb +61 -0
- data/app/controllers/command_tower/admin/messaging/announcements_controller.rb +1 -8
- data/app/controllers/command_tower/admin/users/identities_controller.rb +77 -0
- data/app/controllers/command_tower/admin/users/impersonation_sessions_controller.rb +33 -0
- data/app/controllers/command_tower/admin/users/roles_controller.rb +40 -0
- data/app/controllers/command_tower/admin/users_controller.rb +46 -0
- data/app/controllers/command_tower/admin/workspace_controller.rb +15 -0
- data/app/controllers/command_tower/application_controller.rb +17 -1
- data/app/controllers/command_tower/auth/impersonation_session_controller.rb +27 -0
- data/app/controllers/command_tower/auth/logout_controller.rb +3 -1
- data/app/controllers/command_tower/auth/principal_capabilities_controller.rb +19 -0
- data/app/controllers/command_tower/me/audit_events_controller.rb +59 -0
- data/app/controllers/concerns/command_tower/api/application_response_renderer.rb +5 -0
- data/app/controllers/concerns/command_tower/auth/authentication_boundary.rb +3 -2
- data/app/controllers/concerns/command_tower/execution/http_boundary.rb +65 -0
- data/app/deserializers/command_tower/deserializers/admin/scope_parameter.rb +49 -0
- data/app/deserializers/command_tower/deserializers/admin/users.rb +218 -0
- data/app/deserializers/command_tower/deserializers/audit/events.rb +224 -0
- data/app/deserializers/command_tower/deserializers/clients/types.rb +27 -0
- data/app/errors/command_tower/errors/auth/admin_unavailable_during_impersonation_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/default_membership_assignment_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/impersonation_session_expired_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/impersonation_session_missing_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/nested_impersonation_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/self_impersonation_error.rb +21 -0
- data/app/errors/command_tower/errors/continuation_exhausted_error.rb +25 -0
- data/app/jobs/command_tower/application_job.rb +7 -0
- data/app/jobs/command_tower/execution/job_boundary.rb +19 -0
- data/app/mailers/command_tower/application_mailer.rb +11 -1
- data/app/mailers/command_tower/messaging/channel_mailer.rb +1 -2
- data/app/models/command_tower/audit/event.rb +36 -0
- data/app/models/command_tower/impersonation/session.rb +51 -0
- data/app/serializers/command_tower/serializers/admin/users.rb +69 -0
- data/app/serializers/command_tower/serializers/admin/workspace/manifest_serializer.rb +58 -0
- data/app/serializers/command_tower/serializers/audit/events/filter_options_serializer.rb +44 -0
- data/app/serializers/command_tower/serializers/audit/events.rb +42 -0
- data/app/serializers/command_tower/serializers/auth/principal_capabilities_serializer.rb +15 -0
- data/app/serializers/command_tower/serializers/auth/session_response_serializer.rb +26 -2
- data/app/serializers/command_tower/serializers/impersonation/session_serializer.rb +19 -0
- data/app/services/command_tower/README.md +4 -10
- data/app/services/command_tower/authorize/validate.rb +7 -4
- data/app/services/command_tower/jwt/authenticate_user.rb +8 -2
- data/app/services/command_tower/jwt/authentication_outcome.rb +5 -4
- data/app/services/command_tower/jwt/login_create.rb +7 -4
- data/app/services/command_tower/messaging/accept/operation_logger.rb +3 -3
- data/app/services/command_tower/messaging/contract/observability/correlation.rb +3 -1
- data/app/services/command_tower/messaging/contract/observability/operation_logger.rb +3 -3
- data/app/services/command_tower/messaging/contract/observability/publisher.rb +44 -0
- data/app/services/command_tower/messaging/execution/operation_logger.rb +1 -1
- data/app/services/command_tower/messaging/handoff/operation_logger.rb +1 -1
- data/app/services/command_tower/messaging/inbox/operation_logger.rb +1 -1
- data/app/services/command_tower/messaging/planner/operation_logger.rb +1 -1
- data/app/services/command_tower/messaging/recipient_readiness/evaluate.rb +1 -1
- data/app/services/command_tower/service_base.rb +30 -43
- data/app/services/command_tower/service_logging.rb +2 -36
- data/app/services/command_tower/services/account/clear_phone.rb +8 -0
- data/app/services/command_tower/services/account/phone_verification/verify.rb +1 -0
- data/app/services/command_tower/services/account/update_phone.rb +9 -1
- data/app/services/command_tower/services/admin/users.rb +377 -0
- data/app/services/command_tower/services/admin/workspace/manifest.rb +49 -0
- data/app/services/command_tower/services/application_service.rb +37 -0
- data/app/services/command_tower/services/audit/events/filter_options.rb +75 -0
- data/app/services/command_tower/services/audit/events.rb +199 -0
- data/app/services/command_tower/services/auth/assign_default_membership_role.rb +44 -0
- data/app/services/command_tower/services/auth/authenticate_session.rb +33 -6
- data/app/services/command_tower/services/auth/email_verification/verify.rb +2 -0
- data/app/services/command_tower/services/auth/password_reset/reset.rb +13 -3
- data/app/services/command_tower/services/auth/plain_text/login.rb +9 -1
- data/app/services/command_tower/services/auth/principal_capabilities/project.rb +48 -0
- data/app/services/command_tower/services/impersonation/create.rb +29 -0
- data/app/services/command_tower/services/impersonation/end.rb +28 -0
- data/app/services/command_tower/services/impersonation/record_activity.rb +30 -0
- data/app/services/command_tower/services/impersonation/terminate_open_sessions.rb +51 -0
- data/app/services/command_tower/services/me/change_password.rb +12 -0
- data/app/shared_sequences/command_tower/shared_sequences/admin/users/resolve_scoped_user.rb +42 -0
- data/app/workflows/command_tower/transactional.rb +65 -0
- data/app/workflows/command_tower/workflows/admin/messaging/create_announcement_workflow.rb +11 -0
- data/app/workflows/command_tower/workflows/admin/scope_resolution.rb +17 -0
- data/app/workflows/command_tower/workflows/admin/users/error_mapping.rb +26 -0
- data/app/workflows/command_tower/workflows/admin/users/identity_mutation.rb +30 -0
- data/app/workflows/command_tower/workflows/admin/users/list_assignable_roles_workflow.rb +30 -0
- data/app/workflows/command_tower/workflows/admin/users/list_workflow.rb +54 -0
- data/app/workflows/command_tower/workflows/admin/users/set_email_validated_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/users/show_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/admin/users/update_email_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/users/update_name_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/admin/users/update_roles_workflow.rb +61 -0
- data/app/workflows/command_tower/workflows/admin/users/update_username_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/workspace/manifest_workflow.rb +25 -0
- data/app/workflows/command_tower/workflows/application_workflow.rb +163 -24
- data/app/workflows/command_tower/workflows/audit/error_mapping.rb +24 -0
- data/app/workflows/command_tower/workflows/audit/events/filter_options_for_admin_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/audit/events/filter_options_for_user_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/audit/events/list_for_admin_workflow.rb +74 -0
- data/app/workflows/command_tower/workflows/audit/events/list_for_user_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/audit/events/show_for_admin_workflow.rb +54 -0
- data/app/workflows/command_tower/workflows/audit/events/show_for_user_workflow.rb +42 -0
- data/app/workflows/command_tower/workflows/auth/authenticate_request_workflow.rb +3 -2
- data/app/workflows/command_tower/workflows/auth/authentication_response_effects.rb +5 -1
- data/app/workflows/command_tower/workflows/auth/logout_workflow.rb +41 -1
- data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +2 -0
- data/app/workflows/command_tower/workflows/auth/principal_capabilities/show_workflow.rb +27 -0
- data/app/workflows/command_tower/workflows/auth/register_workflow.rb +56 -24
- data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +15 -1
- data/app/workflows/command_tower/workflows/auth/session_error_status.rb +1 -0
- data/app/workflows/command_tower/workflows/impersonation/error_mapping.rb +27 -0
- data/app/workflows/command_tower/workflows/impersonation/start_workflow.rb +97 -0
- data/app/workflows/command_tower/workflows/impersonation/stop_workflow.rb +66 -0
- data/app/workflows/command_tower/workflows/me/update_name_workflow.rb +1 -0
- data/app/workflows/command_tower/workflows/profile/show_workflow.rb +1 -0
- data/app/workflows/command_tower/workflows/workflow_result.rb +31 -7
- data/config/routes.rb +20 -0
- data/db/migrate/20260816000001_create_command_tower_audit_events.rb +45 -0
- data/db/migrate/20260817000001_add_scope_columns_to_command_tower_audit_events.rb +34 -0
- data/db/migrate/20260817000003_create_command_tower_impersonation_sessions.rb +26 -0
- data/docs/admin_workspace.md +158 -0
- data/docs/api_reference.md +175 -14
- data/docs/architecture.md +5 -0
- data/docs/audit.md +195 -0
- data/docs/authentication.md +14 -0
- data/docs/authentication_authorization_guide.md +15 -11
- data/docs/authorization.md +25 -4
- data/docs/controllers.md +7 -1
- data/docs/eventing.md +179 -0
- data/docs/extending.md +32 -1
- data/docs/host_integration_guide.md +103 -12
- data/docs/initializing.md +2 -2
- data/docs/messaging_integration_guide.md +1 -1
- data/docs/models.md +4 -0
- data/docs/pagination.md +2 -2
- data/docs/principal_capabilities.md +89 -0
- data/docs/upgrades/0.10.0.md +2 -2
- data/docs/upgrades/0.11.0.md +92 -0
- data/docs/upgrades/README.md +1 -0
- data/lib/command_tower/admin_scope/apply_audit_scoping.rb +45 -0
- data/lib/command_tower/admin_scope/apply_users_narrowing.rb +21 -0
- data/lib/command_tower/admin_scope/manifest_projection.rb +102 -0
- data/lib/command_tower/admin_scope/resolve.rb +31 -0
- data/lib/command_tower/admin_scope/scope_context.rb +7 -0
- data/lib/command_tower/admin_scope/scope_option.rb +7 -0
- data/lib/command_tower/admin_scope.rb +20 -0
- data/lib/command_tower/admin_workspace.rb +16 -0
- data/lib/command_tower/audit/attribution.rb +90 -0
- data/lib/command_tower/audit/emit.rb +113 -0
- data/lib/command_tower/audit/masking.rb +41 -0
- data/lib/command_tower/audit/payload.rb +128 -0
- data/lib/command_tower/audit/persistence/subscriber.rb +85 -0
- data/lib/command_tower/audit.rb +27 -0
- data/lib/command_tower/authorization/assignable_roles.rb +36 -0
- data/lib/command_tower/authorization/default.yml +99 -5
- data/lib/command_tower/authorization/effective_entity_grants.rb +55 -0
- data/lib/command_tower/authorization/entity.rb +15 -5
- data/lib/command_tower/authorization/role.rb +5 -4
- data/lib/command_tower/authorization.rb +71 -10
- data/lib/command_tower/configuration/admin_scope/config.rb +104 -0
- data/lib/command_tower/configuration/admin_scope/tool_registration.rb +28 -0
- data/lib/command_tower/configuration/authorization/config.rb +6 -0
- data/lib/command_tower/configuration/config.rb +20 -0
- data/lib/command_tower/configuration/impersonation/config.rb +38 -0
- data/lib/command_tower/configuration/registry/admin_workspace/config.rb +199 -0
- data/lib/command_tower/configuration/registry/admin_workspace/tool_definition.rb +150 -0
- data/lib/command_tower/configuration/registry/audit/config.rb +403 -0
- data/lib/command_tower/configuration/registry/audit/event_definition.rb +155 -0
- data/lib/command_tower/configuration/registry/config.rb +34 -0
- data/lib/command_tower/configuration/registry/principal_capabilities/capability_definition.rb +43 -0
- data/lib/command_tower/configuration/registry/principal_capabilities/config.rb +136 -0
- data/lib/command_tower/current.rb +12 -0
- data/lib/command_tower/engine.rb +19 -4
- data/lib/command_tower/events.rb +184 -0
- data/lib/command_tower/execution.rb +111 -0
- data/lib/command_tower/impersonation/activity_declaration.rb +23 -0
- data/lib/command_tower/impersonation/apply_overlay.rb +88 -0
- data/lib/command_tower/impersonation/clear_overlay_for_audit.rb +23 -0
- data/lib/command_tower/impersonation/establish_identity.rb +44 -0
- data/lib/command_tower/install/baseline.rb +3 -0
- data/lib/command_tower/logging/lifecycle_declaration.rb +27 -0
- data/lib/command_tower/logging/projection.rb +67 -0
- data/lib/command_tower/logging/subscriber.rb +103 -0
- data/lib/command_tower/principal_capabilities.rb +15 -0
- data/lib/command_tower/version.rb +1 -1
- data/lib/command_tower.rb +12 -0
- data/spec/factories/impersonation_session.rb +17 -0
- data/spec/factories/user.rb +16 -0
- metadata +112 -2
- data/app/services/command_tower/messaging/contract/observability/structured_logger.rb +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2becbc85a78f04d571714ca6a99a1442cc0bc9c270a322fe25d8d3881af9252
|
|
4
|
+
data.tar.gz: ab5432203fd213f203bdd1e5fc2af0ea81a873c5b0ee9fdd25afa357aca50988
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8622b2e00ceb1218df245a00aad41f17e8840779e2f4b91531cdeecc6316bfa18305ce06c569e5a92582819875c87a58acb8ef396cfdcecfcc69752715e99671
|
|
7
|
+
data.tar.gz: 391cca33f7e186d24fa8f904be8a4f3dad6f5cb12d9615c84e54da2b0e59dc2fb4f49c8bd4279ca41083e204e8735e5864a575fc9c09e5de6694ce2afc2ace94
|
data/README.md
CHANGED
|
@@ -41,10 +41,14 @@ See [Architecture](docs/architecture.md) and [ServiceBase](app/services/command_
|
|
|
41
41
|
| Guide | Purpose |
|
|
42
42
|
|-------|---------|
|
|
43
43
|
| [Host integration](docs/host_integration_guide.md) | **Start here** — step-by-step new Rails host path |
|
|
44
|
-
| [Upgrades](docs/upgrades/README.md) | Release upgrade summaries (see [0.
|
|
44
|
+
| [Upgrades](docs/upgrades/README.md) | Release upgrade summaries (see [0.11.0](docs/upgrades/0.11.0.md)) |
|
|
45
45
|
| [Initializing](docs/initializing.md) | Install, configure, migrate, doctor, upgrades |
|
|
46
46
|
| [Testing](docs/testing.md) | Shared FactoryBot / `CommandTower::Testing` |
|
|
47
47
|
| [Architecture](docs/architecture.md) | Layer map for hosts and contributors |
|
|
48
|
+
| [Eventing](docs/eventing.md) | ASN grammar, lifecycle vs semantic, envelope, subscriber failure |
|
|
49
|
+
| [Audit](docs/audit.md) | Registered `audit(...)` + append-only `command_tower_audit_events` ledger |
|
|
50
|
+
| [Admin Workspace](docs/admin_workspace.md) | Tool registry + RBAC-filtered `GET /admin/workspace` manifest |
|
|
51
|
+
| [Principal capabilities](docs/principal_capabilities.md) | Curated FE-projectable ids via `GET /auth/principal-capabilities` |
|
|
48
52
|
| [Controllers / routes](docs/controllers.md) | Engine route areas (index) |
|
|
49
53
|
| [API reference](docs/api_reference.md) | Endpoint catalog (detailed contracts) |
|
|
50
54
|
| [Extending](docs/extending.md) | Stable extension points / do-not-extend |
|
|
@@ -86,7 +90,7 @@ Engine HTTP coverage lives primarily under `spec/requests/`. Residual journey co
|
|
|
86
90
|
|
|
87
91
|
## Routes
|
|
88
92
|
|
|
89
|
-
Engine routes cover Auth, Me (profile, inbox, preferences, phone, Pushover), and Admin messaging announcements. Index: [Controllers](docs/controllers.md). Contracts: [API reference](docs/api_reference.md).
|
|
93
|
+
Engine routes cover Auth, Me (profile, inbox, preferences, phone, Pushover), Admin Workspace manifest, admin audit events, and Admin messaging announcements. Index: [Controllers](docs/controllers.md). Contracts: [API reference](docs/api_reference.md).
|
|
90
94
|
|
|
91
95
|
## Models
|
|
92
96
|
|
|
@@ -109,7 +113,7 @@ Cookie mode supports HttpOnly JWT cookies, SameSite, optional double-submit CSRF
|
|
|
109
113
|
|
|
110
114
|
Authorization establishes permission after authentication (`403` when it fails).
|
|
111
115
|
|
|
112
|
-
Engine defaults ship `owner` and `admin`
|
|
116
|
+
Engine defaults ship `owner` (full access) plus CT-owned Me/Auth and Admin **entity** definitions. CommandTower does **not** ship an operational `admin` role. Hosts supply `rbac_groups.yml` **product roles** that grant those entity names (fail-closed), including any operational Admin bundles. Do not copy CT controller mappings. Host controllers may use provisional `authorize_user!` after `authenticate_user!`.
|
|
113
117
|
|
|
114
118
|
- Quick start: [Authorization](docs/authorization.md)
|
|
115
119
|
- Deep guide: [Authentication & authorization](docs/authentication_authorization_guide.md)
|
|
@@ -8,16 +8,20 @@ module CommandTower
|
|
|
8
8
|
:token_source,
|
|
9
9
|
:roles,
|
|
10
10
|
:principal_type,
|
|
11
|
-
:generated_token
|
|
11
|
+
:generated_token,
|
|
12
|
+
:actor_user,
|
|
13
|
+
:impersonation_session_id
|
|
12
14
|
) do
|
|
13
|
-
def initialize(user:, token_expires_at:, token_source:, roles:, principal_type:, generated_token: nil)
|
|
15
|
+
def initialize(user:, token_expires_at:, token_source:, roles:, principal_type:, generated_token: nil, actor_user: nil, impersonation_session_id: nil)
|
|
14
16
|
super(
|
|
15
17
|
user:,
|
|
16
18
|
token_expires_at:,
|
|
17
19
|
token_source:,
|
|
18
20
|
roles:,
|
|
19
21
|
principal_type:,
|
|
20
|
-
generated_token
|
|
22
|
+
generated_token:,
|
|
23
|
+
actor_user: actor_user || user,
|
|
24
|
+
impersonation_session_id:
|
|
21
25
|
)
|
|
22
26
|
end
|
|
23
27
|
|
|
@@ -28,7 +32,9 @@ module CommandTower
|
|
|
28
32
|
token_source: metadata[:token_source],
|
|
29
33
|
roles: data[:user].roles,
|
|
30
34
|
principal_type: :user,
|
|
31
|
-
generated_token: data[:generated_token]
|
|
35
|
+
generated_token: data[:generated_token],
|
|
36
|
+
actor_user: data[:actor_user] || data[:user],
|
|
37
|
+
impersonation_session_id: data[:impersonation_session_id]
|
|
32
38
|
)
|
|
33
39
|
end
|
|
34
40
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
class ApplicationController < CommandTower::ApplicationController
|
|
6
|
+
include CommandTower::Auth::AuthenticationBoundary
|
|
7
|
+
include CommandTower::Auth::AuthorizationBoundary
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_request!
|
|
10
|
+
before_action :authorize_request!
|
|
11
|
+
before_action :reject_admin_operations_during_impersonation!
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def reject_admin_operations_during_impersonation!
|
|
16
|
+
return unless CommandTower::Current.impersonation_active
|
|
17
|
+
|
|
18
|
+
render_application_result(
|
|
19
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
20
|
+
errors: [CommandTower::Errors::Auth::AdminUnavailableDuringImpersonationError.new],
|
|
21
|
+
http_status: 418
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
module Audit
|
|
6
|
+
class EventsController < CommandTower::Admin::ApplicationController
|
|
7
|
+
def index
|
|
8
|
+
deserialized = CommandTower::Deserializers::Audit::Events::AdminListDeserializer.call(params)
|
|
9
|
+
return render_deserializer_errors unless deserialized.success?
|
|
10
|
+
|
|
11
|
+
render_application_result(
|
|
12
|
+
CommandTower::Workflows::Audit::Events::ListForAdminWorkflow.call(
|
|
13
|
+
limit: deserialized.input.limit,
|
|
14
|
+
offset: deserialized.input.offset,
|
|
15
|
+
actions: deserialized.input.actions,
|
|
16
|
+
occurred_after: deserialized.input.occurred_after,
|
|
17
|
+
occurred_before: deserialized.input.occurred_before,
|
|
18
|
+
subject_types: deserialized.input.subject_types,
|
|
19
|
+
affected_user_id: deserialized.input.affected_user_id,
|
|
20
|
+
actor_user_id: deserialized.input.actor_user_id,
|
|
21
|
+
originating_administrator_id: deserialized.input.originating_administrator_id,
|
|
22
|
+
attribution_mode: deserialized.input.attribution_mode,
|
|
23
|
+
user: current_user,
|
|
24
|
+
scope_value: deserialized.input.scope_value
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def show
|
|
30
|
+
deserialized = CommandTower::Deserializers::Audit::Events::ShowDeserializer.call(params)
|
|
31
|
+
return render_deserializer_errors unless deserialized.success?
|
|
32
|
+
|
|
33
|
+
render_application_result(
|
|
34
|
+
CommandTower::Workflows::Audit::Events::ShowForAdminWorkflow.call(
|
|
35
|
+
id: deserialized.input.id,
|
|
36
|
+
user: current_user,
|
|
37
|
+
scope_value: deserialized.input.scope_value
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def filter_options
|
|
43
|
+
render_application_result(
|
|
44
|
+
CommandTower::Workflows::Audit::Events::FilterOptionsForAdminWorkflow.call
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def render_deserializer_errors
|
|
51
|
+
render_application_result(
|
|
52
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
53
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
54
|
+
http_status: :unprocessable_entity
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
module CommandTower
|
|
4
4
|
module Admin
|
|
5
5
|
module Messaging
|
|
6
|
-
class AnnouncementsController < CommandTower::ApplicationController
|
|
7
|
-
include CommandTower::Auth::AuthenticationBoundary
|
|
8
|
-
include CommandTower::Auth::AuthorizationBoundary
|
|
9
|
-
include CommandTower::Api::ApplicationResponseRenderer
|
|
10
|
-
|
|
11
|
-
before_action :authenticate_request!
|
|
12
|
-
before_action :authorize_request!
|
|
13
|
-
|
|
6
|
+
class AnnouncementsController < CommandTower::Admin::ApplicationController
|
|
14
7
|
def create
|
|
15
8
|
deserialized = CommandTower::Deserializers::Admin::Messaging::CreateAnnouncementDeserializer.call(params)
|
|
16
9
|
unless deserialized.success?
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
module Users
|
|
6
|
+
class IdentitiesController < CommandTower::Admin::ApplicationController
|
|
7
|
+
def update_name
|
|
8
|
+
deserialized = CommandTower::Deserializers::Admin::Users::UpdateNameDeserializer.call(params)
|
|
9
|
+
return render_deserializer_errors unless deserialized.success?
|
|
10
|
+
|
|
11
|
+
render_application_result(
|
|
12
|
+
CommandTower::Workflows::Admin::Users::UpdateNameWorkflow.call(
|
|
13
|
+
id: deserialized.input.id,
|
|
14
|
+
user: current_user,
|
|
15
|
+
first_name: deserialized.input.first_name,
|
|
16
|
+
last_name: deserialized.input.last_name,
|
|
17
|
+
scope_value: deserialized.input.scope_value
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_username
|
|
23
|
+
deserialized = CommandTower::Deserializers::Admin::Users::UpdateUsernameDeserializer.call(params)
|
|
24
|
+
return render_deserializer_errors unless deserialized.success?
|
|
25
|
+
|
|
26
|
+
render_application_result(
|
|
27
|
+
CommandTower::Workflows::Admin::Users::UpdateUsernameWorkflow.call(
|
|
28
|
+
id: deserialized.input.id,
|
|
29
|
+
user: current_user,
|
|
30
|
+
username: deserialized.input.username,
|
|
31
|
+
scope_value: deserialized.input.scope_value
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update_email
|
|
37
|
+
deserialized = CommandTower::Deserializers::Admin::Users::UpdateEmailDeserializer.call(params)
|
|
38
|
+
return render_deserializer_errors unless deserialized.success?
|
|
39
|
+
|
|
40
|
+
render_application_result(
|
|
41
|
+
CommandTower::Workflows::Admin::Users::UpdateEmailWorkflow.call(
|
|
42
|
+
id: deserialized.input.id,
|
|
43
|
+
user: current_user,
|
|
44
|
+
email: deserialized.input.email,
|
|
45
|
+
scope_value: deserialized.input.scope_value
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update_email_validation
|
|
51
|
+
deserialized = CommandTower::Deserializers::Admin::Users::UpdateEmailValidationDeserializer.call(params)
|
|
52
|
+
return render_deserializer_errors unless deserialized.success?
|
|
53
|
+
|
|
54
|
+
render_application_result(
|
|
55
|
+
CommandTower::Workflows::Admin::Users::SetEmailValidatedWorkflow.call(
|
|
56
|
+
id: deserialized.input.id,
|
|
57
|
+
user: current_user,
|
|
58
|
+
email_validated: deserialized.input.email_validated,
|
|
59
|
+
scope_value: deserialized.input.scope_value
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def render_deserializer_errors
|
|
67
|
+
render_application_result(
|
|
68
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
69
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
70
|
+
http_status: :unprocessable_entity
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
module Users
|
|
6
|
+
class ImpersonationSessionsController < CommandTower::Admin::ApplicationController
|
|
7
|
+
def create
|
|
8
|
+
deserialized = CommandTower::Deserializers::Admin::Users::ShowDeserializer.call(params)
|
|
9
|
+
return render_deserializer_errors unless deserialized.success?
|
|
10
|
+
|
|
11
|
+
render_application_result(
|
|
12
|
+
CommandTower::Workflows::Impersonation::StartWorkflow.call(
|
|
13
|
+
id: deserialized.input.id,
|
|
14
|
+
actor: current_user,
|
|
15
|
+
scope_value: deserialized.input.scope_value
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def render_deserializer_errors
|
|
23
|
+
render_application_result(
|
|
24
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
25
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
26
|
+
http_status: :unprocessable_entity
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
module Users
|
|
6
|
+
class RolesController < CommandTower::Admin::ApplicationController
|
|
7
|
+
def index
|
|
8
|
+
render_application_result(
|
|
9
|
+
CommandTower::Workflows::Admin::Users::ListAssignableRolesWorkflow.call
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def update
|
|
14
|
+
deserialized = CommandTower::Deserializers::Admin::Users::UpdateRolesDeserializer.call(params)
|
|
15
|
+
return render_deserializer_errors unless deserialized.success?
|
|
16
|
+
|
|
17
|
+
render_application_result(
|
|
18
|
+
CommandTower::Workflows::Admin::Users::UpdateRolesWorkflow.call(
|
|
19
|
+
id: deserialized.input.id,
|
|
20
|
+
user: current_user,
|
|
21
|
+
roles: deserialized.input.roles,
|
|
22
|
+
scope_value: deserialized.input.scope_value
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def render_deserializer_errors
|
|
30
|
+
render_application_result(
|
|
31
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
32
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
33
|
+
http_status: :unprocessable_entity
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
class UsersController < CommandTower::Admin::ApplicationController
|
|
6
|
+
def index
|
|
7
|
+
deserialized = CommandTower::Deserializers::Admin::Users::ListDeserializer.call(params)
|
|
8
|
+
return render_deserializer_errors unless deserialized.success?
|
|
9
|
+
|
|
10
|
+
render_application_result(
|
|
11
|
+
CommandTower::Workflows::Admin::Users::ListWorkflow.call(
|
|
12
|
+
limit: deserialized.input.limit,
|
|
13
|
+
offset: deserialized.input.offset,
|
|
14
|
+
search: deserialized.input.search,
|
|
15
|
+
user: current_user,
|
|
16
|
+
scope_value: deserialized.input.scope_value
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def show
|
|
22
|
+
deserialized = CommandTower::Deserializers::Admin::Users::ShowDeserializer.call(params)
|
|
23
|
+
return render_deserializer_errors unless deserialized.success?
|
|
24
|
+
|
|
25
|
+
render_application_result(
|
|
26
|
+
CommandTower::Workflows::Admin::Users::ShowWorkflow.call(
|
|
27
|
+
id: deserialized.input.id,
|
|
28
|
+
user: current_user,
|
|
29
|
+
scope_value: deserialized.input.scope_value
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def render_deserializer_errors
|
|
37
|
+
render_application_result(
|
|
38
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
39
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
40
|
+
http_status: :unprocessable_entity
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Admin
|
|
5
|
+
class WorkspaceController < CommandTower::Admin::ApplicationController
|
|
6
|
+
skip_before_action :reject_admin_operations_during_impersonation!, only: :show
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
render_application_result(
|
|
10
|
+
CommandTower::Workflows::Admin::Workspace::ManifestWorkflow.call(user: current_user)
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module CommandTower
|
|
4
4
|
class ApplicationController < ActionController::API
|
|
5
|
+
include CommandTower::Execution::HttpBoundary
|
|
6
|
+
|
|
5
7
|
AUTHENTICATION_HEADER = CommandTower::Jwt::AuthorizationHelper::AUTHENTICATION_HEADER
|
|
6
8
|
AUTHENTICATION_EXPIRE_HEADER = CommandTower::Jwt::AuthorizationHelper::AUTHENTICATION_EXPIRE_HEADER
|
|
7
9
|
AUTHENTICATION_WITH_RESET = CommandTower::Jwt::AuthorizationHelper::AUTHENTICATION_WITH_RESET
|
|
@@ -46,7 +48,21 @@ module CommandTower
|
|
|
46
48
|
with_reset = CommandTower::Jwt::AuthorizationHelper.get_with_reset_flag(request)
|
|
47
49
|
result = CommandTower::Jwt::AuthenticateUser.(token:, bypass_email_validation:, with_reset:)
|
|
48
50
|
if result.success?
|
|
49
|
-
|
|
51
|
+
established = CommandTower::Impersonation::EstablishIdentity.call(
|
|
52
|
+
actor: result.user,
|
|
53
|
+
impersonation_session_id: result.impersonation_session_id
|
|
54
|
+
)
|
|
55
|
+
if established.expired?
|
|
56
|
+
status = 401
|
|
57
|
+
schema = CommandTower::Schema::Error::Base.new(
|
|
58
|
+
status:,
|
|
59
|
+
message: CommandTower::Errors::Auth::ImpersonationSessionExpiredError.new.message
|
|
60
|
+
)
|
|
61
|
+
render(json: schema.to_h, status:)
|
|
62
|
+
return false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@current_user = established.user
|
|
50
66
|
if with_reset
|
|
51
67
|
# Use helper to set both headers and cookie (if enabled)
|
|
52
68
|
CommandTower::Jwt::AuthorizationHelper.set_token(
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Auth
|
|
5
|
+
class ImpersonationSessionController < CommandTower::ApplicationController
|
|
6
|
+
include CommandTower::Auth::AuthenticationBoundary
|
|
7
|
+
include CommandTower::Api::ApplicationResponseRenderer
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_stop_request!
|
|
10
|
+
|
|
11
|
+
def destroy
|
|
12
|
+
result = CommandTower::Workflows::Impersonation::StopWorkflow.call(
|
|
13
|
+
actor: current_auth_context.actor_user,
|
|
14
|
+
impersonation_session_id: current_auth_context.impersonation_session_id,
|
|
15
|
+
token_expires_at: current_auth_context.token_expires_at
|
|
16
|
+
)
|
|
17
|
+
render_application_result(result)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def authenticate_stop_request!
|
|
23
|
+
authenticate_request!(overlay_mode: :capture)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -6,7 +6,9 @@ module CommandTower
|
|
|
6
6
|
include CommandTower::Api::ApplicationResponseRenderer
|
|
7
7
|
|
|
8
8
|
def create
|
|
9
|
-
|
|
9
|
+
token_data = CommandTower::Jwt::AuthorizationHelper.extract_token(request)
|
|
10
|
+
token = token_data[:error] ? nil : token_data[:token]
|
|
11
|
+
result = CommandTower::Workflows::Auth::LogoutWorkflow.call(token:)
|
|
10
12
|
render_application_result(result)
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Auth
|
|
5
|
+
class PrincipalCapabilitiesController < CommandTower::ApplicationController
|
|
6
|
+
include CommandTower::Auth::AuthenticationBoundary
|
|
7
|
+
include CommandTower::Auth::AuthorizationBoundary
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_request!
|
|
10
|
+
before_action :authorize_request!
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
render_application_result(
|
|
14
|
+
CommandTower::Workflows::Auth::PrincipalCapabilities::ShowWorkflow.call(user: current_user)
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Me
|
|
5
|
+
class AuditEventsController < CommandTower::ApplicationController
|
|
6
|
+
include CommandTower::Auth::AuthenticationBoundary
|
|
7
|
+
include CommandTower::Auth::AuthorizationBoundary
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_request!
|
|
10
|
+
before_action :authorize_request!
|
|
11
|
+
|
|
12
|
+
def index
|
|
13
|
+
deserialized = CommandTower::Deserializers::Audit::Events::UserListDeserializer.call(params)
|
|
14
|
+
return render_deserializer_errors unless deserialized.success?
|
|
15
|
+
|
|
16
|
+
render_application_result(
|
|
17
|
+
CommandTower::Workflows::Audit::Events::ListForUserWorkflow.call(
|
|
18
|
+
user: current_user,
|
|
19
|
+
limit: deserialized.input.limit,
|
|
20
|
+
offset: deserialized.input.offset,
|
|
21
|
+
actions: deserialized.input.actions,
|
|
22
|
+
occurred_after: deserialized.input.occurred_after,
|
|
23
|
+
occurred_before: deserialized.input.occurred_before,
|
|
24
|
+
subject_types: deserialized.input.subject_types
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def show
|
|
30
|
+
deserialized = CommandTower::Deserializers::Audit::Events::ShowDeserializer.call(params)
|
|
31
|
+
return render_deserializer_errors unless deserialized.success?
|
|
32
|
+
|
|
33
|
+
render_application_result(
|
|
34
|
+
CommandTower::Workflows::Audit::Events::ShowForUserWorkflow.call(
|
|
35
|
+
user: current_user,
|
|
36
|
+
id: deserialized.input.id
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def filter_options
|
|
42
|
+
render_application_result(
|
|
43
|
+
CommandTower::Workflows::Audit::Events::FilterOptionsForUserWorkflow.call
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def render_deserializer_errors
|
|
50
|
+
render_application_result(
|
|
51
|
+
CommandTower::Workflows::WorkflowResult.failure(
|
|
52
|
+
errors: [CommandTower::Errors::ValidationError.new(details: { base: "Invalid request parameters" })],
|
|
53
|
+
http_status: :unprocessable_entity
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -14,6 +14,11 @@ module CommandTower
|
|
|
14
14
|
data: result.payload,
|
|
15
15
|
meta: result.meta
|
|
16
16
|
)
|
|
17
|
+
elsif result.deferred?
|
|
18
|
+
CommandTower::Serializers::Application::EnvelopeSerializer.success(
|
|
19
|
+
data: result.payload,
|
|
20
|
+
meta: result.meta.merge(reason: result.reason, retry_after: result.retry_after)
|
|
21
|
+
)
|
|
17
22
|
else
|
|
18
23
|
serialized_errors = result.errors.map do |error|
|
|
19
24
|
CommandTower::Serializers::Application::ErrorEntrySerializer.serialize(error)
|
|
@@ -13,11 +13,12 @@ module CommandTower
|
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
-
def authenticate_request!(bypass_email_validation: false)
|
|
16
|
+
def authenticate_request!(bypass_email_validation: false, overlay_mode: :enforce)
|
|
17
17
|
result = CommandTower::Workflows::Auth::AuthenticateRequestWorkflow.call(
|
|
18
18
|
request: request,
|
|
19
19
|
response: response,
|
|
20
|
-
bypass_email_validation: bypass_email_validation
|
|
20
|
+
bypass_email_validation: bypass_email_validation,
|
|
21
|
+
overlay_mode: overlay_mode
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
unless result.success?
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Execution
|
|
5
|
+
module HttpBoundary
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
around_action :command_tower_http_execution
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def command_tower_http_execution
|
|
15
|
+
request_id = request.request_id.presence || request.uuid
|
|
16
|
+
CommandTower.with_execution(
|
|
17
|
+
source: :http,
|
|
18
|
+
request_id:,
|
|
19
|
+
correlation_id: request_id,
|
|
20
|
+
remote_ip: request.remote_ip,
|
|
21
|
+
user_agent: request.user_agent
|
|
22
|
+
) do
|
|
23
|
+
yield
|
|
24
|
+
record_impersonation_activity_if_needed
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def record_impersonation_activity_if_needed
|
|
29
|
+
return unless CommandTower::Current.impersonation_active
|
|
30
|
+
return unless CommandTower::Current.impersonation_activity_recorded
|
|
31
|
+
return unless response.status.to_i.between?(200, 299)
|
|
32
|
+
|
|
33
|
+
session_id = CommandTower::Current.impersonation_session_id
|
|
34
|
+
return if session_id.blank?
|
|
35
|
+
|
|
36
|
+
result = CommandTower::Services::Impersonation::RecordActivity.call(session_id:)
|
|
37
|
+
return unless result.success?
|
|
38
|
+
return unless result.data[:refreshed]
|
|
39
|
+
|
|
40
|
+
merge_impersonation_clock_meta!(result.data[:session])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def merge_impersonation_clock_meta!(session)
|
|
44
|
+
return if session.nil?
|
|
45
|
+
|
|
46
|
+
raw = response.body
|
|
47
|
+
return if raw.blank?
|
|
48
|
+
|
|
49
|
+
parsed = JSON.parse(raw)
|
|
50
|
+
return unless parsed.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
parsed["meta"] ||= {}
|
|
53
|
+
parsed["meta"]["impersonation"] = {
|
|
54
|
+
"idleExpiresAt" => CommandTower::Serializers::ApplicationSerializer.iso8601(session.idle_expires_at),
|
|
55
|
+
"absoluteExpiresAt" => CommandTower::Serializers::ApplicationSerializer.iso8601(session.absolute_expires_at)
|
|
56
|
+
}
|
|
57
|
+
json = parsed.to_json
|
|
58
|
+
response.body = json
|
|
59
|
+
response.headers["Content-Length"] = json.bytesize.to_s
|
|
60
|
+
rescue JSON::ParserError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|