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,279 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
require_relative 'coordinator'
|
|
5
|
+
require_relative 'coordinators/oauth2_coordinator'
|
|
6
|
+
require_relative 'coordinators/oidc_coordinator'
|
|
7
|
+
require_relative 'coordinators/service_account_coordinator'
|
|
8
|
+
require_relative 'token_store'
|
|
9
|
+
require_relative 'token_manager'
|
|
10
|
+
|
|
11
|
+
module Legate
|
|
12
|
+
module Auth
|
|
13
|
+
# Runner provides the execution environment for fiber-based authentication flows.
|
|
14
|
+
# It handles creating and managing authentication coordinators, running tasks within
|
|
15
|
+
# a fiber, and handling authentication requests/responses.
|
|
16
|
+
class Runner
|
|
17
|
+
# Initialize a new authentication runner
|
|
18
|
+
# @param session_service [Legate::SessionService::Base] The session service for persistence
|
|
19
|
+
# @param token_store [Legate::Auth::TokenStore, nil] Optional token store for caching tokens
|
|
20
|
+
# @param token_manager [Legate::Auth::TokenManager, nil] Optional token manager for lifecycle management
|
|
21
|
+
def initialize(session_service:, token_store: nil, token_manager: nil)
|
|
22
|
+
@session_service = session_service
|
|
23
|
+
@token_store = token_store || TokenStore.new(session_service)
|
|
24
|
+
@token_manager = token_manager || TokenManager.new(@token_store)
|
|
25
|
+
@active_coordinators = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Run a task within a fiber with authentication handling
|
|
29
|
+
# @param task [Proc] The task to run
|
|
30
|
+
# @param context [Object] The context to run the task in (typically ToolContext)
|
|
31
|
+
# @yield [Hash, nil] Optional block to handle authentication requests
|
|
32
|
+
# @return [Object] The result of the task
|
|
33
|
+
# @raise [Legate::Auth::Error] If authentication fails
|
|
34
|
+
def run(task, context = nil, &auth_handler)
|
|
35
|
+
raise ArgumentError, 'Task must be a Proc or lambda' unless task.is_a?(Proc)
|
|
36
|
+
|
|
37
|
+
# Create a fiber for the task
|
|
38
|
+
task_fiber = Fiber.new do
|
|
39
|
+
# Make the auth_session method available in the context
|
|
40
|
+
if context && !context.respond_to?(:auth_session)
|
|
41
|
+
context.define_singleton_method(:auth_session) do |scheme, credential, **opts|
|
|
42
|
+
Fiber.yield({
|
|
43
|
+
action: :authenticate,
|
|
44
|
+
scheme: scheme,
|
|
45
|
+
credential: credential,
|
|
46
|
+
options: opts
|
|
47
|
+
})
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Run the task
|
|
52
|
+
task.call
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
{ error: e }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Start the fiber
|
|
58
|
+
result = nil
|
|
59
|
+
loop do
|
|
60
|
+
# Resume the fiber and get the next result/yield
|
|
61
|
+
result = task_fiber.resume
|
|
62
|
+
|
|
63
|
+
# If the fiber has completed (not yielded), return the result
|
|
64
|
+
break unless task_fiber.alive?
|
|
65
|
+
|
|
66
|
+
# Handle authentication requests
|
|
67
|
+
if result.is_a?(Hash) && result[:action] == :authenticate
|
|
68
|
+
handle_authentication_request(result, task_fiber, &auth_handler)
|
|
69
|
+
else
|
|
70
|
+
# For other types of yields, just pass them to the handler if provided
|
|
71
|
+
response = auth_handler ? auth_handler.call(result) : nil
|
|
72
|
+
result = task_fiber.resume(response)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# If the result is an error hash, raise it
|
|
77
|
+
raise result[:error] if result.is_a?(Hash) && result[:error]
|
|
78
|
+
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Handle an authentication response from the client
|
|
83
|
+
# @param request_id [String] The request ID for the authentication flow
|
|
84
|
+
# @param response [Hash] The response from the client
|
|
85
|
+
# @return [Hash] The result of handling the response
|
|
86
|
+
def handle_auth_response(request_id, response)
|
|
87
|
+
coordinator = @active_coordinators[request_id]
|
|
88
|
+
|
|
89
|
+
unless coordinator
|
|
90
|
+
return {
|
|
91
|
+
status: :error,
|
|
92
|
+
error: "No active authentication flow found for request ID: #{request_id}"
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
result = coordinator.resume(response)
|
|
98
|
+
|
|
99
|
+
if coordinator.complete?
|
|
100
|
+
if coordinator.success?
|
|
101
|
+
# Authentication completed successfully
|
|
102
|
+
@active_coordinators.delete(request_id)
|
|
103
|
+
{
|
|
104
|
+
status: :completed,
|
|
105
|
+
credential: result
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
# Authentication failed
|
|
109
|
+
@active_coordinators.delete(request_id)
|
|
110
|
+
{
|
|
111
|
+
status: :failed,
|
|
112
|
+
error: coordinator.error&.message || 'Authentication failed'
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
# Authentication is still in progress, return the next request
|
|
117
|
+
{
|
|
118
|
+
status: :pending,
|
|
119
|
+
request: result
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
@active_coordinators.delete(request_id)
|
|
124
|
+
{
|
|
125
|
+
status: :error,
|
|
126
|
+
error: "Error handling authentication response: #{e.message}"
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Cancel an active authentication flow
|
|
132
|
+
# @param request_id [String] The request ID for the authentication flow
|
|
133
|
+
# @param reason [String, nil] Optional reason for cancellation
|
|
134
|
+
# @return [Boolean] True if the flow was successfully cancelled
|
|
135
|
+
def cancel_auth_flow(request_id, reason = nil)
|
|
136
|
+
coordinator = @active_coordinators[request_id]
|
|
137
|
+
|
|
138
|
+
return false unless coordinator
|
|
139
|
+
|
|
140
|
+
result = coordinator.cancel(reason)
|
|
141
|
+
@active_coordinators.delete(request_id) if result
|
|
142
|
+
result
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Handle an authentication request yielded from a task fiber
|
|
148
|
+
# @param request [Hash] The authentication request
|
|
149
|
+
# @param task_fiber [Fiber] The task fiber
|
|
150
|
+
# @yield [Hash] Optional block to handle authentication requests
|
|
151
|
+
# @return [Legate::Auth::ExchangedCredential, nil] The result of authentication
|
|
152
|
+
def handle_authentication_request(request, task_fiber, &auth_handler)
|
|
153
|
+
scheme = request[:scheme]
|
|
154
|
+
credential = request[:credential]
|
|
155
|
+
options = request[:options] || {}
|
|
156
|
+
|
|
157
|
+
# Validate request
|
|
158
|
+
raise ArgumentError, "Invalid authentication scheme: #{scheme.class}" unless scheme.is_a?(Legate::Auth::Scheme)
|
|
159
|
+
|
|
160
|
+
raise ArgumentError, "Invalid credential: #{credential.class}" unless credential.is_a?(Legate::Auth::Credential)
|
|
161
|
+
|
|
162
|
+
# First, try to get an existing token from the token manager
|
|
163
|
+
token = @token_manager.get_token(scheme, credential)
|
|
164
|
+
|
|
165
|
+
# If we have a valid token, use it
|
|
166
|
+
return task_fiber.resume(token) if token && !token.expired?
|
|
167
|
+
|
|
168
|
+
# Create an appropriate coordinator based on the scheme type
|
|
169
|
+
coordinator = create_coordinator(scheme, credential, options)
|
|
170
|
+
|
|
171
|
+
# Start the authentication flow
|
|
172
|
+
auth_request = coordinator.start
|
|
173
|
+
|
|
174
|
+
# Store the coordinator for future responses
|
|
175
|
+
@active_coordinators[auth_request[:request_id]] = coordinator
|
|
176
|
+
|
|
177
|
+
# If a handler block is provided, use it
|
|
178
|
+
if auth_handler
|
|
179
|
+
# Pass the authentication request to the handler
|
|
180
|
+
response = auth_handler.call(auth_request)
|
|
181
|
+
|
|
182
|
+
# If the handler provided a response directly, process it
|
|
183
|
+
if response
|
|
184
|
+
result = handle_auth_response(auth_request[:request_id], response)
|
|
185
|
+
|
|
186
|
+
# Return the credential to the task fiber if authentication completed
|
|
187
|
+
return task_fiber.resume(result[:credential]) if result[:status] == :completed
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Otherwise, return authentication request to await client response
|
|
192
|
+
{ request_id: auth_request[:request_id], status: :pending }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Create an appropriate coordinator based on the scheme type
|
|
196
|
+
# @param scheme [Legate::Auth::Scheme] The authentication scheme
|
|
197
|
+
# @param credential [Legate::Auth::Credential] The credential
|
|
198
|
+
# @param options [Hash] Additional options for the coordinator
|
|
199
|
+
# @return [Legate::Auth::Coordinator] The appropriate coordinator
|
|
200
|
+
def create_coordinator(scheme, credential, options)
|
|
201
|
+
case scheme
|
|
202
|
+
when Legate::Auth::Schemes::OAuth2
|
|
203
|
+
Legate::Auth::Coordinators::OAuth2Coordinator.new(
|
|
204
|
+
scheme: scheme,
|
|
205
|
+
credential: credential,
|
|
206
|
+
session_service: @session_service,
|
|
207
|
+
token_store: @token_store,
|
|
208
|
+
timeout: options[:timeout],
|
|
209
|
+
redirect_uri: options[:redirect_uri]
|
|
210
|
+
)
|
|
211
|
+
when Legate::Auth::Schemes::OIDC
|
|
212
|
+
Legate::Auth::Coordinators::OIDCCoordinator.new(
|
|
213
|
+
scheme: scheme,
|
|
214
|
+
credential: credential,
|
|
215
|
+
session_service: @session_service,
|
|
216
|
+
token_store: @token_store,
|
|
217
|
+
timeout: options[:timeout],
|
|
218
|
+
redirect_uri: options[:redirect_uri]
|
|
219
|
+
)
|
|
220
|
+
when Legate::Auth::Schemes::ServiceAccount
|
|
221
|
+
Legate::Auth::Coordinators::ServiceAccountCoordinator.new(
|
|
222
|
+
scheme: scheme,
|
|
223
|
+
credential: credential,
|
|
224
|
+
session_service: @session_service,
|
|
225
|
+
token_store: @token_store,
|
|
226
|
+
timeout: options[:timeout]
|
|
227
|
+
)
|
|
228
|
+
# Add more coordinator types as needed for other schemes
|
|
229
|
+
else
|
|
230
|
+
raise NotImplementedError, "No coordinator available for scheme type: #{scheme.class}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Create a service account coordinator
|
|
235
|
+
# @param scheme [Legate::Auth::Schemes::ServiceAccount] The service account scheme
|
|
236
|
+
# @param credential [Legate::Auth::Credential] The credential with service account info
|
|
237
|
+
# @param options [Hash] Additional options for the coordinator
|
|
238
|
+
# @return [Legate::Auth::Coordinators::ServiceAccountCoordinator] The coordinator
|
|
239
|
+
def create_service_account_coordinator(scheme, credential, options = {})
|
|
240
|
+
raise ArgumentError, "Expected a ServiceAccount scheme, got #{scheme.class}" unless scheme.is_a?(Legate::Auth::Schemes::ServiceAccount)
|
|
241
|
+
|
|
242
|
+
raise ArgumentError, "Credential must have auth_type :service_account, got #{credential.auth_type}" unless credential.auth_type.to_sym == :service_account
|
|
243
|
+
|
|
244
|
+
Legate::Auth::Coordinators::ServiceAccountCoordinator.new(
|
|
245
|
+
scheme: scheme,
|
|
246
|
+
credential: credential,
|
|
247
|
+
session_service: @session_service,
|
|
248
|
+
token_store: @token_store,
|
|
249
|
+
timeout: options[:timeout]
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Authenticate using a service account
|
|
254
|
+
# @param scheme [Legate::Auth::Schemes::ServiceAccount] The service account scheme
|
|
255
|
+
# @param credential [Legate::Auth::Credential] The credential with service account info
|
|
256
|
+
# @param options [Hash] Additional options for the coordinator
|
|
257
|
+
# @return [Legate::Auth::ExchangedCredential] The authenticated credential
|
|
258
|
+
# @raise [Legate::Auth::Error] If authentication fails
|
|
259
|
+
def authenticate_with_service_account(scheme, credential, options = {})
|
|
260
|
+
# Create the coordinator
|
|
261
|
+
coordinator = create_service_account_coordinator(scheme, credential, options)
|
|
262
|
+
|
|
263
|
+
# Start the authentication flow
|
|
264
|
+
coordinator.start
|
|
265
|
+
|
|
266
|
+
# For service accounts, authentication is non-interactive, so the result should be available immediately
|
|
267
|
+
if coordinator.complete?
|
|
268
|
+
return coordinator.result if coordinator.success?
|
|
269
|
+
|
|
270
|
+
raise coordinator.error || Legate::Auth::Error.new('Service account authentication failed')
|
|
271
|
+
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# This should never happen for service accounts
|
|
275
|
+
raise Legate::Auth::Error.new('Unexpected state: service account authentication requires interaction')
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# File: lib/legate/auth/scheme.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'uri'
|
|
5
|
+
require_relative 'url_guard'
|
|
6
|
+
|
|
7
|
+
module Legate
|
|
8
|
+
module Auth
|
|
9
|
+
# Base class for all authentication schemes.
|
|
10
|
+
# Schemes provide logic for applying authentication to requests,
|
|
11
|
+
# refreshing tokens, and other operations specific to their authentication type.
|
|
12
|
+
class Scheme
|
|
13
|
+
# Get the type of authentication scheme
|
|
14
|
+
# @return [Symbol] The scheme type identifier
|
|
15
|
+
def scheme_type
|
|
16
|
+
raise NotImplementedError, "#{self.class} must implement #scheme_type"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Apply authentication to a request
|
|
20
|
+
# @param request [Hash] The request hash to modify
|
|
21
|
+
# @param credential [Legate::Auth::Credential, Legate::Auth::ExchangedCredential] The credential to use
|
|
22
|
+
# @return [Hash] The modified request with authentication applied
|
|
23
|
+
def apply_to_request(request, credential)
|
|
24
|
+
raise NotImplementedError, "#{self.class} must implement #apply_to_request"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Check if this scheme supports token refresh
|
|
28
|
+
# @return [Boolean] True if this scheme supports token refresh
|
|
29
|
+
def supports_refresh?
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Refresh an authentication token
|
|
34
|
+
# @param token [Legate::Auth::ExchangedCredential] The token to refresh
|
|
35
|
+
# @param credential [Legate::Auth::Credential] The credential containing refresh parameters
|
|
36
|
+
# @return [Legate::Auth::ExchangedCredential] The refreshed token
|
|
37
|
+
# @raise [Legate::Auth::TokenRefreshError] If the token cannot be refreshed
|
|
38
|
+
def refresh_token(token, credential)
|
|
39
|
+
raise NotImplementedError, "#{self.class} does not support token refresh"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Exchange a credential for a token
|
|
43
|
+
# @param credential [Legate::Auth::Credential] The credential to exchange
|
|
44
|
+
# @return [Legate::Auth::ExchangedCredential] The exchanged token
|
|
45
|
+
# @raise [Legate::Auth::TokenExchangeError] If the credential cannot be exchanged
|
|
46
|
+
def exchange_token(credential)
|
|
47
|
+
raise NotImplementedError, "#{self.class} does not support token exchange"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Revoke a token
|
|
51
|
+
# @param token [Legate::Auth::ExchangedCredential] The token to revoke
|
|
52
|
+
# @param credential [Legate::Auth::Credential] The credential for revocation parameters
|
|
53
|
+
# @return [Boolean] True if the token was revoked successfully
|
|
54
|
+
# @raise [Legate::Auth::TokenRevokeError] If the token cannot be revoked
|
|
55
|
+
def revoke_token(token, credential)
|
|
56
|
+
raise NotImplementedError, "#{self.class} does not support token revocation"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Validates the scheme configuration
|
|
60
|
+
# @raise [Legate::Auth::SchemeValidationError] If the scheme configuration is invalid
|
|
61
|
+
# @abstract
|
|
62
|
+
def validate!
|
|
63
|
+
raise NotImplementedError, 'Subclasses must implement validate!'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns a hash representation of the scheme
|
|
67
|
+
# @return [Hash] A hash containing the scheme configuration
|
|
68
|
+
# @abstract
|
|
69
|
+
def to_h
|
|
70
|
+
{ type: scheme_type }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns a string representation of the scheme
|
|
74
|
+
# @return [String] A string representing the scheme
|
|
75
|
+
def to_s
|
|
76
|
+
"#{self.class.name}<#{scheme_type}>"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Builds an authorization URI for interactive authentication flows
|
|
80
|
+
# @param config [Legate::Auth::Config] The authentication configuration
|
|
81
|
+
# @param redirect_uri [String, nil] The redirect URI for the authorization request
|
|
82
|
+
# @param state [String, nil] A state parameter for the authorization request
|
|
83
|
+
# @return [String, nil] The authorization URI, or nil if not applicable
|
|
84
|
+
# @abstract
|
|
85
|
+
def build_authorization_uri(_config, _redirect_uri = nil, _state = nil)
|
|
86
|
+
nil # No-op in base class, override in subclasses that support interactive flows
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Checks if a response indicates an authentication error
|
|
90
|
+
# @param response [Hash] The HTTP response to check
|
|
91
|
+
# @return [Boolean] True if the response indicates an authentication error
|
|
92
|
+
def authentication_error?(response)
|
|
93
|
+
return false unless response.is_a?(Hash)
|
|
94
|
+
|
|
95
|
+
# HTTP status codes for auth errors (401 Unauthorized, 403 Forbidden)
|
|
96
|
+
[401, 403].include?(response[:status])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# Validates that a value is safe to use in an HTTP header.
|
|
102
|
+
# Rejects values containing CR, LF, or null bytes to prevent header injection.
|
|
103
|
+
# @param value [String] The header value to validate
|
|
104
|
+
# @param label [String] A label for error messages (e.g., "Bearer token")
|
|
105
|
+
# @raise [Legate::Auth::Error] If the value contains unsafe characters
|
|
106
|
+
def validate_header_value!(value, label = 'credential')
|
|
107
|
+
return unless value.is_a?(String)
|
|
108
|
+
|
|
109
|
+
return unless value.match?(/[\r\n\0]/)
|
|
110
|
+
|
|
111
|
+
raise Legate::Auth::Error, "#{label} contains invalid characters (CR, LF, or null byte)"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Validates that an auth URL does not point to private/restricted network
|
|
115
|
+
# addresses. Delegates to the canonical {Legate::Auth::UrlGuard} so schemes
|
|
116
|
+
# and the web credential-test routes share one SSRF policy.
|
|
117
|
+
# @param url [String] The URL to validate
|
|
118
|
+
# @param label [String] A label for error messages
|
|
119
|
+
# @raise [Legate::Auth::Error] If the URL resolves to a restricted address
|
|
120
|
+
def validate_auth_url!(url, label: 'Auth URL')
|
|
121
|
+
Legate::Auth::UrlGuard.validate!(url, label: label)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# File: lib/legate/auth/schemes/api_key.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../scheme'
|
|
5
|
+
require_relative '../error'
|
|
6
|
+
require_relative '../exchanged_credential'
|
|
7
|
+
|
|
8
|
+
module Legate
|
|
9
|
+
module Auth
|
|
10
|
+
module Schemes
|
|
11
|
+
# API Key authentication scheme.
|
|
12
|
+
# This scheme applies an API key to requests via a header, query parameter, or cookie.
|
|
13
|
+
class ApiKey < Scheme
|
|
14
|
+
# Default header name for API key authentication
|
|
15
|
+
DEFAULT_HEADER_NAME = 'X-API-Key'
|
|
16
|
+
|
|
17
|
+
# Get the type of authentication scheme
|
|
18
|
+
# @return [Symbol] The scheme type identifier
|
|
19
|
+
def scheme_type
|
|
20
|
+
:api_key
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Apply authentication to a request
|
|
24
|
+
# @param request [Hash] The request hash to modify
|
|
25
|
+
# @param credential [Legate::Auth::Credential, Legate::Auth::ExchangedCredential] The credential to use
|
|
26
|
+
# @return [Hash] The modified request with authentication applied
|
|
27
|
+
# @raise [Legate::Auth::Error] If the API key cannot be applied
|
|
28
|
+
def apply_to_request(request, credential)
|
|
29
|
+
# Create a deep copy of the request to avoid modifying the original
|
|
30
|
+
request_copy = Marshal.load(Marshal.dump(request))
|
|
31
|
+
|
|
32
|
+
# Handle the case where we get a stack object from Excon
|
|
33
|
+
if request_copy.is_a?(Hash)
|
|
34
|
+
if request_copy[:stack]
|
|
35
|
+
# Extract the data from stack (Excon middleware format)
|
|
36
|
+
%i[scheme method path host port query].each do |key|
|
|
37
|
+
request_copy[key] = request_copy[:stack][key] if request_copy[:stack][key] && !request_copy[key]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Ensure headers hash exists
|
|
42
|
+
request_copy[:headers] ||= {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Extract the API key from the credential
|
|
46
|
+
api_key = extract_api_key(credential)
|
|
47
|
+
raise Legate::Auth::Error, 'API key not found in credential' unless api_key
|
|
48
|
+
|
|
49
|
+
# Get parameters for applying the API key
|
|
50
|
+
location = credential[:location] || 'header'
|
|
51
|
+
name = credential[:name] || DEFAULT_HEADER_NAME
|
|
52
|
+
|
|
53
|
+
# Apply the API key based on location
|
|
54
|
+
case location.to_s.downcase
|
|
55
|
+
when 'header'
|
|
56
|
+
apply_to_header(request_copy, name, api_key)
|
|
57
|
+
when 'query', 'querystring'
|
|
58
|
+
apply_to_query(request_copy, name, api_key)
|
|
59
|
+
when 'cookie'
|
|
60
|
+
apply_to_cookie(request_copy, name, api_key)
|
|
61
|
+
else
|
|
62
|
+
raise Legate::Auth::Error, "Unsupported API key location: #{location}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Exchange a credential for a token
|
|
67
|
+
# @param credential [Legate::Auth::Credential] The credential to exchange
|
|
68
|
+
# @return [Legate::Auth::ExchangedCredential] The exchanged token
|
|
69
|
+
def exchange_token(credential)
|
|
70
|
+
# For API keys, we simply create a "token" that wraps the API key
|
|
71
|
+
# This is useful for token management consistency
|
|
72
|
+
api_key = extract_api_key(credential)
|
|
73
|
+
raise Legate::Auth::TokenExchangeError, 'API key not found in credential' unless api_key
|
|
74
|
+
|
|
75
|
+
# Create a simple exchanged credential that never expires
|
|
76
|
+
Legate::Auth::ExchangedCredential.new(
|
|
77
|
+
auth_type: :api_key,
|
|
78
|
+
api_key: api_key,
|
|
79
|
+
location: credential[:location] || 'header',
|
|
80
|
+
name: credential[:name] || DEFAULT_HEADER_NAME
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get hash representation of the scheme
|
|
85
|
+
# @return [Hash] Scheme configuration as a hash
|
|
86
|
+
def to_h
|
|
87
|
+
{
|
|
88
|
+
type: scheme_type
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Extract the API key from a credential
|
|
95
|
+
# @param credential [Legate::Auth::Credential, Legate::Auth::ExchangedCredential] The credential
|
|
96
|
+
# @return [String, nil] The API key or nil if not found
|
|
97
|
+
def extract_api_key(credential)
|
|
98
|
+
# First try api_key
|
|
99
|
+
return credential[:api_key] if credential[:api_key]
|
|
100
|
+
|
|
101
|
+
# Next try key
|
|
102
|
+
return credential[:key] if credential[:key]
|
|
103
|
+
|
|
104
|
+
# Finally try token
|
|
105
|
+
credential[:token]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Apply the API key to a request header
|
|
109
|
+
# @param request [Hash] The request to modify
|
|
110
|
+
# @param name [String] The header name
|
|
111
|
+
# @param api_key [String] The API key
|
|
112
|
+
# @return [Hash] The modified request
|
|
113
|
+
def apply_to_header(request, name, api_key)
|
|
114
|
+
validate_header_value!(api_key, 'API key')
|
|
115
|
+
validate_header_value!(name, 'Header name')
|
|
116
|
+
request[:headers] ||= {}
|
|
117
|
+
request[:headers][name] = api_key
|
|
118
|
+
request
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Apply the API key to a request query parameter
|
|
122
|
+
# @param request [Hash] The request to modify
|
|
123
|
+
# @param name [String] The parameter name
|
|
124
|
+
# @param api_key [String] The API key
|
|
125
|
+
# @return [Hash] The modified request
|
|
126
|
+
def apply_to_query(request, name, api_key)
|
|
127
|
+
# Initialize headers if not present
|
|
128
|
+
request[:headers] ||= {}
|
|
129
|
+
|
|
130
|
+
# The proper way to handle query parameters depends on the format
|
|
131
|
+
# expected by the HTTP client library
|
|
132
|
+
|
|
133
|
+
# Handle simple URL case
|
|
134
|
+
if request[:url]
|
|
135
|
+
# Properly append the query parameter
|
|
136
|
+
separator = request[:url].include?('?') ? '&' : '?'
|
|
137
|
+
request[:url] = "#{request[:url]}#{separator}#{name}=#{api_key}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Handle query param hash (used by Excon and other libraries)
|
|
141
|
+
if request[:query].is_a?(Hash)
|
|
142
|
+
# Simply add the param to the hash
|
|
143
|
+
request[:query][name] = api_key
|
|
144
|
+
elsif request[:query].is_a?(String)
|
|
145
|
+
# Append to existing query string
|
|
146
|
+
separator = request[:query].empty? ? '' : '&'
|
|
147
|
+
request[:query] = "#{request[:query]}#{separator}#{name}=#{api_key}"
|
|
148
|
+
elsif request[:query].nil?
|
|
149
|
+
# Create a new query hash
|
|
150
|
+
request[:query] = { name => api_key }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Also update the URL with query parameters for debugging
|
|
154
|
+
if !request[:url] && (request[:scheme] || request[:host] || request[:path])
|
|
155
|
+
# Build the URL from components for reference
|
|
156
|
+
scheme = request[:scheme] || 'https'
|
|
157
|
+
host = request[:host] || 'example.com'
|
|
158
|
+
path = request[:path] || '/'
|
|
159
|
+
port = request[:port]
|
|
160
|
+
|
|
161
|
+
# Construct URL from components
|
|
162
|
+
port_part = port ? ":#{port}" : ''
|
|
163
|
+
url = "#{scheme}://#{host}#{port_part}#{path}"
|
|
164
|
+
|
|
165
|
+
# Add query string if present
|
|
166
|
+
if request[:query]
|
|
167
|
+
query_str = if request[:query].is_a?(Hash)
|
|
168
|
+
params = []
|
|
169
|
+
request[:query].each do |k, v|
|
|
170
|
+
params << "#{k}=#{v}"
|
|
171
|
+
end
|
|
172
|
+
params.join('&')
|
|
173
|
+
else
|
|
174
|
+
request[:query].to_s
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
url += "?#{query_str}" unless query_str.empty?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
request[:url] = url
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
request
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Apply the API key to a request cookie
|
|
187
|
+
# @param request [Hash] The request to modify
|
|
188
|
+
# @param name [String] The cookie name
|
|
189
|
+
# @param api_key [String] The API key
|
|
190
|
+
# @return [Hash] The modified request
|
|
191
|
+
def apply_to_cookie(request, name, api_key)
|
|
192
|
+
validate_header_value!(api_key, 'API key (cookie)')
|
|
193
|
+
validate_header_value!(name, 'Cookie name')
|
|
194
|
+
# Initialize headers if not present
|
|
195
|
+
request[:headers] ||= {}
|
|
196
|
+
|
|
197
|
+
# Construct the cookie
|
|
198
|
+
cookie_value = "#{name}=#{api_key}"
|
|
199
|
+
|
|
200
|
+
# Append to existing cookie or set new one
|
|
201
|
+
request[:headers]['Cookie'] = if request[:headers]['Cookie'] && !request[:headers]['Cookie'].empty?
|
|
202
|
+
"#{request[:headers]['Cookie']}; #{cookie_value}"
|
|
203
|
+
else
|
|
204
|
+
cookie_value
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
request
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|