legate 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,1541 @@
|
|
|
1
|
+
# File: lib/legate/web/routes/authentication_routes.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../../auth/url_guard' # SSRF guard for credential-test URLs
|
|
5
|
+
|
|
6
|
+
module Legate
|
|
7
|
+
module Web
|
|
8
|
+
module AuthenticationRoutes
|
|
9
|
+
def self.registered(app)
|
|
10
|
+
# Add helper methods to the app
|
|
11
|
+
app.helpers do
|
|
12
|
+
# Helper method to get a description for a scheme
|
|
13
|
+
def get_scheme_description(scheme)
|
|
14
|
+
case scheme.scheme_type
|
|
15
|
+
when :api_key
|
|
16
|
+
'Simple API key authentication for services that use API keys in headers or query parameters'
|
|
17
|
+
when :http_bearer
|
|
18
|
+
'HTTP Bearer token authentication and basic auth support'
|
|
19
|
+
when :oauth2
|
|
20
|
+
'OAuth2 authorization code flow for secure third-party authentication'
|
|
21
|
+
when :oidc, :openid_connect
|
|
22
|
+
'OpenID Connect authentication extending OAuth2 with identity information'
|
|
23
|
+
when :service_account
|
|
24
|
+
'Service account authentication with automatic token exchange'
|
|
25
|
+
when :google_service_account
|
|
26
|
+
'Google Cloud service account authentication with JSON key files'
|
|
27
|
+
else
|
|
28
|
+
"Authentication scheme of type: #{scheme.scheme_type}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Helper method to get a description for a credential
|
|
33
|
+
def get_credential_description(credential)
|
|
34
|
+
case credential.auth_type
|
|
35
|
+
when :api_key
|
|
36
|
+
'API key credential for service authentication'
|
|
37
|
+
when :oauth2, :oidc
|
|
38
|
+
'OAuth2/OIDC client credentials for authorization flows'
|
|
39
|
+
when :service_account, :google_service_account
|
|
40
|
+
'Service account credentials for automated authentication'
|
|
41
|
+
when :http_bearer
|
|
42
|
+
'Bearer token or basic auth credentials'
|
|
43
|
+
else
|
|
44
|
+
"Credential of type: #{credential.auth_type}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Helper method to get masked credential information for display
|
|
49
|
+
def get_masked_credential_info(credential)
|
|
50
|
+
info_parts = []
|
|
51
|
+
|
|
52
|
+
# Check for common credential fields and mask them appropriately
|
|
53
|
+
if credential[:api_key, resolve_env: false]
|
|
54
|
+
masked_key = mask_sensitive_value(credential[:api_key, resolve_env: false])
|
|
55
|
+
info_parts << "API Key: #{masked_key}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
info_parts << "Client ID: #{credential[:client_id, resolve_env: false]}" if credential[:client_id, resolve_env: false]
|
|
59
|
+
|
|
60
|
+
if credential[:client_secret, resolve_env: false]
|
|
61
|
+
masked_secret = mask_sensitive_value(credential[:client_secret, resolve_env: false])
|
|
62
|
+
info_parts << "Client Secret: #{masked_secret}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if credential[:bearer_token, resolve_env: false]
|
|
66
|
+
masked_token = mask_sensitive_value(credential[:bearer_token, resolve_env: false])
|
|
67
|
+
info_parts << "Bearer Token: #{masked_token}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
info_parts << "Username: #{credential[:username, resolve_env: false]}" if credential[:username, resolve_env: false]
|
|
71
|
+
|
|
72
|
+
info_parts << "Password: #{mask_sensitive_value(credential[:password, resolve_env: false])}" if credential[:password, resolve_env: false]
|
|
73
|
+
|
|
74
|
+
info_parts.empty? ? 'No displayable information' : info_parts.join(', ')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Helper method to mask sensitive values for display
|
|
78
|
+
def mask_sensitive_value(value)
|
|
79
|
+
return '[Not Set]' if value.nil? || value.empty?
|
|
80
|
+
|
|
81
|
+
value_str = value.to_s
|
|
82
|
+
# Never reveal the head, and never reveal any characters of a short
|
|
83
|
+
# secret (the old first-3/last-3 mask exposed "secret" as "sec***ret").
|
|
84
|
+
return '••••••••' if value_str.length < 12
|
|
85
|
+
|
|
86
|
+
"••••••••#{value_str[-4..]}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Helper method to get scheme configuration fields
|
|
90
|
+
def get_scheme_config_fields(scheme_type)
|
|
91
|
+
case scheme_type.to_sym
|
|
92
|
+
when :api_key
|
|
93
|
+
[] # API Key scheme has no configuration
|
|
94
|
+
when :http_bearer
|
|
95
|
+
[] # HTTP Bearer scheme has no configuration
|
|
96
|
+
when :oauth2
|
|
97
|
+
[
|
|
98
|
+
{ name: 'authorization_url', type: 'url', required: true, label: 'Authorization URL' },
|
|
99
|
+
{ name: 'token_url', type: 'url', required: true, label: 'Token URL' },
|
|
100
|
+
{ name: 'scopes', type: 'text', required: false, label: 'Scopes (space-separated)' },
|
|
101
|
+
{ name: 'use_pkce', type: 'checkbox', required: false, label: 'Use PKCE' },
|
|
102
|
+
{ name: 'revocation_url', type: 'url', required: false, label: 'Revocation URL' }
|
|
103
|
+
]
|
|
104
|
+
when :oidc, :openid_connect
|
|
105
|
+
[
|
|
106
|
+
{ name: 'authorization_url', type: 'url', required: true, label: 'Authorization URL' },
|
|
107
|
+
{ name: 'token_url', type: 'url', required: true, label: 'Token URL' },
|
|
108
|
+
{ name: 'userinfo_url', type: 'url', required: false, label: 'UserInfo URL' },
|
|
109
|
+
{ name: 'scopes', type: 'text', required: false, label: 'Scopes (space-separated)' },
|
|
110
|
+
{ name: 'use_pkce', type: 'checkbox', required: false, label: 'Use PKCE' }
|
|
111
|
+
]
|
|
112
|
+
when :service_account
|
|
113
|
+
[
|
|
114
|
+
{ name: 'token_url', type: 'url', required: true, label: 'Token URL' },
|
|
115
|
+
{ name: 'scopes', type: 'text', required: false, label: 'Scopes (space-separated)' }
|
|
116
|
+
]
|
|
117
|
+
when :google_service_account
|
|
118
|
+
[
|
|
119
|
+
{ name: 'scopes', type: 'text', required: false, label: 'Scopes (space-separated)' }
|
|
120
|
+
]
|
|
121
|
+
else
|
|
122
|
+
[]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Helper method to get current scheme configuration
|
|
127
|
+
def get_scheme_current_config(scheme)
|
|
128
|
+
config = {}
|
|
129
|
+
case scheme.scheme_type
|
|
130
|
+
when :oauth2, :oidc, :openid_connect
|
|
131
|
+
config['authorization_url'] = scheme.authorization_url if scheme.respond_to?(:authorization_url)
|
|
132
|
+
config['token_url'] = scheme.token_url if scheme.respond_to?(:token_url)
|
|
133
|
+
config['scopes'] = scheme.scopes.join(' ') if scheme.respond_to?(:scopes) && scheme.scopes
|
|
134
|
+
config['use_pkce'] = scheme.use_pkce if scheme.respond_to?(:use_pkce)
|
|
135
|
+
config['revocation_url'] = scheme.revocation_url if scheme.respond_to?(:revocation_url)
|
|
136
|
+
config['userinfo_url'] = scheme.userinfo_url if scheme.respond_to?(:userinfo_url)
|
|
137
|
+
when :service_account, :google_service_account
|
|
138
|
+
config['token_url'] = scheme.token_url if scheme.respond_to?(:token_url)
|
|
139
|
+
config['scopes'] = scheme.scopes.join(' ') if scheme.respond_to?(:scopes) && scheme.scopes
|
|
140
|
+
end
|
|
141
|
+
config
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Helper method to get compatible credential types for a scheme
|
|
145
|
+
def get_compatible_credential_types(scheme_type)
|
|
146
|
+
case scheme_type.to_sym
|
|
147
|
+
when :api_key
|
|
148
|
+
['api_key']
|
|
149
|
+
when :http_bearer
|
|
150
|
+
%w[http_bearer bearer_token]
|
|
151
|
+
when :oauth2, :oidc, :openid_connect
|
|
152
|
+
%w[oauth2 oidc]
|
|
153
|
+
when :service_account, :google_service_account
|
|
154
|
+
%w[service_account google_service_account]
|
|
155
|
+
else
|
|
156
|
+
[]
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Helper method to get credential configuration fields
|
|
161
|
+
def get_credential_config_fields(auth_type)
|
|
162
|
+
case auth_type.to_sym
|
|
163
|
+
when :api_key
|
|
164
|
+
[
|
|
165
|
+
{ name: 'api_key', type: 'password', required: true, label: 'API Key', placeholder: 'Enter your API key' },
|
|
166
|
+
{ name: 'location', type: 'select', required: false, label: 'Location', options: %w[header query cookie], default: 'header' },
|
|
167
|
+
{ name: 'name', type: 'text', required: false, label: 'Parameter Name', placeholder: 'X-API-Key' }
|
|
168
|
+
]
|
|
169
|
+
when :http_bearer
|
|
170
|
+
[
|
|
171
|
+
{ name: 'bearer_token', type: 'password', required: true, label: 'Bearer Token', placeholder: 'Enter bearer token' }
|
|
172
|
+
]
|
|
173
|
+
when :oauth2, :oidc
|
|
174
|
+
[
|
|
175
|
+
{ name: 'client_id', type: 'text', required: true, label: 'Client ID', placeholder: 'Enter client ID' },
|
|
176
|
+
{ name: 'client_secret', type: 'password', required: true, label: 'Client Secret', placeholder: 'Enter client secret' },
|
|
177
|
+
{ name: 'redirect_uri', type: 'url', required: false, label: 'Redirect URI', placeholder: 'https://your-app.com/callback' },
|
|
178
|
+
{ name: 'scopes', type: 'text', required: false, label: 'Scopes (space-separated)', placeholder: 'read write' }
|
|
179
|
+
]
|
|
180
|
+
when :service_account
|
|
181
|
+
[
|
|
182
|
+
{ name: 'client_email', type: 'email', required: true, label: 'Client Email', placeholder: 'service-account@project.iam.gserviceaccount.com' },
|
|
183
|
+
{ name: 'private_key', type: 'textarea', required: true, label: 'Private Key', placeholder: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----' },
|
|
184
|
+
{ name: 'project_id', type: 'text', required: false, label: 'Project ID', placeholder: 'your-project-id' }
|
|
185
|
+
]
|
|
186
|
+
when :google_service_account
|
|
187
|
+
[
|
|
188
|
+
{ name: 'service_account_key', type: 'textarea', required: true, label: 'Service Account Key (JSON)', placeholder: '{"type": "service_account", "project_id": "..."}' }
|
|
189
|
+
]
|
|
190
|
+
when :basic
|
|
191
|
+
[
|
|
192
|
+
{ name: 'username', type: 'text', required: true, label: 'Username', placeholder: 'Enter username' },
|
|
193
|
+
{ name: 'password', type: 'password', required: true, label: 'Password', placeholder: 'Enter password' }
|
|
194
|
+
]
|
|
195
|
+
else
|
|
196
|
+
[]
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Helper method to get current credential configuration (masked)
|
|
201
|
+
def get_credential_current_config(credential)
|
|
202
|
+
config = {}
|
|
203
|
+
|
|
204
|
+
# Get all attributes but mask sensitive ones
|
|
205
|
+
credential.to_h(resolve_env: false).each do |key, value|
|
|
206
|
+
next if key == :auth_type
|
|
207
|
+
|
|
208
|
+
config[key.to_s] = if sensitive_credential_field?(key)
|
|
209
|
+
mask_sensitive_value(value)
|
|
210
|
+
else
|
|
211
|
+
value
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
config
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Helper method to check if a credential field is sensitive
|
|
219
|
+
def sensitive_credential_field?(field_name)
|
|
220
|
+
sensitive_fields = %i[api_key client_secret bearer_token password private_key service_account_key token]
|
|
221
|
+
sensitive_fields.include?(field_name.to_sym)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Helper method to get compatible schemes for a credential type
|
|
225
|
+
def get_compatible_schemes_for_credential(auth_type)
|
|
226
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
227
|
+
schemes = auth_manager.schemes || {}
|
|
228
|
+
|
|
229
|
+
compatible_schemes = schemes.select do |scheme_name, scheme|
|
|
230
|
+
case auth_type.to_sym
|
|
231
|
+
when :api_key
|
|
232
|
+
scheme.scheme_type == :api_key
|
|
233
|
+
when :http_bearer
|
|
234
|
+
scheme.scheme_type == :http_bearer
|
|
235
|
+
when :oauth2, :oidc
|
|
236
|
+
%i[oauth2 oidc openid_connect].include?(scheme.scheme_type)
|
|
237
|
+
when :service_account, :google_service_account
|
|
238
|
+
%i[service_account google_service_account].include?(scheme.scheme_type)
|
|
239
|
+
when :basic
|
|
240
|
+
scheme.scheme_type == :http_bearer
|
|
241
|
+
else
|
|
242
|
+
false
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
compatible_schemes.keys
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Helper method to get mapping configuration fields
|
|
250
|
+
def get_mapping_config_fields
|
|
251
|
+
[
|
|
252
|
+
{ name: 'pattern', type: 'text', required: true, label: 'URL Pattern', placeholder: 'e.g. https://api.example.com/v1/* or ^https://.*\\.example\\.com/.*$' },
|
|
253
|
+
{ name: 'pattern_type', type: 'select', required: true, label: 'Pattern Type', options: %w[string regex], default: 'string' },
|
|
254
|
+
{ name: 'scheme_name', type: 'select', required: true, label: 'Authentication Scheme' },
|
|
255
|
+
{ name: 'credential_name', type: 'select', required: true, label: 'Credential' },
|
|
256
|
+
{ name: 'priority', type: 'number', required: false, label: 'Priority', placeholder: '1' },
|
|
257
|
+
{ name: 'active', type: 'checkbox', required: false, label: 'Active', default: true }
|
|
258
|
+
]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Helper to validate a mapping pattern
|
|
262
|
+
def valid_mapping_pattern?(pattern, pattern_type)
|
|
263
|
+
return false if pattern.nil? || pattern.strip.empty?
|
|
264
|
+
|
|
265
|
+
if pattern_type == 'regex'
|
|
266
|
+
begin
|
|
267
|
+
Regexp.new(pattern)
|
|
268
|
+
true
|
|
269
|
+
rescue RegexpError
|
|
270
|
+
false
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
true
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Helper to check scheme/credential compatibility
|
|
278
|
+
def mapping_scheme_credential_compatible?(scheme, credential)
|
|
279
|
+
return false unless scheme && credential
|
|
280
|
+
|
|
281
|
+
compatible_types = get_compatible_credential_types(scheme.scheme_type)
|
|
282
|
+
compatible_types.include?(credential.auth_type.to_s) || compatible_types.include?(credential.auth_type)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Helper method to perform credential testing
|
|
286
|
+
def test_credential_functionality(credential, test_options = {})
|
|
287
|
+
test_results = { success: true, tests: [], credential_name: credential.to_s }
|
|
288
|
+
|
|
289
|
+
begin
|
|
290
|
+
# Test 1: Basic validation
|
|
291
|
+
credential.to_h(resolve_env: true)
|
|
292
|
+
test_results[:tests] << {
|
|
293
|
+
name: 'Basic Validation',
|
|
294
|
+
status: 'passed',
|
|
295
|
+
message: 'Credential structure and environment variables are valid'
|
|
296
|
+
}
|
|
297
|
+
rescue StandardError => e
|
|
298
|
+
test_results[:success] = false
|
|
299
|
+
test_results[:tests] << {
|
|
300
|
+
name: 'Basic Validation',
|
|
301
|
+
status: 'failed',
|
|
302
|
+
message: "Validation failed: #{e.message}"
|
|
303
|
+
}
|
|
304
|
+
return test_results
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Test 2: Type-specific validation
|
|
308
|
+
test_results[:tests] << case credential.auth_type
|
|
309
|
+
when :api_key
|
|
310
|
+
test_api_key_credential(credential, test_options)
|
|
311
|
+
when :oauth2, :oidc
|
|
312
|
+
test_oauth_credential(credential, test_options)
|
|
313
|
+
when :service_account, :google_service_account
|
|
314
|
+
test_service_account_credential(credential, test_options)
|
|
315
|
+
when :http_bearer
|
|
316
|
+
test_bearer_credential(credential, test_options)
|
|
317
|
+
else
|
|
318
|
+
{
|
|
319
|
+
name: 'Type-Specific Test',
|
|
320
|
+
status: 'skipped',
|
|
321
|
+
message: "No specific test available for credential type: #{credential.auth_type}"
|
|
322
|
+
}
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Check if any test failed
|
|
326
|
+
test_results[:success] = test_results[:tests].all? { |test| test[:status] != 'failed' }
|
|
327
|
+
test_results
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Test API key credential
|
|
331
|
+
def test_api_key_credential(credential, options = {})
|
|
332
|
+
api_key = credential[:api_key]
|
|
333
|
+
return { name: 'API Key Test', status: 'failed', message: 'API key is missing' } unless api_key
|
|
334
|
+
|
|
335
|
+
if options[:test_url]
|
|
336
|
+
# Test against provided URL
|
|
337
|
+
begin
|
|
338
|
+
require 'net/http'
|
|
339
|
+
Legate::Auth::UrlGuard.validate!(options[:test_url], label: 'Test URL')
|
|
340
|
+
uri = URI(options[:test_url])
|
|
341
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
342
|
+
http.use_ssl = uri.scheme == 'https'
|
|
343
|
+
http.read_timeout = 10
|
|
344
|
+
|
|
345
|
+
request = Net::HTTP::Get.new(uri)
|
|
346
|
+
|
|
347
|
+
# Add API key based on location
|
|
348
|
+
location = credential[:location] || 'header'
|
|
349
|
+
key_name = credential[:name] || 'X-API-Key'
|
|
350
|
+
|
|
351
|
+
case location
|
|
352
|
+
when 'header'
|
|
353
|
+
request[key_name] = api_key
|
|
354
|
+
when 'query'
|
|
355
|
+
uri.query = "#{key_name}=#{api_key}"
|
|
356
|
+
request = Net::HTTP::Get.new(uri)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
response = http.request(request)
|
|
360
|
+
|
|
361
|
+
if response.code.to_i < 400
|
|
362
|
+
{ name: 'API Key Test', status: 'passed', message: "API key accepted (HTTP #{response.code})" }
|
|
363
|
+
else
|
|
364
|
+
{ name: 'API Key Test', status: 'failed', message: "API key rejected (HTTP #{response.code})" }
|
|
365
|
+
end
|
|
366
|
+
rescue StandardError => e
|
|
367
|
+
{ name: 'API Key Test', status: 'failed', message: "Network error: #{e.message}" }
|
|
368
|
+
end
|
|
369
|
+
else
|
|
370
|
+
{ name: 'API Key Test', status: 'passed', message: 'API key format is valid (no test URL provided)' }
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Test OAuth credential
|
|
375
|
+
def test_oauth_credential(credential, _options = {})
|
|
376
|
+
client_id = credential[:client_id]
|
|
377
|
+
client_secret = credential[:client_secret]
|
|
378
|
+
|
|
379
|
+
return { name: 'OAuth Test', status: 'failed', message: 'Client ID is missing' } unless client_id
|
|
380
|
+
return { name: 'OAuth Test', status: 'failed', message: 'Client secret is missing' } unless client_secret
|
|
381
|
+
|
|
382
|
+
{ name: 'OAuth Test', status: 'passed', message: 'OAuth credentials are properly formatted' }
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Test service account credential
|
|
386
|
+
def test_service_account_credential(credential, _options = {})
|
|
387
|
+
if credential.auth_type == :google_service_account
|
|
388
|
+
key_data = credential[:service_account_key]
|
|
389
|
+
return { name: 'Service Account Test', status: 'failed', message: 'Service account key is missing' } unless key_data
|
|
390
|
+
|
|
391
|
+
begin
|
|
392
|
+
parsed_key = JSON.parse(key_data)
|
|
393
|
+
required_fields = %w[type project_id private_key_id private_key client_email client_id]
|
|
394
|
+
missing_fields = required_fields.reject { |field| parsed_key.key?(field) }
|
|
395
|
+
|
|
396
|
+
if missing_fields.empty?
|
|
397
|
+
{ name: 'Service Account Test', status: 'passed', message: 'Service account key is valid JSON with all required fields' }
|
|
398
|
+
else
|
|
399
|
+
{ name: 'Service Account Test', status: 'failed', message: "Missing required fields: #{missing_fields.join(', ')}" }
|
|
400
|
+
end
|
|
401
|
+
rescue JSON::ParserError
|
|
402
|
+
{ name: 'Service Account Test', status: 'failed', message: 'Service account key is not valid JSON' }
|
|
403
|
+
end
|
|
404
|
+
else
|
|
405
|
+
client_email = credential[:client_email]
|
|
406
|
+
private_key = credential[:private_key]
|
|
407
|
+
|
|
408
|
+
return { name: 'Service Account Test', status: 'failed', message: 'Client email is missing' } unless client_email
|
|
409
|
+
return { name: 'Service Account Test', status: 'failed', message: 'Private key is missing' } unless private_key
|
|
410
|
+
|
|
411
|
+
if private_key.include?('BEGIN PRIVATE KEY')
|
|
412
|
+
{ name: 'Service Account Test', status: 'passed', message: 'Service account credentials are properly formatted' }
|
|
413
|
+
else
|
|
414
|
+
{ name: 'Service Account Test', status: 'failed', message: 'Private key does not appear to be in PEM format' }
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Test bearer token credential
|
|
420
|
+
def test_bearer_credential(credential, options = {})
|
|
421
|
+
bearer_token = credential[:bearer_token]
|
|
422
|
+
return { name: 'Bearer Token Test', status: 'failed', message: 'Bearer token is missing' } unless bearer_token
|
|
423
|
+
|
|
424
|
+
if options[:test_url]
|
|
425
|
+
begin
|
|
426
|
+
require 'net/http'
|
|
427
|
+
Legate::Auth::UrlGuard.validate!(options[:test_url], label: 'Test URL')
|
|
428
|
+
uri = URI(options[:test_url])
|
|
429
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
430
|
+
http.use_ssl = uri.scheme == 'https'
|
|
431
|
+
http.read_timeout = 10
|
|
432
|
+
|
|
433
|
+
request = Net::HTTP::Get.new(uri)
|
|
434
|
+
request['Authorization'] = "Bearer #{bearer_token}"
|
|
435
|
+
|
|
436
|
+
response = http.request(request)
|
|
437
|
+
|
|
438
|
+
if response.code.to_i < 400
|
|
439
|
+
{ name: 'Bearer Token Test', status: 'passed', message: "Bearer token accepted (HTTP #{response.code})" }
|
|
440
|
+
else
|
|
441
|
+
{ name: 'Bearer Token Test', status: 'failed', message: "Bearer token rejected (HTTP #{response.code})" }
|
|
442
|
+
end
|
|
443
|
+
rescue StandardError => e
|
|
444
|
+
{ name: 'Bearer Token Test', status: 'failed', message: "Network error: #{e.message}" }
|
|
445
|
+
end
|
|
446
|
+
else
|
|
447
|
+
{ name: 'Bearer Token Test', status: 'passed', message: 'Bearer token format is valid (no test URL provided)' }
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Helper to test API calls with authentication
|
|
452
|
+
def test_authenticated_api_call(url, scheme_name, credential_name, _options = {})
|
|
453
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
454
|
+
scheme = auth_manager.get_scheme(scheme_name.to_sym)
|
|
455
|
+
credential = auth_manager.get_credential(credential_name.to_sym)
|
|
456
|
+
|
|
457
|
+
return { success: false, error: 'Scheme not found' } unless scheme
|
|
458
|
+
return { success: false, error: 'Credential not found' } unless credential
|
|
459
|
+
return { success: false, error: 'Scheme and credential are not compatible' } unless mapping_scheme_credential_compatible?(scheme, credential)
|
|
460
|
+
|
|
461
|
+
begin
|
|
462
|
+
require 'net/http'
|
|
463
|
+
Legate::Auth::UrlGuard.validate!(url, label: 'Test URL')
|
|
464
|
+
uri = URI(url)
|
|
465
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
466
|
+
http.use_ssl = uri.scheme == 'https'
|
|
467
|
+
http.read_timeout = 15
|
|
468
|
+
|
|
469
|
+
request = Net::HTTP::Get.new(uri)
|
|
470
|
+
|
|
471
|
+
# Apply authentication based on scheme type
|
|
472
|
+
case scheme.scheme_type
|
|
473
|
+
when :api_key
|
|
474
|
+
location = credential[:location] || 'header'
|
|
475
|
+
key_name = credential[:name] || 'X-API-Key'
|
|
476
|
+
api_key = credential[:api_key]
|
|
477
|
+
|
|
478
|
+
case location
|
|
479
|
+
when 'header'
|
|
480
|
+
request[key_name] = api_key
|
|
481
|
+
when 'query'
|
|
482
|
+
uri.query = "#{key_name}=#{api_key}"
|
|
483
|
+
request = Net::HTTP::Get.new(uri)
|
|
484
|
+
end
|
|
485
|
+
when :http_bearer
|
|
486
|
+
request['Authorization'] = "Bearer #{credential[:bearer_token]}"
|
|
487
|
+
when :oauth2, :oidc
|
|
488
|
+
# For OAuth2, we'd need an access token, which requires a full flow
|
|
489
|
+
return { success: false, error: 'OAuth2 testing requires a complete authentication flow' }
|
|
490
|
+
when :service_account, :google_service_account
|
|
491
|
+
# Service account testing would require token generation
|
|
492
|
+
return { success: false, error: 'Service account testing requires token generation' }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
response = http.request(request)
|
|
496
|
+
|
|
497
|
+
{
|
|
498
|
+
success: true,
|
|
499
|
+
status_code: response.code.to_i,
|
|
500
|
+
status_message: response.message,
|
|
501
|
+
headers: response.to_hash,
|
|
502
|
+
body_preview: response.body&.slice(0, 500),
|
|
503
|
+
authenticated: response.code.to_i < 400
|
|
504
|
+
}
|
|
505
|
+
rescue StandardError => e
|
|
506
|
+
{ success: false, error: "Network error: #{e.message}" }
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# GET /auth - Main authentication management dashboard
|
|
512
|
+
app.get '/auth' do
|
|
513
|
+
logger.info('GET /auth route handler entered (from AuthenticationRoutes)')
|
|
514
|
+
|
|
515
|
+
# Access the authentication manager
|
|
516
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
517
|
+
|
|
518
|
+
# Get basic counts for dashboard
|
|
519
|
+
schemes_count = auth_manager.schemes&.size || 0
|
|
520
|
+
credentials_count = auth_manager.credentials&.size || 0
|
|
521
|
+
mappings_count = auth_manager.url_mappings&.size || 0
|
|
522
|
+
|
|
523
|
+
# Set instance variables for the view
|
|
524
|
+
instance_variable_set(:@auth_manager_available, true)
|
|
525
|
+
instance_variable_set(:@schemes_count, schemes_count)
|
|
526
|
+
instance_variable_set(:@credentials_count, credentials_count)
|
|
527
|
+
instance_variable_set(:@mappings_count, mappings_count)
|
|
528
|
+
|
|
529
|
+
slim :auth
|
|
530
|
+
rescue StandardError => e
|
|
531
|
+
logger.error("Error in /auth route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
532
|
+
instance_variable_set(:@auth_manager_available, false)
|
|
533
|
+
instance_variable_set(:@error_message, e.message)
|
|
534
|
+
slim :auth
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# GET /auth/schemes - List all available authentication schemes
|
|
538
|
+
app.get '/auth/schemes' do
|
|
539
|
+
logger.info('GET /auth/schemes route handler entered (from AuthenticationRoutes)')
|
|
540
|
+
content_type :html
|
|
541
|
+
|
|
542
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
543
|
+
schemes = auth_manager.schemes || {}
|
|
544
|
+
credentials = auth_manager.credentials || {}
|
|
545
|
+
url_mappings = auth_manager.url_mappings || []
|
|
546
|
+
|
|
547
|
+
# Convert schemes to a more view-friendly format with usage information
|
|
548
|
+
schemes_data = schemes.map do |name, scheme|
|
|
549
|
+
# Find compatible credentials
|
|
550
|
+
compatible_credentials = credentials.select do |cred_name, credential|
|
|
551
|
+
auth_manager.send(:credential_compatible_with_scheme?, credential, scheme)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Find URL mappings using this scheme
|
|
555
|
+
scheme_mappings = url_mappings.select { |mapping| mapping[:scheme_name] == name }
|
|
556
|
+
|
|
557
|
+
{
|
|
558
|
+
name: name,
|
|
559
|
+
scheme_type: scheme.scheme_type,
|
|
560
|
+
class_name: scheme.class.name.split('::').last,
|
|
561
|
+
description: get_scheme_description(scheme),
|
|
562
|
+
compatible_credentials_count: compatible_credentials.size,
|
|
563
|
+
url_mappings_count: scheme_mappings.size,
|
|
564
|
+
config_fields: get_scheme_config_fields(scheme.scheme_type),
|
|
565
|
+
has_config: !get_scheme_config_fields(scheme.scheme_type).empty?
|
|
566
|
+
}
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
instance_variable_set(:@schemes, schemes_data)
|
|
570
|
+
slim :auth_schemes
|
|
571
|
+
rescue StandardError => e
|
|
572
|
+
logger.error("Error in /auth/schemes route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
573
|
+
halt 500, "Error loading authentication schemes: #{e.message}"
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# GET /auth/schemes/:name - Individual scheme details and configuration
|
|
577
|
+
app.get '/auth/schemes/:name' do
|
|
578
|
+
logger.info("GET /auth/schemes/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
579
|
+
content_type :html
|
|
580
|
+
|
|
581
|
+
scheme_name = params[:name].to_sym
|
|
582
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
583
|
+
scheme = auth_manager.get_scheme(scheme_name)
|
|
584
|
+
|
|
585
|
+
halt 404, "Scheme not found: #{Rack::Utils.escape_html(params[:name])}" unless scheme
|
|
586
|
+
|
|
587
|
+
credentials = auth_manager.credentials || {}
|
|
588
|
+
url_mappings = auth_manager.url_mappings || []
|
|
589
|
+
|
|
590
|
+
# Find compatible credentials
|
|
591
|
+
compatible_credentials = credentials.select do |cred_name, credential|
|
|
592
|
+
auth_manager.send(:credential_compatible_with_scheme?, credential, scheme)
|
|
593
|
+
end.map do |cred_name, credential|
|
|
594
|
+
{
|
|
595
|
+
name: cred_name,
|
|
596
|
+
auth_type: credential.auth_type,
|
|
597
|
+
description: get_credential_description(credential),
|
|
598
|
+
masked_info: get_masked_credential_info(credential)
|
|
599
|
+
}
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Find URL mappings using this scheme
|
|
603
|
+
scheme_mappings = url_mappings.select { |mapping| mapping[:scheme_name] == scheme_name }
|
|
604
|
+
.map.with_index do |mapping, index|
|
|
605
|
+
{
|
|
606
|
+
id: index,
|
|
607
|
+
pattern: mapping[:pattern].is_a?(Regexp) ? mapping[:pattern].source : mapping[:pattern].to_s,
|
|
608
|
+
pattern_type: mapping[:pattern].is_a?(Regexp) ? 'regex' : 'string',
|
|
609
|
+
credential_name: mapping[:credential_name]
|
|
610
|
+
}
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
scheme_data = {
|
|
614
|
+
name: scheme_name,
|
|
615
|
+
scheme_type: scheme.scheme_type,
|
|
616
|
+
class_name: scheme.class.name.split('::').last,
|
|
617
|
+
description: get_scheme_description(scheme),
|
|
618
|
+
config_fields: get_scheme_config_fields(scheme.scheme_type),
|
|
619
|
+
current_config: get_scheme_current_config(scheme),
|
|
620
|
+
compatible_credential_types: get_compatible_credential_types(scheme.scheme_type),
|
|
621
|
+
compatible_credentials: compatible_credentials,
|
|
622
|
+
url_mappings: scheme_mappings
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
instance_variable_set(:@scheme, scheme_data)
|
|
626
|
+
slim :auth_scheme_detail
|
|
627
|
+
rescue StandardError => e
|
|
628
|
+
logger.error("Error in /auth/schemes/#{params[:name]} route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
629
|
+
halt 500, "Error loading scheme details: #{e.message}"
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# POST /auth/schemes - Register new scheme instance
|
|
633
|
+
app.post '/auth/schemes' do
|
|
634
|
+
logger.info('POST /auth/schemes route handler entered (from AuthenticationRoutes)')
|
|
635
|
+
content_type :json
|
|
636
|
+
|
|
637
|
+
scheme_type = params[:scheme_type]&.to_sym
|
|
638
|
+
scheme_name = params[:scheme_name]&.to_sym
|
|
639
|
+
|
|
640
|
+
halt 400, { error: 'Scheme type is required' }.to_json unless scheme_type
|
|
641
|
+
halt 400, { error: 'Scheme name is required' }.to_json unless scheme_name
|
|
642
|
+
|
|
643
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
644
|
+
|
|
645
|
+
# Check if scheme name already exists
|
|
646
|
+
halt 400, { error: "Scheme with name '#{scheme_name}' already exists" }.to_json if auth_manager.get_scheme(scheme_name)
|
|
647
|
+
|
|
648
|
+
begin
|
|
649
|
+
# Create new scheme instance based on type
|
|
650
|
+
scheme = case scheme_type
|
|
651
|
+
when :api_key
|
|
652
|
+
Legate::Auth::Schemes::ApiKey.new
|
|
653
|
+
when :http_bearer
|
|
654
|
+
Legate::Auth::Schemes::HTTPBearer.new
|
|
655
|
+
when :oauth2
|
|
656
|
+
Legate::Auth::Schemes::OAuth2.new(
|
|
657
|
+
authorization_url: params[:authorization_url],
|
|
658
|
+
token_url: params[:token_url],
|
|
659
|
+
scopes: params[:scopes]&.split(/\s+/),
|
|
660
|
+
use_pkce: params[:use_pkce] == 'true',
|
|
661
|
+
revocation_url: params[:revocation_url]
|
|
662
|
+
)
|
|
663
|
+
when :oidc, :openid_connect
|
|
664
|
+
Legate::Auth::Schemes::OpenIDConnect.new(
|
|
665
|
+
authorization_url: params[:authorization_url],
|
|
666
|
+
token_url: params[:token_url],
|
|
667
|
+
userinfo_url: params[:userinfo_url],
|
|
668
|
+
scopes: params[:scopes]&.split(/\s+/),
|
|
669
|
+
use_pkce: params[:use_pkce] == 'true'
|
|
670
|
+
)
|
|
671
|
+
when :service_account
|
|
672
|
+
Legate::Auth::Schemes::ServiceAccount.new(
|
|
673
|
+
token_url: params[:token_url],
|
|
674
|
+
scopes: params[:scopes]&.split(/\s+/)
|
|
675
|
+
)
|
|
676
|
+
when :google_service_account
|
|
677
|
+
Legate::Auth::Schemes::GoogleServiceAccount.new(
|
|
678
|
+
scopes: params[:scopes]&.split(/\s+/)
|
|
679
|
+
)
|
|
680
|
+
else
|
|
681
|
+
halt 400, { error: "Unsupported scheme type: #{scheme_type}" }.to_json
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Register the new scheme
|
|
685
|
+
auth_manager.register_scheme(scheme, scheme_name)
|
|
686
|
+
|
|
687
|
+
logger.info("Successfully registered new scheme '#{scheme_name}' of type '#{scheme_type}'")
|
|
688
|
+
{ success: true, message: "Scheme '#{scheme_name}' registered successfully" }.to_json
|
|
689
|
+
rescue StandardError => e
|
|
690
|
+
logger.error("Error registering scheme '#{scheme_name}': #{e.class} - #{e.message}")
|
|
691
|
+
halt 500, { error: "Failed to register scheme: #{e.message}" }.to_json
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# PUT /auth/schemes/:name - Update scheme configuration
|
|
696
|
+
app.put '/auth/schemes/:name' do
|
|
697
|
+
logger.info("PUT /auth/schemes/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
698
|
+
content_type :json
|
|
699
|
+
|
|
700
|
+
scheme_name = params[:name].to_sym
|
|
701
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
702
|
+
existing_scheme = auth_manager.get_scheme(scheme_name)
|
|
703
|
+
|
|
704
|
+
halt 404, { error: "Scheme not found: #{params[:name]}" }.to_json unless existing_scheme
|
|
705
|
+
|
|
706
|
+
begin
|
|
707
|
+
# Create updated scheme instance with new configuration
|
|
708
|
+
scheme_type = existing_scheme.scheme_type
|
|
709
|
+
updated_scheme = case scheme_type
|
|
710
|
+
when :oauth2
|
|
711
|
+
Legate::Auth::Schemes::OAuth2.new(
|
|
712
|
+
authorization_url: params[:authorization_url],
|
|
713
|
+
token_url: params[:token_url],
|
|
714
|
+
scopes: params[:scopes]&.split(/\s+/),
|
|
715
|
+
use_pkce: params[:use_pkce] == 'true',
|
|
716
|
+
revocation_url: params[:revocation_url]
|
|
717
|
+
)
|
|
718
|
+
when :oidc, :openid_connect
|
|
719
|
+
Legate::Auth::Schemes::OpenIDConnect.new(
|
|
720
|
+
authorization_url: params[:authorization_url],
|
|
721
|
+
token_url: params[:token_url],
|
|
722
|
+
userinfo_url: params[:userinfo_url],
|
|
723
|
+
scopes: params[:scopes]&.split(/\s+/),
|
|
724
|
+
use_pkce: params[:use_pkce] == 'true'
|
|
725
|
+
)
|
|
726
|
+
when :service_account
|
|
727
|
+
Legate::Auth::Schemes::ServiceAccount.new(
|
|
728
|
+
token_url: params[:token_url],
|
|
729
|
+
scopes: params[:scopes]&.split(/\s+/)
|
|
730
|
+
)
|
|
731
|
+
when :google_service_account
|
|
732
|
+
Legate::Auth::Schemes::GoogleServiceAccount.new(
|
|
733
|
+
scopes: params[:scopes]&.split(/\s+/)
|
|
734
|
+
)
|
|
735
|
+
else
|
|
736
|
+
halt 400, { error: "Scheme type '#{scheme_type}' does not support configuration updates" }.to_json
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Replace the existing scheme
|
|
740
|
+
auth_manager.register_scheme(updated_scheme, scheme_name)
|
|
741
|
+
|
|
742
|
+
logger.info("Successfully updated scheme '#{scheme_name}' configuration")
|
|
743
|
+
{ success: true, message: "Scheme '#{scheme_name}' updated successfully" }.to_json
|
|
744
|
+
rescue StandardError => e
|
|
745
|
+
logger.error("Error updating scheme '#{scheme_name}': #{e.class} - #{e.message}")
|
|
746
|
+
halt 500, { error: "Failed to update scheme: #{e.message}" }.to_json
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# DELETE /auth/schemes/:name - Remove scheme instance
|
|
751
|
+
app.delete '/auth/schemes/:name' do
|
|
752
|
+
logger.info("DELETE /auth/schemes/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
753
|
+
content_type :json
|
|
754
|
+
|
|
755
|
+
scheme_name = params[:name].to_sym
|
|
756
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
757
|
+
scheme = auth_manager.get_scheme(scheme_name)
|
|
758
|
+
|
|
759
|
+
halt 404, { error: "Scheme not found: #{params[:name]}" }.to_json unless scheme
|
|
760
|
+
|
|
761
|
+
# Check if scheme is used in URL mappings
|
|
762
|
+
url_mappings = auth_manager.url_mappings || []
|
|
763
|
+
dependent_mappings = url_mappings.select { |mapping| mapping[:scheme_name] == scheme_name }
|
|
764
|
+
|
|
765
|
+
if dependent_mappings.any?
|
|
766
|
+
mapping_patterns = dependent_mappings.map { |m| m[:pattern] }.join(', ')
|
|
767
|
+
halt 400, {
|
|
768
|
+
error: "Cannot delete scheme '#{scheme_name}' - it is used by URL mappings: #{mapping_patterns}"
|
|
769
|
+
}.to_json
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
begin
|
|
773
|
+
# Remove the scheme from the manager (persists via the store)
|
|
774
|
+
auth_manager.unregister_scheme(scheme_name)
|
|
775
|
+
|
|
776
|
+
logger.info("Successfully deleted scheme '#{scheme_name}'")
|
|
777
|
+
{ success: true, message: "Scheme '#{scheme_name}' deleted successfully" }.to_json
|
|
778
|
+
rescue StandardError => e
|
|
779
|
+
logger.error("Error deleting scheme '#{scheme_name}': #{e.class} - #{e.message}")
|
|
780
|
+
halt 500, { error: "Failed to delete scheme: #{e.message}" }.to_json
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
# GET /auth/credentials - List all configured credentials
|
|
785
|
+
app.get '/auth/credentials' do
|
|
786
|
+
logger.info('GET /auth/credentials route handler entered (from AuthenticationRoutes)')
|
|
787
|
+
content_type :html
|
|
788
|
+
|
|
789
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
790
|
+
credentials = auth_manager.credentials || {}
|
|
791
|
+
url_mappings = auth_manager.url_mappings || []
|
|
792
|
+
|
|
793
|
+
# Convert credentials to a view-friendly format with usage information
|
|
794
|
+
credentials_data = credentials.map do |name, credential|
|
|
795
|
+
# Find URL mappings using this credential
|
|
796
|
+
credential_mappings = url_mappings.select { |mapping| mapping[:credential_name] == name }
|
|
797
|
+
|
|
798
|
+
# Find compatible schemes
|
|
799
|
+
compatible_schemes = get_compatible_schemes_for_credential(credential.auth_type)
|
|
800
|
+
|
|
801
|
+
{
|
|
802
|
+
name: name,
|
|
803
|
+
auth_type: credential.auth_type,
|
|
804
|
+
description: get_credential_description(credential),
|
|
805
|
+
masked_info: get_masked_credential_info(credential),
|
|
806
|
+
url_mappings_count: credential_mappings.size,
|
|
807
|
+
compatible_schemes_count: compatible_schemes.size,
|
|
808
|
+
config_fields: get_credential_config_fields(credential.auth_type)
|
|
809
|
+
}
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
instance_variable_set(:@credentials, credentials_data)
|
|
813
|
+
slim :auth_credentials
|
|
814
|
+
rescue StandardError => e
|
|
815
|
+
logger.error("Error in /auth/credentials route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
816
|
+
halt 500, "Error loading authentication credentials: #{e.message}"
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# GET /auth/credentials/:name - Individual credential details and configuration
|
|
820
|
+
app.get '/auth/credentials/:name' do
|
|
821
|
+
logger.info("GET /auth/credentials/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
822
|
+
content_type :html
|
|
823
|
+
|
|
824
|
+
credential_name = params[:name].to_sym
|
|
825
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
826
|
+
credential = auth_manager.get_credential(credential_name)
|
|
827
|
+
|
|
828
|
+
halt 404, "Credential not found: #{Rack::Utils.escape_html(params[:name])}" unless credential
|
|
829
|
+
|
|
830
|
+
url_mappings = auth_manager.url_mappings || []
|
|
831
|
+
|
|
832
|
+
# Find URL mappings using this credential
|
|
833
|
+
credential_mappings = url_mappings.select { |mapping| mapping[:credential_name] == credential_name }
|
|
834
|
+
.map.with_index do |mapping, index|
|
|
835
|
+
{
|
|
836
|
+
id: index,
|
|
837
|
+
pattern: mapping[:pattern].is_a?(Regexp) ? mapping[:pattern].source : mapping[:pattern].to_s,
|
|
838
|
+
pattern_type: mapping[:pattern].is_a?(Regexp) ? 'regex' : 'string',
|
|
839
|
+
scheme_name: mapping[:scheme_name]
|
|
840
|
+
}
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
# Find compatible schemes
|
|
844
|
+
compatible_schemes = get_compatible_schemes_for_credential(credential.auth_type)
|
|
845
|
+
|
|
846
|
+
credential_data = {
|
|
847
|
+
name: credential_name,
|
|
848
|
+
auth_type: credential.auth_type,
|
|
849
|
+
description: get_credential_description(credential),
|
|
850
|
+
config_fields: get_credential_config_fields(credential.auth_type),
|
|
851
|
+
current_config: get_credential_current_config(credential),
|
|
852
|
+
compatible_schemes: compatible_schemes,
|
|
853
|
+
url_mappings: credential_mappings
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
instance_variable_set(:@credential, credential_data)
|
|
857
|
+
slim :auth_credential_detail
|
|
858
|
+
rescue StandardError => e
|
|
859
|
+
logger.error("Error in /auth/credentials/#{params[:name]} route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
860
|
+
halt 500, "Error loading credential details: #{e.message}"
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
# POST /auth/credentials - Create new credential
|
|
864
|
+
app.post '/auth/credentials' do
|
|
865
|
+
logger.info('POST /auth/credentials route handler entered (from AuthenticationRoutes)')
|
|
866
|
+
content_type :json
|
|
867
|
+
|
|
868
|
+
auth_type = params[:auth_type]&.to_sym
|
|
869
|
+
credential_name = params[:credential_name]&.to_sym
|
|
870
|
+
|
|
871
|
+
halt 400, { error: 'Credential type is required' }.to_json unless auth_type
|
|
872
|
+
halt 400, { error: 'Credential name is required' }.to_json unless credential_name
|
|
873
|
+
|
|
874
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
875
|
+
|
|
876
|
+
# Check if credential name already exists
|
|
877
|
+
halt 400, { error: "Credential with name '#{credential_name}' already exists" }.to_json if auth_manager.get_credential(credential_name)
|
|
878
|
+
|
|
879
|
+
begin
|
|
880
|
+
# Build credential attributes based on type
|
|
881
|
+
credential_attrs = { auth_type: auth_type }
|
|
882
|
+
|
|
883
|
+
case auth_type
|
|
884
|
+
when :api_key
|
|
885
|
+
credential_attrs[:api_key] = params[:api_key]
|
|
886
|
+
credential_attrs[:location] = params[:location] if params[:location] && !params[:location].empty?
|
|
887
|
+
credential_attrs[:name] = params[:name] if params[:name] && !params[:name].empty?
|
|
888
|
+
when :http_bearer
|
|
889
|
+
credential_attrs[:bearer_token] = params[:bearer_token]
|
|
890
|
+
when :oauth2, :oidc
|
|
891
|
+
credential_attrs[:client_id] = params[:client_id]
|
|
892
|
+
credential_attrs[:client_secret] = params[:client_secret]
|
|
893
|
+
credential_attrs[:redirect_uri] = params[:redirect_uri] if params[:redirect_uri] && !params[:redirect_uri].empty?
|
|
894
|
+
credential_attrs[:scopes] = params[:scopes] if params[:scopes] && !params[:scopes].empty?
|
|
895
|
+
when :service_account
|
|
896
|
+
credential_attrs[:client_email] = params[:client_email]
|
|
897
|
+
credential_attrs[:private_key] = params[:private_key]
|
|
898
|
+
credential_attrs[:project_id] = params[:project_id] if params[:project_id] && !params[:project_id].empty?
|
|
899
|
+
when :google_service_account
|
|
900
|
+
credential_attrs[:service_account_key] = params[:service_account_key]
|
|
901
|
+
when :basic
|
|
902
|
+
credential_attrs[:username] = params[:username]
|
|
903
|
+
credential_attrs[:password] = params[:password]
|
|
904
|
+
else
|
|
905
|
+
halt 400, { error: "Unsupported credential type: #{auth_type}" }.to_json
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
# Create new credential
|
|
909
|
+
credential = Legate::Auth::Credential.new(**credential_attrs)
|
|
910
|
+
|
|
911
|
+
# Register the new credential
|
|
912
|
+
auth_manager.register_credential(credential, credential_name)
|
|
913
|
+
|
|
914
|
+
logger.info("Successfully registered new credential '#{credential_name}' of type '#{auth_type}'")
|
|
915
|
+
{ success: true, message: "Credential '#{credential_name}' created successfully" }.to_json
|
|
916
|
+
rescue Legate::Auth::CredentialError => e
|
|
917
|
+
logger.error("Credential validation error for '#{credential_name}': #{e.message}")
|
|
918
|
+
halt 400, { error: "Invalid credential: #{e.message}" }.to_json
|
|
919
|
+
rescue StandardError => e
|
|
920
|
+
logger.error("Error creating credential '#{credential_name}': #{e.class} - #{e.message}")
|
|
921
|
+
halt 500, { error: "Failed to create credential: #{e.message}" }.to_json
|
|
922
|
+
end
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# PUT /auth/credentials/:name - Update existing credential
|
|
926
|
+
app.put '/auth/credentials/:name' do
|
|
927
|
+
logger.info("PUT /auth/credentials/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
928
|
+
content_type :json
|
|
929
|
+
|
|
930
|
+
credential_name = params[:name].to_sym
|
|
931
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
932
|
+
existing_credential = auth_manager.get_credential(credential_name)
|
|
933
|
+
|
|
934
|
+
halt 404, { error: "Credential not found: #{params[:name]}" }.to_json unless existing_credential
|
|
935
|
+
|
|
936
|
+
begin
|
|
937
|
+
# Build updated credential attributes
|
|
938
|
+
auth_type = existing_credential.auth_type
|
|
939
|
+
credential_attrs = { auth_type: auth_type }
|
|
940
|
+
|
|
941
|
+
case auth_type
|
|
942
|
+
when :api_key
|
|
943
|
+
credential_attrs[:api_key] = params[:api_key]
|
|
944
|
+
credential_attrs[:location] = params[:location] if params[:location] && !params[:location].empty?
|
|
945
|
+
credential_attrs[:name] = params[:name] if params[:name] && !params[:name].empty?
|
|
946
|
+
when :http_bearer
|
|
947
|
+
credential_attrs[:bearer_token] = params[:bearer_token]
|
|
948
|
+
when :oauth2, :oidc
|
|
949
|
+
credential_attrs[:client_id] = params[:client_id]
|
|
950
|
+
credential_attrs[:client_secret] = params[:client_secret]
|
|
951
|
+
credential_attrs[:redirect_uri] = params[:redirect_uri] if params[:redirect_uri] && !params[:redirect_uri].empty?
|
|
952
|
+
credential_attrs[:scopes] = params[:scopes] if params[:scopes] && !params[:scopes].empty?
|
|
953
|
+
when :service_account
|
|
954
|
+
credential_attrs[:client_email] = params[:client_email]
|
|
955
|
+
credential_attrs[:private_key] = params[:private_key]
|
|
956
|
+
credential_attrs[:project_id] = params[:project_id] if params[:project_id] && !params[:project_id].empty?
|
|
957
|
+
when :google_service_account
|
|
958
|
+
credential_attrs[:service_account_key] = params[:service_account_key]
|
|
959
|
+
when :basic
|
|
960
|
+
credential_attrs[:username] = params[:username]
|
|
961
|
+
credential_attrs[:password] = params[:password]
|
|
962
|
+
else
|
|
963
|
+
halt 400, { error: "Credential type '#{auth_type}' does not support updates" }.to_json
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
# Create updated credential
|
|
967
|
+
updated_credential = Legate::Auth::Credential.new(**credential_attrs)
|
|
968
|
+
|
|
969
|
+
# Replace the existing credential
|
|
970
|
+
auth_manager.register_credential(updated_credential, credential_name)
|
|
971
|
+
|
|
972
|
+
logger.info("Successfully updated credential '#{credential_name}'")
|
|
973
|
+
{ success: true, message: "Credential '#{credential_name}' updated successfully" }.to_json
|
|
974
|
+
rescue Legate::Auth::CredentialError => e
|
|
975
|
+
logger.error("Credential validation error for '#{credential_name}': #{e.message}")
|
|
976
|
+
halt 400, { error: "Invalid credential: #{e.message}" }.to_json
|
|
977
|
+
rescue StandardError => e
|
|
978
|
+
logger.error("Error updating credential '#{credential_name}': #{e.class} - #{e.message}")
|
|
979
|
+
halt 500, { error: "Failed to update credential: #{e.message}" }.to_json
|
|
980
|
+
end
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
# DELETE /auth/credentials/:name - Remove credential
|
|
984
|
+
app.delete '/auth/credentials/:name' do
|
|
985
|
+
logger.info("DELETE /auth/credentials/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
986
|
+
content_type :json
|
|
987
|
+
|
|
988
|
+
credential_name = params[:name].to_sym
|
|
989
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
990
|
+
credential = auth_manager.get_credential(credential_name)
|
|
991
|
+
|
|
992
|
+
halt 404, { error: "Credential not found: #{params[:name]}" }.to_json unless credential
|
|
993
|
+
|
|
994
|
+
# Check if credential is used in URL mappings
|
|
995
|
+
url_mappings = auth_manager.url_mappings || []
|
|
996
|
+
dependent_mappings = url_mappings.select { |mapping| mapping[:credential_name] == credential_name }
|
|
997
|
+
|
|
998
|
+
if dependent_mappings.any?
|
|
999
|
+
mapping_patterns = dependent_mappings.map { |m| m[:pattern] }.join(', ')
|
|
1000
|
+
halt 400, {
|
|
1001
|
+
error: "Cannot delete credential '#{credential_name}' - it is used by URL mappings: #{mapping_patterns}"
|
|
1002
|
+
}.to_json
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
begin
|
|
1006
|
+
# Remove the credential from the manager (persists via the store)
|
|
1007
|
+
auth_manager.unregister_credential(credential_name)
|
|
1008
|
+
|
|
1009
|
+
logger.info("Successfully deleted credential '#{credential_name}'")
|
|
1010
|
+
{ success: true, message: "Credential '#{credential_name}' deleted successfully" }.to_json
|
|
1011
|
+
rescue StandardError => e
|
|
1012
|
+
logger.error("Error deleting credential '#{credential_name}': #{e.class} - #{e.message}")
|
|
1013
|
+
halt 500, { error: "Failed to delete credential: #{e.message}" }.to_json
|
|
1014
|
+
end
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
# POST /auth/credentials/:name/test - Test credential validity
|
|
1018
|
+
app.post '/auth/credentials/:name/test' do
|
|
1019
|
+
logger.info("POST /auth/credentials/#{params[:name]}/test route handler entered (from AuthenticationRoutes)")
|
|
1020
|
+
content_type :json
|
|
1021
|
+
|
|
1022
|
+
credential_name = params[:name].to_sym
|
|
1023
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1024
|
+
credential = auth_manager.get_credential(credential_name)
|
|
1025
|
+
|
|
1026
|
+
halt 404, { error: "Credential not found: #{params[:name]}" }.to_json unless credential
|
|
1027
|
+
|
|
1028
|
+
begin
|
|
1029
|
+
# Basic validation - check if credential can resolve environment variables
|
|
1030
|
+
test_result = { success: true, tests: [] }
|
|
1031
|
+
|
|
1032
|
+
# Test 1: Environment variable resolution
|
|
1033
|
+
begin
|
|
1034
|
+
credential.to_h(resolve_env: true)
|
|
1035
|
+
test_result[:tests] << {
|
|
1036
|
+
name: 'Environment Variable Resolution',
|
|
1037
|
+
status: 'passed',
|
|
1038
|
+
message: 'All environment variables resolved successfully'
|
|
1039
|
+
}
|
|
1040
|
+
rescue Legate::Auth::EnvironmentVariableNotFoundError => e
|
|
1041
|
+
test_result[:success] = false
|
|
1042
|
+
test_result[:tests] << {
|
|
1043
|
+
name: 'Environment Variable Resolution',
|
|
1044
|
+
status: 'failed',
|
|
1045
|
+
message: "Environment variable not found: #{e.message}"
|
|
1046
|
+
}
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
# Test 2: Required fields validation
|
|
1050
|
+
begin
|
|
1051
|
+
# Create a new credential with the same attributes to validate
|
|
1052
|
+
Legate::Auth::Credential.new(**credential.to_h(resolve_env: false))
|
|
1053
|
+
test_result[:tests] << {
|
|
1054
|
+
name: 'Required Fields Validation',
|
|
1055
|
+
status: 'passed',
|
|
1056
|
+
message: 'All required fields are present'
|
|
1057
|
+
}
|
|
1058
|
+
rescue Legate::Auth::CredentialError => e
|
|
1059
|
+
test_result[:success] = false
|
|
1060
|
+
test_result[:tests] << {
|
|
1061
|
+
name: 'Required Fields Validation',
|
|
1062
|
+
status: 'failed',
|
|
1063
|
+
message: e.message
|
|
1064
|
+
}
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Test 3: Format validation for specific types
|
|
1068
|
+
case credential.auth_type
|
|
1069
|
+
when :google_service_account
|
|
1070
|
+
begin
|
|
1071
|
+
key_data = credential[:service_account_key]
|
|
1072
|
+
if key_data
|
|
1073
|
+
JSON.parse(key_data)
|
|
1074
|
+
test_result[:tests] << {
|
|
1075
|
+
name: 'Service Account Key Format',
|
|
1076
|
+
status: 'passed',
|
|
1077
|
+
message: 'Service account key is valid JSON'
|
|
1078
|
+
}
|
|
1079
|
+
end
|
|
1080
|
+
rescue JSON::ParserError
|
|
1081
|
+
test_result[:success] = false
|
|
1082
|
+
test_result[:tests] << {
|
|
1083
|
+
name: 'Service Account Key Format',
|
|
1084
|
+
status: 'failed',
|
|
1085
|
+
message: 'Service account key is not valid JSON'
|
|
1086
|
+
}
|
|
1087
|
+
end
|
|
1088
|
+
when :service_account
|
|
1089
|
+
begin
|
|
1090
|
+
private_key = credential[:private_key]
|
|
1091
|
+
if private_key && !private_key.include?('BEGIN PRIVATE KEY')
|
|
1092
|
+
test_result[:success] = false
|
|
1093
|
+
test_result[:tests] << {
|
|
1094
|
+
name: 'Private Key Format',
|
|
1095
|
+
status: 'failed',
|
|
1096
|
+
message: 'Private key does not appear to be in PEM format'
|
|
1097
|
+
}
|
|
1098
|
+
else
|
|
1099
|
+
test_result[:tests] << {
|
|
1100
|
+
name: 'Private Key Format',
|
|
1101
|
+
status: 'passed',
|
|
1102
|
+
message: 'Private key appears to be in correct PEM format'
|
|
1103
|
+
}
|
|
1104
|
+
end
|
|
1105
|
+
rescue StandardError => e
|
|
1106
|
+
test_result[:success] = false
|
|
1107
|
+
test_result[:tests] << {
|
|
1108
|
+
name: 'Private Key Format',
|
|
1109
|
+
status: 'failed',
|
|
1110
|
+
message: "Private key validation error: #{e.message}"
|
|
1111
|
+
}
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
logger.info("Credential test completed for '#{credential_name}': #{test_result[:success] ? 'PASSED' : 'FAILED'}")
|
|
1116
|
+
test_result.to_json
|
|
1117
|
+
rescue StandardError => e
|
|
1118
|
+
logger.error("Error testing credential '#{credential_name}': #{e.class} - #{e.message}")
|
|
1119
|
+
halt 500, { error: "Failed to test credential: #{e.message}" }.to_json
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
# GET /auth/mappings - List all URL mappings
|
|
1124
|
+
app.get '/auth/mappings' do
|
|
1125
|
+
logger.info('GET /auth/mappings route handler entered (from AuthenticationRoutes)')
|
|
1126
|
+
content_type :html
|
|
1127
|
+
|
|
1128
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1129
|
+
mappings = auth_manager.url_mappings || []
|
|
1130
|
+
|
|
1131
|
+
# Convert mappings to a view-friendly format
|
|
1132
|
+
mappings_data = mappings.map.with_index do |mapping, index|
|
|
1133
|
+
{
|
|
1134
|
+
id: index,
|
|
1135
|
+
pattern: mapping[:pattern].is_a?(Regexp) ? mapping[:pattern].source : mapping[:pattern].to_s,
|
|
1136
|
+
pattern_type: mapping[:pattern].is_a?(Regexp) ? 'regex' : 'string',
|
|
1137
|
+
scheme_name: mapping[:scheme_name],
|
|
1138
|
+
credential_name: mapping[:credential_name]
|
|
1139
|
+
}
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
instance_variable_set(:@mappings, mappings_data)
|
|
1143
|
+
slim :auth_mappings
|
|
1144
|
+
rescue StandardError => e
|
|
1145
|
+
logger.error("Error in /auth/mappings route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
1146
|
+
halt 500, "Error loading URL mappings: #{e.message}"
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
# GET /auth/debug - Debug information about authentication state
|
|
1150
|
+
app.get '/auth/debug' do
|
|
1151
|
+
logger.info('GET /auth/debug route handler entered (from AuthenticationRoutes)')
|
|
1152
|
+
content_type :html
|
|
1153
|
+
|
|
1154
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1155
|
+
|
|
1156
|
+
# Gather debug information
|
|
1157
|
+
debug_info = {
|
|
1158
|
+
manager_class: auth_manager.class.name,
|
|
1159
|
+
schemes_registered: auth_manager.schemes&.keys || [],
|
|
1160
|
+
credentials_registered: auth_manager.credentials&.keys || [],
|
|
1161
|
+
url_mappings_count: auth_manager.url_mappings&.size || 0,
|
|
1162
|
+
manager_instance_id: auth_manager.object_id
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
instance_variable_set(:@debug_info, debug_info)
|
|
1166
|
+
slim :auth_debug
|
|
1167
|
+
rescue StandardError => e
|
|
1168
|
+
logger.error("Error in /auth/debug route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
1169
|
+
halt 500, "Error gathering debug information: #{e.message}"
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# GET /auth/test - Main testing dashboard
|
|
1173
|
+
app.get '/auth/test' do
|
|
1174
|
+
logger.info('GET /auth/test route handler entered (from AuthenticationRoutes)')
|
|
1175
|
+
content_type :html
|
|
1176
|
+
|
|
1177
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1178
|
+
schemes = auth_manager.schemes || {}
|
|
1179
|
+
credentials = auth_manager.credentials || {}
|
|
1180
|
+
mappings = auth_manager.url_mappings || []
|
|
1181
|
+
|
|
1182
|
+
# Convert to view-friendly format
|
|
1183
|
+
schemes_data = schemes.map do |name, scheme|
|
|
1184
|
+
{
|
|
1185
|
+
name: name,
|
|
1186
|
+
scheme_type: scheme.scheme_type,
|
|
1187
|
+
description: get_scheme_description(scheme)
|
|
1188
|
+
}
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
credentials_data = credentials.map do |name, credential|
|
|
1192
|
+
{
|
|
1193
|
+
name: name,
|
|
1194
|
+
auth_type: credential.auth_type,
|
|
1195
|
+
description: get_credential_description(credential)
|
|
1196
|
+
}
|
|
1197
|
+
end
|
|
1198
|
+
|
|
1199
|
+
instance_variable_set(:@schemes, schemes_data)
|
|
1200
|
+
instance_variable_set(:@credentials, credentials_data)
|
|
1201
|
+
instance_variable_set(:@mappings_count, mappings.size)
|
|
1202
|
+
slim :auth_test
|
|
1203
|
+
rescue StandardError => e
|
|
1204
|
+
logger.error("Error in /auth/test route (from AuthenticationRoutes): #{e.class} - #{e.message}")
|
|
1205
|
+
halt 500, "Error loading testing dashboard: #{e.message}"
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
# POST /auth/test/credential/:name - Test individual credential
|
|
1209
|
+
app.post '/auth/test/credential/:name' do
|
|
1210
|
+
logger.info("POST /auth/test/credential/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
1211
|
+
content_type :json
|
|
1212
|
+
|
|
1213
|
+
credential_name = params[:name].to_sym
|
|
1214
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1215
|
+
credential = auth_manager.get_credential(credential_name)
|
|
1216
|
+
|
|
1217
|
+
halt 404, { error: "Credential not found: #{params[:name]}" }.to_json unless credential
|
|
1218
|
+
|
|
1219
|
+
begin
|
|
1220
|
+
test_options = {}
|
|
1221
|
+
test_options[:test_url] = params[:test_url] if params[:test_url] && !params[:test_url].empty?
|
|
1222
|
+
|
|
1223
|
+
test_results = test_credential_functionality(credential, test_options)
|
|
1224
|
+
test_results[:credential_name] = credential_name.to_s
|
|
1225
|
+
|
|
1226
|
+
logger.info("Credential test completed for '#{credential_name}': #{test_results[:success] ? 'PASSED' : 'FAILED'}")
|
|
1227
|
+
test_results.to_json
|
|
1228
|
+
rescue StandardError => e
|
|
1229
|
+
logger.error("Error testing credential '#{credential_name}': #{e.class} - #{e.message}")
|
|
1230
|
+
halt 500, { error: "Failed to test credential: #{e.message}" }.to_json
|
|
1231
|
+
end
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
# POST /auth/test/scheme/:name - Test authentication scheme
|
|
1235
|
+
app.post '/auth/test/scheme/:name' do
|
|
1236
|
+
logger.info("POST /auth/test/scheme/#{params[:name]} route handler entered (from AuthenticationRoutes)")
|
|
1237
|
+
content_type :json
|
|
1238
|
+
|
|
1239
|
+
scheme_name = params[:name].to_sym
|
|
1240
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1241
|
+
scheme = auth_manager.get_scheme(scheme_name)
|
|
1242
|
+
|
|
1243
|
+
halt 404, { error: "Scheme not found: #{params[:name]}" }.to_json unless scheme
|
|
1244
|
+
|
|
1245
|
+
begin
|
|
1246
|
+
test_results = { success: true, tests: [], scheme_name: scheme_name.to_s }
|
|
1247
|
+
|
|
1248
|
+
# Test 1: Scheme configuration validation
|
|
1249
|
+
case scheme.scheme_type
|
|
1250
|
+
when :oauth2, :oidc, :openid_connect
|
|
1251
|
+
if scheme.respond_to?(:authorization_url) && scheme.authorization_url
|
|
1252
|
+
test_results[:tests] << {
|
|
1253
|
+
name: 'Authorization URL',
|
|
1254
|
+
status: 'passed',
|
|
1255
|
+
message: "Valid authorization URL: #{scheme.authorization_url}"
|
|
1256
|
+
}
|
|
1257
|
+
else
|
|
1258
|
+
test_results[:success] = false
|
|
1259
|
+
test_results[:tests] << {
|
|
1260
|
+
name: 'Authorization URL',
|
|
1261
|
+
status: 'failed',
|
|
1262
|
+
message: 'Authorization URL is missing or invalid'
|
|
1263
|
+
}
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
if scheme.respond_to?(:token_url) && scheme.token_url
|
|
1267
|
+
test_results[:tests] << {
|
|
1268
|
+
name: 'Token URL',
|
|
1269
|
+
status: 'passed',
|
|
1270
|
+
message: "Valid token URL: #{scheme.token_url}"
|
|
1271
|
+
}
|
|
1272
|
+
else
|
|
1273
|
+
test_results[:success] = false
|
|
1274
|
+
test_results[:tests] << {
|
|
1275
|
+
name: 'Token URL',
|
|
1276
|
+
status: 'failed',
|
|
1277
|
+
message: 'Token URL is missing or invalid'
|
|
1278
|
+
}
|
|
1279
|
+
end
|
|
1280
|
+
when :service_account, :google_service_account
|
|
1281
|
+
test_results[:tests] << if scheme.respond_to?(:token_url) && scheme.token_url
|
|
1282
|
+
{
|
|
1283
|
+
name: 'Token URL',
|
|
1284
|
+
status: 'passed',
|
|
1285
|
+
message: "Valid token URL: #{scheme.token_url}"
|
|
1286
|
+
}
|
|
1287
|
+
else
|
|
1288
|
+
{
|
|
1289
|
+
name: 'Token URL',
|
|
1290
|
+
status: 'skipped',
|
|
1291
|
+
message: 'No token URL configured (using default)'
|
|
1292
|
+
}
|
|
1293
|
+
end
|
|
1294
|
+
else
|
|
1295
|
+
test_results[:tests] << {
|
|
1296
|
+
name: 'Scheme Configuration',
|
|
1297
|
+
status: 'passed',
|
|
1298
|
+
message: "Scheme type #{scheme.scheme_type} requires no additional configuration"
|
|
1299
|
+
}
|
|
1300
|
+
end
|
|
1301
|
+
|
|
1302
|
+
# Test 2: Compatible credentials check
|
|
1303
|
+
credentials = auth_manager.credentials || {}
|
|
1304
|
+
compatible_credentials = credentials.select do |cred_name, credential|
|
|
1305
|
+
mapping_scheme_credential_compatible?(scheme, credential)
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
test_results[:tests] << if compatible_credentials.any?
|
|
1309
|
+
{
|
|
1310
|
+
name: 'Compatible Credentials',
|
|
1311
|
+
status: 'passed',
|
|
1312
|
+
message: "Found #{compatible_credentials.size} compatible credential(s)"
|
|
1313
|
+
}
|
|
1314
|
+
else
|
|
1315
|
+
{
|
|
1316
|
+
name: 'Compatible Credentials',
|
|
1317
|
+
status: 'warning',
|
|
1318
|
+
message: 'No compatible credentials found'
|
|
1319
|
+
}
|
|
1320
|
+
end
|
|
1321
|
+
|
|
1322
|
+
logger.info("Scheme test completed for '#{scheme_name}': #{test_results[:success] ? 'PASSED' : 'FAILED'}")
|
|
1323
|
+
test_results.to_json
|
|
1324
|
+
rescue StandardError => e
|
|
1325
|
+
logger.error("Error testing scheme '#{scheme_name}': #{e.class} - #{e.message}")
|
|
1326
|
+
halt 500, { error: "Failed to test scheme: #{e.message}" }.to_json
|
|
1327
|
+
end
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
# POST /auth/test/api - Test API call with authentication
|
|
1331
|
+
app.post '/auth/test/api' do
|
|
1332
|
+
logger.info('POST /auth/test/api route handler entered (from AuthenticationRoutes)')
|
|
1333
|
+
content_type :json
|
|
1334
|
+
|
|
1335
|
+
url = params[:url]
|
|
1336
|
+
scheme_name = params[:scheme_name]
|
|
1337
|
+
credential_name = params[:credential_name]
|
|
1338
|
+
|
|
1339
|
+
halt 400, { error: 'URL is required' }.to_json unless url && !url.empty?
|
|
1340
|
+
halt 400, { error: 'Scheme name is required' }.to_json unless scheme_name && !scheme_name.empty?
|
|
1341
|
+
halt 400, { error: 'Credential name is required' }.to_json unless credential_name && !credential_name.empty?
|
|
1342
|
+
|
|
1343
|
+
begin
|
|
1344
|
+
result = test_authenticated_api_call(url, scheme_name, credential_name)
|
|
1345
|
+
|
|
1346
|
+
logger.info("API test completed for URL '#{url}' with scheme '#{scheme_name}' and credential '#{credential_name}': #{result[:success] ? 'SUCCESS' : 'FAILED'}")
|
|
1347
|
+
result.to_json
|
|
1348
|
+
rescue StandardError => e
|
|
1349
|
+
logger.error("Error testing API call: #{e.class} - #{e.message}")
|
|
1350
|
+
halt 500, { error: "Failed to test API call: #{e.message}" }.to_json
|
|
1351
|
+
end
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
# POST /auth/test/flow - Test complete authentication flow
|
|
1355
|
+
app.post '/auth/test/flow' do
|
|
1356
|
+
logger.info('POST /auth/test/flow route handler entered (from AuthenticationRoutes)')
|
|
1357
|
+
content_type :json
|
|
1358
|
+
|
|
1359
|
+
scheme_name = params[:scheme_name]
|
|
1360
|
+
credential_name = params[:credential_name]
|
|
1361
|
+
|
|
1362
|
+
halt 400, { error: 'Scheme name is required' }.to_json unless scheme_name && !scheme_name.empty?
|
|
1363
|
+
halt 400, { error: 'Credential name is required' }.to_json unless credential_name && !credential_name.empty?
|
|
1364
|
+
|
|
1365
|
+
begin
|
|
1366
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1367
|
+
scheme = auth_manager.get_scheme(scheme_name.to_sym)
|
|
1368
|
+
credential = auth_manager.get_credential(credential_name.to_sym)
|
|
1369
|
+
|
|
1370
|
+
halt 404, { error: 'Scheme not found' }.to_json unless scheme
|
|
1371
|
+
halt 404, { error: 'Credential not found' }.to_json unless credential
|
|
1372
|
+
halt 400, { error: 'Scheme and credential are not compatible' }.to_json unless mapping_scheme_credential_compatible?(scheme, credential)
|
|
1373
|
+
|
|
1374
|
+
flow_results = { success: true, steps: [], scheme_type: scheme.scheme_type }
|
|
1375
|
+
|
|
1376
|
+
case scheme.scheme_type
|
|
1377
|
+
when :api_key
|
|
1378
|
+
flow_results[:steps] << {
|
|
1379
|
+
step: 'API Key Authentication',
|
|
1380
|
+
status: 'passed',
|
|
1381
|
+
message: 'API key authentication is ready for use'
|
|
1382
|
+
}
|
|
1383
|
+
when :http_bearer
|
|
1384
|
+
flow_results[:steps] << {
|
|
1385
|
+
step: 'Bearer Token Authentication',
|
|
1386
|
+
status: 'passed',
|
|
1387
|
+
message: 'Bearer token authentication is ready for use'
|
|
1388
|
+
}
|
|
1389
|
+
when :oauth2, :oidc, :openid_connect
|
|
1390
|
+
flow_results[:steps] << {
|
|
1391
|
+
step: 'OAuth2 Flow Simulation',
|
|
1392
|
+
status: 'simulated',
|
|
1393
|
+
message: 'OAuth2 flow would redirect to authorization URL and exchange code for token'
|
|
1394
|
+
}
|
|
1395
|
+
when :service_account, :google_service_account
|
|
1396
|
+
flow_results[:steps] << {
|
|
1397
|
+
step: 'Service Account Flow Simulation',
|
|
1398
|
+
status: 'simulated',
|
|
1399
|
+
message: 'Service account would generate JWT and exchange for access token'
|
|
1400
|
+
}
|
|
1401
|
+
else
|
|
1402
|
+
flow_results[:success] = false
|
|
1403
|
+
flow_results[:steps] << {
|
|
1404
|
+
step: 'Unknown Flow',
|
|
1405
|
+
status: 'failed',
|
|
1406
|
+
message: "No flow simulation available for scheme type: #{scheme.scheme_type}"
|
|
1407
|
+
}
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
logger.info("Flow test completed for scheme '#{scheme_name}' and credential '#{credential_name}': #{flow_results[:success] ? 'SUCCESS' : 'FAILED'}")
|
|
1411
|
+
flow_results.to_json
|
|
1412
|
+
rescue StandardError => e
|
|
1413
|
+
logger.error("Error testing authentication flow: #{e.class} - #{e.message}")
|
|
1414
|
+
halt 500, { error: "Failed to test authentication flow: #{e.message}" }.to_json
|
|
1415
|
+
end
|
|
1416
|
+
end
|
|
1417
|
+
|
|
1418
|
+
# GET /auth/mappings/new - Form for creating new mapping
|
|
1419
|
+
app.get '/auth/mappings/new' do
|
|
1420
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1421
|
+
schemes = auth_manager.schemes || {}
|
|
1422
|
+
credentials = auth_manager.credentials || {}
|
|
1423
|
+
mapping_fields = get_mapping_config_fields
|
|
1424
|
+
instance_variable_set(:@schemes, schemes)
|
|
1425
|
+
instance_variable_set(:@credentials, credentials)
|
|
1426
|
+
instance_variable_set(:@mapping_fields, mapping_fields)
|
|
1427
|
+
slim :auth_mapping_new
|
|
1428
|
+
end
|
|
1429
|
+
|
|
1430
|
+
# GET /auth/mappings/:id - Mapping detail/edit
|
|
1431
|
+
app.get '/auth/mappings/:id' do
|
|
1432
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1433
|
+
mappings = auth_manager.url_mappings || []
|
|
1434
|
+
id = params[:id].to_i
|
|
1435
|
+
mapping = mappings[id] or halt 404, 'Mapping not found'
|
|
1436
|
+
schemes = auth_manager.schemes || {}
|
|
1437
|
+
credentials = auth_manager.credentials || {}
|
|
1438
|
+
mapping_fields = get_mapping_config_fields
|
|
1439
|
+
instance_variable_set(:@mapping, mapping)
|
|
1440
|
+
instance_variable_set(:@schemes, schemes)
|
|
1441
|
+
instance_variable_set(:@credentials, credentials)
|
|
1442
|
+
instance_variable_set(:@mapping_fields, mapping_fields)
|
|
1443
|
+
slim :auth_mapping_detail
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
# POST /auth/mappings - Create new mapping
|
|
1447
|
+
app.post '/auth/mappings' do
|
|
1448
|
+
content_type :json
|
|
1449
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1450
|
+
mappings = auth_manager.url_mappings || []
|
|
1451
|
+
pattern = params[:pattern]
|
|
1452
|
+
pattern_type = params[:pattern_type]
|
|
1453
|
+
scheme_name = params[:scheme_name]&.to_sym
|
|
1454
|
+
credential_name = params[:credential_name]&.to_sym
|
|
1455
|
+
priority = params[:priority]&.to_i || 1
|
|
1456
|
+
active = %w[on true].include?(params[:active])
|
|
1457
|
+
# Validate pattern
|
|
1458
|
+
halt 400, { error: 'Invalid pattern' }.to_json unless valid_mapping_pattern?(pattern, pattern_type)
|
|
1459
|
+
# Validate scheme/credential
|
|
1460
|
+
scheme = auth_manager.get_scheme(scheme_name)
|
|
1461
|
+
credential = auth_manager.get_credential(credential_name)
|
|
1462
|
+
halt 400, { error: 'Scheme and credential are not compatible' }.to_json unless mapping_scheme_credential_compatible?(scheme, credential)
|
|
1463
|
+
# Build mapping
|
|
1464
|
+
mapping = {
|
|
1465
|
+
pattern: pattern_type == 'regex' ? Regexp.new(pattern) : pattern,
|
|
1466
|
+
pattern_type: pattern_type,
|
|
1467
|
+
scheme_name: scheme_name,
|
|
1468
|
+
credential_name: credential_name,
|
|
1469
|
+
priority: priority,
|
|
1470
|
+
active: active
|
|
1471
|
+
}
|
|
1472
|
+
mappings << mapping
|
|
1473
|
+
auth_manager.replace_url_mappings(mappings)
|
|
1474
|
+
{ success: true, message: 'Mapping created', id: mappings.size - 1 }.to_json
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
# PUT /auth/mappings/:id - Update mapping
|
|
1478
|
+
app.put '/auth/mappings/:id' do
|
|
1479
|
+
content_type :json
|
|
1480
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1481
|
+
mappings = auth_manager.url_mappings || []
|
|
1482
|
+
id = params[:id].to_i
|
|
1483
|
+
mapping = mappings[id] or halt 404, { error: 'Mapping not found' }.to_json
|
|
1484
|
+
pattern = params[:pattern]
|
|
1485
|
+
pattern_type = params[:pattern_type]
|
|
1486
|
+
scheme_name = params[:scheme_name]&.to_sym
|
|
1487
|
+
credential_name = params[:credential_name]&.to_sym
|
|
1488
|
+
priority = params[:priority]&.to_i || 1
|
|
1489
|
+
active = %w[on true].include?(params[:active])
|
|
1490
|
+
halt 400, { error: 'Invalid pattern' }.to_json unless valid_mapping_pattern?(pattern, pattern_type)
|
|
1491
|
+
scheme = auth_manager.get_scheme(scheme_name)
|
|
1492
|
+
credential = auth_manager.get_credential(credential_name)
|
|
1493
|
+
halt 400, { error: 'Scheme and credential are not compatible' }.to_json unless mapping_scheme_credential_compatible?(scheme, credential)
|
|
1494
|
+
mapping[:pattern] = pattern_type == 'regex' ? Regexp.new(pattern) : pattern
|
|
1495
|
+
mapping[:pattern_type] = pattern_type
|
|
1496
|
+
mapping[:scheme_name] = scheme_name
|
|
1497
|
+
mapping[:credential_name] = credential_name
|
|
1498
|
+
mapping[:priority] = priority
|
|
1499
|
+
mapping[:active] = active
|
|
1500
|
+
auth_manager.replace_url_mappings(mappings)
|
|
1501
|
+
{ success: true, message: 'Mapping updated' }.to_json
|
|
1502
|
+
end
|
|
1503
|
+
|
|
1504
|
+
# DELETE /auth/mappings/:id - Remove mapping
|
|
1505
|
+
app.delete '/auth/mappings/:id' do
|
|
1506
|
+
content_type :json
|
|
1507
|
+
auth_manager = Legate::Auth::Manager.instance
|
|
1508
|
+
mappings = auth_manager.url_mappings || []
|
|
1509
|
+
id = params[:id].to_i
|
|
1510
|
+
mapping = mappings[id] or halt 404, { error: 'Mapping not found' }.to_json
|
|
1511
|
+
mappings.delete_at(id)
|
|
1512
|
+
auth_manager.replace_url_mappings(mappings)
|
|
1513
|
+
{ success: true, message: 'Mapping deleted' }.to_json
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1516
|
+
# POST /auth/mappings/test - Test pattern matching
|
|
1517
|
+
app.post '/auth/mappings/test' do
|
|
1518
|
+
content_type :json
|
|
1519
|
+
pattern = params[:pattern]
|
|
1520
|
+
pattern_type = params[:pattern_type]
|
|
1521
|
+
url = params[:url]
|
|
1522
|
+
halt 400, { error: 'Invalid pattern' }.to_json unless valid_mapping_pattern?(pattern, pattern_type)
|
|
1523
|
+
begin
|
|
1524
|
+
matched =
|
|
1525
|
+
if pattern_type == 'regex'
|
|
1526
|
+
!!(url =~ Regexp.new(pattern))
|
|
1527
|
+
elsif pattern.include?('*')
|
|
1528
|
+
regex = Regexp.new('^' + Regexp.escape(pattern).gsub('\\*', '.*') + '$')
|
|
1529
|
+
!!(url =~ regex)
|
|
1530
|
+
else
|
|
1531
|
+
url == pattern
|
|
1532
|
+
end
|
|
1533
|
+
{ success: true, matched: matched }.to_json
|
|
1534
|
+
rescue StandardError => e
|
|
1535
|
+
halt 400, { error: "Pattern test error: #{e.message}" }.to_json
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
end
|
|
1539
|
+
end
|
|
1540
|
+
end
|
|
1541
|
+
end
|