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,188 @@
|
|
|
1
|
+
# File: lib/legate/auth/tool_integration.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'credential'
|
|
5
|
+
require_relative 'exchanged_credential'
|
|
6
|
+
require_relative 'schemes/api_key'
|
|
7
|
+
require_relative 'schemes/http_bearer'
|
|
8
|
+
require_relative 'token_manager'
|
|
9
|
+
|
|
10
|
+
module Legate
|
|
11
|
+
module Auth
|
|
12
|
+
# Utility module for integrating authentication with tools.
|
|
13
|
+
# Provides methods for applying authentication to requests and
|
|
14
|
+
# detecting authentication errors in responses.
|
|
15
|
+
module ToolIntegration
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Apply authentication to a request based on scheme and credential
|
|
19
|
+
# @param request [Hash] The request to modify
|
|
20
|
+
# @param scheme [Legate::Auth::Scheme] The authentication scheme to use
|
|
21
|
+
# @param credential [Legate::Auth::Credential, Legate::Auth::ExchangedCredential] The credential to use
|
|
22
|
+
# @param token_store [Legate::Auth::TokenStore, nil] Optional token store for retrieving cached tokens
|
|
23
|
+
# @param token_manager [Legate::Auth::TokenManager, nil] Optional token manager for token lifecycle management
|
|
24
|
+
# @return [Hash] The modified request with authentication applied
|
|
25
|
+
# @raise [Legate::Auth::Error] If authentication cannot be applied
|
|
26
|
+
def apply_authentication(request, scheme, credential, token_store = nil, token_manager = nil)
|
|
27
|
+
raise ArgumentError, 'Request must be a Hash' unless request.is_a?(Hash)
|
|
28
|
+
raise ArgumentError, 'Scheme must be an Legate::Auth::Scheme' unless scheme.is_a?(Legate::Auth::Scheme)
|
|
29
|
+
|
|
30
|
+
# If we have a token manager, use it for getting tokens
|
|
31
|
+
if token_manager && token_manager.is_a?(Legate::Auth::TokenManager)
|
|
32
|
+
# Get a token using the token manager
|
|
33
|
+
token = token_manager.get_token(scheme, credential)
|
|
34
|
+
|
|
35
|
+
# Use the token if available
|
|
36
|
+
credential = token if token
|
|
37
|
+
# Fall back to the old mechanism if token_manager not available
|
|
38
|
+
elsif token_store && credential.is_a?(Legate::Auth::Credential)
|
|
39
|
+
cache_key = generate_cache_key(scheme, credential)
|
|
40
|
+
exchanged_credential = token_store.get(cache_key)
|
|
41
|
+
|
|
42
|
+
if exchanged_credential
|
|
43
|
+
# Check if token is expired and needs refresh
|
|
44
|
+
if exchanged_credential.expired? && scheme.supports_refresh?
|
|
45
|
+
begin
|
|
46
|
+
# Try to refresh the token
|
|
47
|
+
refreshed = scheme.refresh_token(exchanged_credential, credential)
|
|
48
|
+
# Store refreshed token
|
|
49
|
+
token_store.store(cache_key, refreshed)
|
|
50
|
+
# Use the refreshed credential
|
|
51
|
+
credential = refreshed
|
|
52
|
+
rescue Legate::Auth::TokenRefreshError => e
|
|
53
|
+
Legate.logger.warn("Failed to refresh token: #{e.message}. Using original credential.") if defined?(Legate.logger)
|
|
54
|
+
# Fall back to original credential if refresh fails
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
# Use cached credential
|
|
58
|
+
credential = exchanged_credential
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Apply the credential to the request using the scheme
|
|
64
|
+
begin
|
|
65
|
+
scheme.apply_to_request(request, credential)
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
# Log the error but return the original request to allow the request to continue
|
|
68
|
+
Legate.logger.error("Error applying authentication: #{e.message}") if defined?(Legate.logger)
|
|
69
|
+
request
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if a response indicates an authentication error
|
|
74
|
+
# @param response [Hash] The HTTP response to check
|
|
75
|
+
# @return [Boolean] True if the response indicates an authentication error
|
|
76
|
+
def authentication_error?(response)
|
|
77
|
+
return false unless response.is_a?(Hash)
|
|
78
|
+
|
|
79
|
+
# Check for common authentication error status codes
|
|
80
|
+
return true if [401, 403].include?(response[:status])
|
|
81
|
+
|
|
82
|
+
# Check for common error messages in response body
|
|
83
|
+
if response[:body] && response[:body].is_a?(String)
|
|
84
|
+
body_lower = response[:body].downcase
|
|
85
|
+
auth_error_indicators = [
|
|
86
|
+
'unauthorized', 'not authorized', 'invalid token',
|
|
87
|
+
'invalid api key', 'access denied', 'forbidden',
|
|
88
|
+
'authentication failed'
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
return true if auth_error_indicators.any? { |indicator| body_lower.include?(indicator) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check for error responses with auth error messages in JSON
|
|
95
|
+
if response[:body] && (response[:body].is_a?(Hash) ||
|
|
96
|
+
(response[:body].is_a?(String) && response[:body].start_with?('{')))
|
|
97
|
+
begin
|
|
98
|
+
body = response[:body].is_a?(Hash) ? response[:body] : JSON.parse(response[:body])
|
|
99
|
+
|
|
100
|
+
# Look for common error fields
|
|
101
|
+
%w[error errors message].each do |field|
|
|
102
|
+
next unless body[field]
|
|
103
|
+
|
|
104
|
+
error_text = body[field].is_a?(String) ? body[field].downcase : body[field].to_s.downcase
|
|
105
|
+
auth_error_indicators = [
|
|
106
|
+
'unauthorized', 'not authorized', 'invalid token',
|
|
107
|
+
'invalid api key', 'access denied', 'forbidden',
|
|
108
|
+
'authentication failed'
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
return true if auth_error_indicators.any? { |indicator| error_text.include?(indicator) }
|
|
112
|
+
end
|
|
113
|
+
rescue JSON::ParserError
|
|
114
|
+
# Ignore parsing errors
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Generate a cache key for storing/retrieving tokens
|
|
122
|
+
# @param scheme [Legate::Auth::Scheme] The authentication scheme
|
|
123
|
+
# @param credential [Legate::Auth::Credential] The credential
|
|
124
|
+
# @return [String] A unique cache key
|
|
125
|
+
def generate_cache_key(scheme, credential)
|
|
126
|
+
# Create a hash based on scheme type and relevant credential properties
|
|
127
|
+
parts = [
|
|
128
|
+
scheme.scheme_type.to_s,
|
|
129
|
+
credential.auth_type.to_s
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# Add scheme-specific information
|
|
133
|
+
case scheme.scheme_type
|
|
134
|
+
when :api_key
|
|
135
|
+
parts << credential[:api_key, resolve_env: false].to_s
|
|
136
|
+
when :http_bearer
|
|
137
|
+
parts << credential[:bearer_token, resolve_env: false].to_s
|
|
138
|
+
when :oauth2, :oidc
|
|
139
|
+
parts << credential[:client_id, resolve_env: false].to_s
|
|
140
|
+
parts << credential[:scope, resolve_env: false].to_s
|
|
141
|
+
when :service_account
|
|
142
|
+
parts << credential[:client_email, resolve_env: false].to_s
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Create a unique key using a digest
|
|
146
|
+
require 'digest/sha2'
|
|
147
|
+
"auth_#{Digest::SHA256.hexdigest(parts.join(':'))}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Determine if a request requires authentication based on URL or headers
|
|
151
|
+
# @param request [Hash] The request to check
|
|
152
|
+
# @return [Boolean] True if the request likely requires authentication
|
|
153
|
+
def requires_authentication?(request)
|
|
154
|
+
return false unless request.is_a?(Hash)
|
|
155
|
+
|
|
156
|
+
# In test environments, always require authentication
|
|
157
|
+
return true if request[:test_auth] == true
|
|
158
|
+
|
|
159
|
+
# Check common indicators that a request requires authentication
|
|
160
|
+
|
|
161
|
+
# 1. Check if path or URL contains auth-protected paths
|
|
162
|
+
protected_paths = %w[
|
|
163
|
+
/api/ /v1/ /v2/ /v3/ /private/ /user/ /admin/
|
|
164
|
+
/account/ /secure/ /protected/ /internal/ /test/
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
return true if request[:url] && protected_paths.any? { |path| request[:url].to_s.include?(path) }
|
|
168
|
+
|
|
169
|
+
return true if request[:path] && protected_paths.any? { |path| request[:path].to_s.include?(path) }
|
|
170
|
+
|
|
171
|
+
# 2. Check for Content-Type that often requires auth
|
|
172
|
+
if request[:headers] && request[:headers]['Content-Type']
|
|
173
|
+
auth_content_types = [
|
|
174
|
+
'application/json', 'application/xml',
|
|
175
|
+
'application/vnd.api+json'
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
return true if auth_content_types.any? { |type| request[:headers]['Content-Type'].to_s.include?(type) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# 3. Check for non-GET methods that typically require auth
|
|
182
|
+
return true if request[:method] && !%w[GET HEAD OPTIONS].include?(request[:method].to_s.upcase)
|
|
183
|
+
|
|
184
|
+
false
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# File: lib/legate/auth/url_guard.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'resolv'
|
|
5
|
+
require 'ipaddr'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require_relative 'error'
|
|
8
|
+
|
|
9
|
+
module Legate
|
|
10
|
+
module Auth
|
|
11
|
+
# Canonical SSRF guard for outbound auth and credential-test URLs.
|
|
12
|
+
#
|
|
13
|
+
# Resolves the host and refuses loopback, link-local, private,
|
|
14
|
+
# 0.0.0.0/8 and CGNAT (100.64.0.0/10) targets so a misconfigured or
|
|
15
|
+
# attacker-supplied URL cannot reach internal services or cloud metadata.
|
|
16
|
+
# Set LEGATE_ALLOW_PRIVATE_AUTH_URLS=1 to bypass in development.
|
|
17
|
+
module UrlGuard
|
|
18
|
+
BLOCKED_RANGES = [
|
|
19
|
+
IPAddr.new('0.0.0.0/8'),
|
|
20
|
+
IPAddr.new('100.64.0.0/10')
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# @param url [String] The URL to validate
|
|
26
|
+
# @param label [String] A label used in error messages
|
|
27
|
+
# @raise [Legate::Auth::Error] If the URL resolves to a restricted address
|
|
28
|
+
def validate!(url, label: 'Auth URL')
|
|
29
|
+
return if ENV['LEGATE_ALLOW_PRIVATE_AUTH_URLS']
|
|
30
|
+
|
|
31
|
+
hostname = parse_http_uri!(url, label).host
|
|
32
|
+
ips = resolved_ips(hostname)
|
|
33
|
+
# Fail closed: if we can't resolve the host, refuse rather than letting
|
|
34
|
+
# the request through (an unresolvable host can't be checked, and a
|
|
35
|
+
# resolver discrepancy could otherwise be used to slip past the guard).
|
|
36
|
+
if ips.empty?
|
|
37
|
+
raise Legate::Auth::Error,
|
|
38
|
+
"#{label}: could not resolve host '#{hostname}' for SSRF validation."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ips.each do |ip_str|
|
|
42
|
+
ip = IPAddr.new(ip_str)
|
|
43
|
+
next unless restricted?(ip)
|
|
44
|
+
|
|
45
|
+
raise Legate::Auth::Error,
|
|
46
|
+
"#{label} resolves to restricted network address (#{hostname} -> #{ip_str}). " \
|
|
47
|
+
'Set LEGATE_ALLOW_PRIVATE_AUTH_URLS=1 for development.'
|
|
48
|
+
rescue IPAddr::InvalidAddressError
|
|
49
|
+
next # skip unparseable IPs from the resolver
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @raise [Legate::Auth::Error] unless the URL uses an http/https scheme
|
|
54
|
+
def parse_http_uri!(url, label)
|
|
55
|
+
uri = URI.parse(url.to_s)
|
|
56
|
+
return uri if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
57
|
+
|
|
58
|
+
raise Legate::Auth::Error, "#{label} must use http or https scheme"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Array<String>] resolved IP strings ([] when resolution fails —
|
|
62
|
+
# the caller treats empty as a hard failure / fail-closed).
|
|
63
|
+
def resolved_ips(hostname)
|
|
64
|
+
[IPAddr.new(hostname).to_s]
|
|
65
|
+
rescue IPAddr::InvalidAddressError
|
|
66
|
+
begin
|
|
67
|
+
Resolv.getaddresses(hostname)
|
|
68
|
+
rescue Resolv::ResolvError
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def restricted?(ip)
|
|
74
|
+
# Normalize IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1) to its IPv4 form so
|
|
75
|
+
# the loopback/private/link-local checks aren't bypassed by the mapping.
|
|
76
|
+
ip = ip.native if ip.ipv4_mapped?
|
|
77
|
+
ip.loopback? || ip.link_local? || ip.private? || BLOCKED_RANGES.any? { |r| r.include?(ip) }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|