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,523 @@
|
|
|
1
|
+
# Custom Authentication Flow
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
While the Legate Ruby library provides built-in support for common authentication schemes, you may need to implement custom authentication flows for APIs with unique requirements. This guide explains how to create custom authentication flows using the Legate's authentication framework.
|
|
6
|
+
|
|
7
|
+
## When to Use Custom Authentication Flows
|
|
8
|
+
|
|
9
|
+
Consider implementing a custom authentication flow in the following scenarios:
|
|
10
|
+
|
|
11
|
+
- The API uses a non-standard authentication mechanism not covered by the built-in schemes
|
|
12
|
+
- You need to modify the standard authentication flow with additional steps or parameters
|
|
13
|
+
- You need to implement a proprietary authentication protocol specific to your API
|
|
14
|
+
- You want to combine multiple authentication mechanisms into a single flow
|
|
15
|
+
|
|
16
|
+
## Building Custom Authentication Schemes
|
|
17
|
+
|
|
18
|
+
### Creating a Custom Scheme Class
|
|
19
|
+
|
|
20
|
+
Custom authentication schemes should inherit from the `Legate::Auth::Scheme` base class:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
module Legate
|
|
24
|
+
module Auth
|
|
25
|
+
module Schemes
|
|
26
|
+
class CustomScheme < Legate::Auth::Scheme
|
|
27
|
+
attr_reader :custom_param, :auth_url, :token_url
|
|
28
|
+
|
|
29
|
+
def initialize(custom_param:, auth_url:, token_url:)
|
|
30
|
+
@custom_param = custom_param
|
|
31
|
+
@auth_url = auth_url
|
|
32
|
+
@token_url = token_url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Define the authentication type
|
|
36
|
+
def auth_type
|
|
37
|
+
:custom
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Define how to apply the credentials to a request
|
|
41
|
+
def apply_to_request(request, credential, tokens = nil)
|
|
42
|
+
if tokens
|
|
43
|
+
# Apply obtained tokens to the request
|
|
44
|
+
request[:headers] ||= {}
|
|
45
|
+
request[:headers]['Authorization'] = "Custom #{tokens[:access_token]}"
|
|
46
|
+
elsif credential
|
|
47
|
+
# Apply initial credentials to the request
|
|
48
|
+
request[:headers] ||= {}
|
|
49
|
+
request[:headers]['X-Custom-Auth'] = credential.custom_key
|
|
50
|
+
end
|
|
51
|
+
request
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Define how to check if tokens are valid
|
|
55
|
+
def tokens_valid?(tokens)
|
|
56
|
+
return false unless tokens
|
|
57
|
+
return false unless tokens[:access_token]
|
|
58
|
+
return false if tokens[:expires_at] && Time.now.to_i >= tokens[:expires_at]
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Define token refresh logic
|
|
63
|
+
def refresh_tokens(credential, tokens)
|
|
64
|
+
# Implement token refresh logic
|
|
65
|
+
# Return new tokens or raise an error
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Key Methods to Implement
|
|
74
|
+
|
|
75
|
+
When creating a custom authentication scheme, implement these key methods:
|
|
76
|
+
|
|
77
|
+
| Method | Description | Return Value |
|
|
78
|
+
|--------|-------------|--------------|
|
|
79
|
+
| `auth_type` | Identifies the authentication type | Symbol (e.g., `:custom`) |
|
|
80
|
+
| `apply_to_request` | Applies credentials or tokens to requests | Modified request hash |
|
|
81
|
+
| `tokens_valid?` | Checks if tokens are valid | Boolean |
|
|
82
|
+
| `refresh_tokens` | Refreshes expired tokens | Hash of new tokens or raises error |
|
|
83
|
+
|
|
84
|
+
## Implementing Interactive Authentication
|
|
85
|
+
|
|
86
|
+
For interactive authentication flows (similar to OAuth2), you'll need to implement both the client-side and server-side components.
|
|
87
|
+
|
|
88
|
+
### Step 1: Creating an Interactive Scheme
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
class CustomInteractiveScheme < Legate::Auth::Scheme
|
|
92
|
+
attr_reader :auth_url, :token_url
|
|
93
|
+
|
|
94
|
+
def initialize(auth_url:, token_url:)
|
|
95
|
+
@auth_url = auth_url
|
|
96
|
+
@token_url = token_url
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def auth_type
|
|
100
|
+
:custom_interactive
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Generate the authorization URL for the interactive flow
|
|
104
|
+
def generate_auth_url(credential, redirect_uri, state)
|
|
105
|
+
uri = URI.parse(@auth_url)
|
|
106
|
+
params = {
|
|
107
|
+
'client_id' => credential.client_id,
|
|
108
|
+
'redirect_uri' => redirect_uri,
|
|
109
|
+
'state' => state,
|
|
110
|
+
'response_type' => 'code',
|
|
111
|
+
'custom_param' => 'custom_value'
|
|
112
|
+
}
|
|
113
|
+
uri.query = URI.encode_www_form(params)
|
|
114
|
+
uri.to_s
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Exchange the auth code for tokens
|
|
118
|
+
def exchange_auth_code(credential, code, redirect_uri)
|
|
119
|
+
response = Excon.post(
|
|
120
|
+
@token_url,
|
|
121
|
+
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
|
122
|
+
body: URI.encode_www_form(
|
|
123
|
+
'grant_type' => 'authorization_code',
|
|
124
|
+
'code' => code,
|
|
125
|
+
'redirect_uri' => redirect_uri,
|
|
126
|
+
'client_id' => credential.client_id,
|
|
127
|
+
'client_secret' => credential.client_secret
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if response.status != 200
|
|
132
|
+
raise Legate::Auth::TokenExchangeError, "Failed to exchange code: #{response.body}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
parse_token_response(response.body)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Parse the token response into a standard format
|
|
139
|
+
def parse_token_response(response_body)
|
|
140
|
+
data = JSON.parse(response_body)
|
|
141
|
+
{
|
|
142
|
+
access_token: data['access_token'],
|
|
143
|
+
refresh_token: data['refresh_token'],
|
|
144
|
+
token_type: data['token_type'] || 'Bearer',
|
|
145
|
+
expires_at: data['expires_in'] ? Time.now.to_i + data['expires_in'].to_i : nil,
|
|
146
|
+
scope: data['scope']
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Apply tokens to requests
|
|
151
|
+
def apply_to_request(request, credential, tokens = nil)
|
|
152
|
+
if tokens
|
|
153
|
+
request[:headers] ||= {}
|
|
154
|
+
request[:headers]['Authorization'] = "#{tokens[:token_type]} #{tokens[:access_token]}"
|
|
155
|
+
end
|
|
156
|
+
request
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Check if tokens are valid
|
|
160
|
+
def tokens_valid?(tokens)
|
|
161
|
+
return false unless tokens
|
|
162
|
+
return false unless tokens[:access_token]
|
|
163
|
+
return false if tokens[:expires_at] && Time.now.to_i >= tokens[:expires_at]
|
|
164
|
+
true
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Refresh tokens
|
|
168
|
+
def refresh_tokens(credential, tokens)
|
|
169
|
+
return nil unless tokens[:refresh_token]
|
|
170
|
+
|
|
171
|
+
response = Excon.post(
|
|
172
|
+
@token_url,
|
|
173
|
+
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
|
174
|
+
body: URI.encode_www_form(
|
|
175
|
+
'grant_type' => 'refresh_token',
|
|
176
|
+
'refresh_token' => tokens[:refresh_token],
|
|
177
|
+
'client_id' => credential.client_id,
|
|
178
|
+
'client_secret' => credential.client_secret
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if response.status != 200
|
|
183
|
+
raise Legate::Auth::TokenRefreshError, "Failed to refresh token: #{response.body}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Merge the new tokens with the existing tokens
|
|
187
|
+
refresh_data = parse_token_response(response.body)
|
|
188
|
+
tokens.merge(refresh_data)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Step 2: Creating a Custom Authentication Coordinator
|
|
194
|
+
|
|
195
|
+
For interactive flows, create a coordinator to handle the authentication flow:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
module Legate
|
|
199
|
+
module Auth
|
|
200
|
+
module Coordinators
|
|
201
|
+
class CustomInteractiveCoordinator
|
|
202
|
+
def initialize(scheme, credential)
|
|
203
|
+
@scheme = scheme
|
|
204
|
+
@credential = credential
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Handle the auth request phase
|
|
208
|
+
def handle_auth_request(config)
|
|
209
|
+
# Generate state for CSRF protection
|
|
210
|
+
state = SecureRandom.hex(16)
|
|
211
|
+
|
|
212
|
+
# Generate the auth URL
|
|
213
|
+
auth_url = @scheme.generate_auth_url(
|
|
214
|
+
@credential,
|
|
215
|
+
config.redirect_uri,
|
|
216
|
+
state
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Return the auth URL and state
|
|
220
|
+
{
|
|
221
|
+
auth_url: auth_url,
|
|
222
|
+
state: state
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Handle the auth response phase
|
|
227
|
+
def handle_auth_response(config)
|
|
228
|
+
# Parse the auth response URI
|
|
229
|
+
uri = URI.parse(config.auth_response_uri)
|
|
230
|
+
params = CGI.parse(uri.query || '')
|
|
231
|
+
|
|
232
|
+
# Extract the authorization code
|
|
233
|
+
code = params['code']&.first
|
|
234
|
+
unless code
|
|
235
|
+
raise Legate::Auth::TokenExchangeError, "Missing authorization code in response"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Exchange the code for tokens
|
|
239
|
+
tokens = @scheme.exchange_auth_code(
|
|
240
|
+
@credential,
|
|
241
|
+
code,
|
|
242
|
+
config.redirect_uri
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Return the tokens
|
|
246
|
+
tokens
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Step 3: Using the Custom Scheme in Tools
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Create custom scheme and credential
|
|
258
|
+
custom_scheme = CustomInteractiveScheme.new(
|
|
259
|
+
auth_url: 'https://auth.example.com/authorize',
|
|
260
|
+
token_url: 'https://auth.example.com/token'
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Note: a custom credential still requires a valid auth_type. If your scheme
|
|
264
|
+
# doesn't map to a built-in type, :basic (with username/password) or one of the
|
|
265
|
+
# existing types can serve as the carrier for your client credentials.
|
|
266
|
+
custom_credential = Legate::Auth::Credential.new(
|
|
267
|
+
auth_type: :oauth2,
|
|
268
|
+
client_id: ENV['CUSTOM_CLIENT_ID'],
|
|
269
|
+
client_secret: ENV['CUSTOM_CLIENT_SECRET']
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Create a tool that uses the custom scheme and coordinator
|
|
273
|
+
class CustomApiTool < Legate::Tool
|
|
274
|
+
tool_description 'A tool that uses custom authentication'
|
|
275
|
+
|
|
276
|
+
def perform_execution(params, context)
|
|
277
|
+
scheme = CustomInteractiveScheme.new(
|
|
278
|
+
auth_url: 'https://auth.example.com/authorize',
|
|
279
|
+
token_url: 'https://auth.example.com/token'
|
|
280
|
+
)
|
|
281
|
+
credential = Legate::Auth::Credential.new(
|
|
282
|
+
auth_type: :oauth2,
|
|
283
|
+
client_id: ENV['CUSTOM_CLIENT_ID'],
|
|
284
|
+
client_secret: ENV['CUSTOM_CLIENT_SECRET']
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Drive interactive auth via context.with_authentication, which yields the
|
|
288
|
+
# auth request to your application for handling (see ToolContextExtension).
|
|
289
|
+
context.with_authentication do
|
|
290
|
+
# Once tokens are available, build a request and apply the scheme's tokens.
|
|
291
|
+
# Here we illustrate applying obtained tokens directly:
|
|
292
|
+
tokens = { access_token: 'obtained-access-token', token_type: 'Bearer' }
|
|
293
|
+
request = scheme.apply_to_request({ headers: {} }, credential, tokens)
|
|
294
|
+
|
|
295
|
+
conn = Excon.new('https://api.example.com')
|
|
296
|
+
response = conn.get(path: '/data', headers: request[:headers])
|
|
297
|
+
{ status: :success, result: JSON.parse(response.body) }
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Testing Custom Authentication Flows
|
|
304
|
+
|
|
305
|
+
### Unit Testing Schemes
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
RSpec.describe CustomInteractiveScheme do
|
|
309
|
+
let(:scheme) do
|
|
310
|
+
CustomInteractiveScheme.new(
|
|
311
|
+
auth_url: 'https://auth.example.com/authorize',
|
|
312
|
+
token_url: 'https://auth.example.com/token'
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
let(:credential) do
|
|
317
|
+
Legate::Auth::Credential.new(
|
|
318
|
+
auth_type: :oauth2,
|
|
319
|
+
client_id: 'test_client_id',
|
|
320
|
+
client_secret: 'test_client_secret'
|
|
321
|
+
)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
describe '#generate_auth_url' do
|
|
325
|
+
it 'generates a valid authorization URL' do
|
|
326
|
+
url = scheme.generate_auth_url(credential, 'https://callback.example.com', 'state123')
|
|
327
|
+
uri = URI.parse(url)
|
|
328
|
+
params = CGI.parse(uri.query)
|
|
329
|
+
|
|
330
|
+
expect(uri.host).to eq('auth.example.com')
|
|
331
|
+
expect(uri.path).to eq('/authorize')
|
|
332
|
+
expect(params['client_id']).to eq(['test_client_id'])
|
|
333
|
+
expect(params['redirect_uri']).to eq(['https://callback.example.com'])
|
|
334
|
+
expect(params['state']).to eq(['state123'])
|
|
335
|
+
expect(params['response_type']).to eq(['code'])
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
describe '#apply_to_request' do
|
|
340
|
+
it 'applies tokens to the request' do
|
|
341
|
+
tokens = {
|
|
342
|
+
access_token: 'test_access_token',
|
|
343
|
+
token_type: 'Bearer'
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
request = {}
|
|
347
|
+
modified_request = scheme.apply_to_request(request, credential, tokens)
|
|
348
|
+
|
|
349
|
+
expect(modified_request[:headers]['Authorization']).to eq('Bearer test_access_token')
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Add tests for other methods
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Integration Testing
|
|
358
|
+
|
|
359
|
+
> The runner harness below is illustrative pseudo-code for an end-to-end test.
|
|
360
|
+
> Adapt it to however your application drives tools and supplies the auth
|
|
361
|
+
> response (e.g. through `context.with_authentication`).
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
RSpec.describe 'Custom Authentication Integration' do
|
|
365
|
+
let(:scheme) do
|
|
366
|
+
CustomInteractiveScheme.new(
|
|
367
|
+
auth_url: 'https://auth.example.com/authorize',
|
|
368
|
+
token_url: 'https://auth.example.com/token'
|
|
369
|
+
)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
let(:credential) do
|
|
373
|
+
Legate::Auth::Credential.new(
|
|
374
|
+
auth_type: :oauth2,
|
|
375
|
+
client_id: 'test_client_id',
|
|
376
|
+
client_secret: 'test_client_secret'
|
|
377
|
+
)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
let(:runner) { Legate::Runner.new }
|
|
381
|
+
let(:tool) { CustomApiTool.new }
|
|
382
|
+
|
|
383
|
+
before do
|
|
384
|
+
# Mock the token endpoint
|
|
385
|
+
stub_request(:post, 'https://auth.example.com/token')
|
|
386
|
+
.to_return(
|
|
387
|
+
status: 200,
|
|
388
|
+
headers: { 'Content-Type' => 'application/json' },
|
|
389
|
+
body: {
|
|
390
|
+
access_token: 'test_access_token',
|
|
391
|
+
refresh_token: 'test_refresh_token',
|
|
392
|
+
token_type: 'Bearer',
|
|
393
|
+
expires_in: 3600
|
|
394
|
+
}.to_json
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Mock the API endpoint
|
|
398
|
+
stub_request(:get, 'https://api.example.com/data')
|
|
399
|
+
.with(headers: { 'Authorization' => 'Bearer test_access_token' })
|
|
400
|
+
.to_return(
|
|
401
|
+
status: 200,
|
|
402
|
+
headers: { 'Content-Type' => 'application/json' },
|
|
403
|
+
body: { success: true, data: [1, 2, 3] }.to_json
|
|
404
|
+
)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
it 'successfully completes the authentication flow' do
|
|
408
|
+
# Mock the auth response by setting up the Fiber directly
|
|
409
|
+
# In a real application, this would come from the user interaction
|
|
410
|
+
runner.auth_response = Legate::Auth::Config.new(
|
|
411
|
+
auth_response_uri: 'https://callback.example.com?code=test_auth_code&state=test_state',
|
|
412
|
+
redirect_uri: 'https://callback.example.com'
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Run the tool
|
|
416
|
+
result = runner.run(tool)
|
|
417
|
+
|
|
418
|
+
# Check the result
|
|
419
|
+
expect(result[:status]).to eq('success')
|
|
420
|
+
expect(result[:result]).to eq({ 'success' => true, 'data' => [1, 2, 3] })
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Handling Edge Cases
|
|
426
|
+
|
|
427
|
+
### Handling Authentication Errors
|
|
428
|
+
|
|
429
|
+
Ensure your custom authentication flow properly handles various error scenarios:
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
def exchange_auth_code(credential, code, redirect_uri)
|
|
433
|
+
response = Excon.post(
|
|
434
|
+
@token_url,
|
|
435
|
+
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
|
436
|
+
body: URI.encode_www_form(
|
|
437
|
+
'grant_type' => 'authorization_code',
|
|
438
|
+
'code' => code,
|
|
439
|
+
'redirect_uri' => redirect_uri,
|
|
440
|
+
'client_id' => credential.client_id,
|
|
441
|
+
'client_secret' => credential.client_secret
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
case response.status
|
|
446
|
+
when 200
|
|
447
|
+
parse_token_response(response.body)
|
|
448
|
+
when 400
|
|
449
|
+
error_data = JSON.parse(response.body) rescue { 'error' => 'invalid_request' }
|
|
450
|
+
handle_error(error_data)
|
|
451
|
+
when 401
|
|
452
|
+
raise Legate::Auth::CredentialError, "Invalid client credentials"
|
|
453
|
+
else
|
|
454
|
+
raise Legate::Auth::TokenExchangeError, "Failed to exchange code: #{response.body}"
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def handle_error(error_data)
|
|
459
|
+
error = error_data['error']
|
|
460
|
+
|
|
461
|
+
case error
|
|
462
|
+
when 'invalid_grant'
|
|
463
|
+
raise Legate::Auth::TokenExchangeError, "Invalid authorization code"
|
|
464
|
+
when 'invalid_client'
|
|
465
|
+
raise Legate::Auth::CredentialError, "Invalid client credentials"
|
|
466
|
+
when 'invalid_request'
|
|
467
|
+
raise Legate::Auth::TokenExchangeError, "Invalid request: #{error_data['error_description']}"
|
|
468
|
+
else
|
|
469
|
+
raise Legate::Auth::TokenExchangeError, "Authentication error: #{error_data['error_description'] || error}"
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Implementing Non-Standard Token Refresh
|
|
475
|
+
|
|
476
|
+
If your API has a non-standard token refresh mechanism:
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
def refresh_tokens(credential, tokens)
|
|
480
|
+
# Custom refresh logic
|
|
481
|
+
response = Excon.post(
|
|
482
|
+
@token_url,
|
|
483
|
+
headers: {
|
|
484
|
+
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
485
|
+
'Authorization' => "Basic #{Base64.strict_encode64("#{credential.client_id}:#{credential.client_secret}")}"
|
|
486
|
+
},
|
|
487
|
+
body: URI.encode_www_form(
|
|
488
|
+
'grant_type' => 'custom_refresh',
|
|
489
|
+
'refresh_token' => tokens[:refresh_token],
|
|
490
|
+
'custom_param' => 'custom_value'
|
|
491
|
+
)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if response.status != 200
|
|
495
|
+
raise Legate::Auth::TokenRefreshError, "Failed to refresh token: #{response.body}"
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Parse and return the refreshed tokens
|
|
499
|
+
refresh_data = parse_token_response(response.body)
|
|
500
|
+
|
|
501
|
+
# Preserve the original refresh token if a new one isn't provided
|
|
502
|
+
refresh_data[:refresh_token] ||= tokens[:refresh_token]
|
|
503
|
+
|
|
504
|
+
refresh_data
|
|
505
|
+
end
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Best Practices
|
|
509
|
+
|
|
510
|
+
1. **Security First**: Implement proper security measures, including CSRF protection, secure token storage, and HTTPS for all requests
|
|
511
|
+
2. **Error Handling**: Implement comprehensive error handling for all authentication steps
|
|
512
|
+
3. **Logging**: Add appropriate logging to help debug authentication issues
|
|
513
|
+
4. **Testing**: Write thorough tests for both normal and error cases
|
|
514
|
+
5. **Documentation**: Document your custom authentication scheme thoroughly
|
|
515
|
+
6. **Token Management**: Implement proper token lifecycle management (expiration, refresh)
|
|
516
|
+
7. **Follow Standards**: When possible, follow existing standards and conventions
|
|
517
|
+
|
|
518
|
+
## Related Topics
|
|
519
|
+
- [Authentication Configuration](./configuration)
|
|
520
|
+
- [Token Lifecycle Management](./token_lifecycle)
|
|
521
|
+
- [Secure Credential Storage](./secure_storage)
|
|
522
|
+
- [`Legate::Auth::Scheme` API Reference](../api_reference/scheme)
|
|
523
|
+
- [`Legate::ToolContext` Authentication Extensions](../api_reference/tool_context_extension)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Authentication Guides
|
|
2
|
+
|
|
3
|
+
This section provides detailed guides for implementing authentication in your Legate Ruby applications.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
- [Authentication Overview](./overview) - Core concepts and general workflow
|
|
8
|
+
- [Authentication Configuration](./configuration) - How to configure authentication
|
|
9
|
+
|
|
10
|
+
## Authentication Methods
|
|
11
|
+
|
|
12
|
+
- [API Key Authentication](./api_key) - Using API Keys in header or query parameters
|
|
13
|
+
- [HTTP Bearer Authentication](./bearer) - Using Bearer tokens
|
|
14
|
+
- [OAuth2 Authentication](./oauth2) - Complete OAuth2 implementation guide
|
|
15
|
+
- [OpenID Connect](./oidc) - Using OIDC authentication
|
|
16
|
+
- [Service Account Authentication](./service_account) - Using Service Accounts
|
|
17
|
+
|
|
18
|
+
## Advanced Topics
|
|
19
|
+
|
|
20
|
+
- [Token Lifecycle Management](./token_lifecycle) - Managing token expiration and refresh
|
|
21
|
+
- [Custom Authentication Flow](./custom_flow) - Building custom authentication flows
|
|
22
|
+
- [Secure Credential Storage](./secure_storage) - Best practices for credential security
|
|
23
|
+
- [Web UI Integration](./web_ui_integration) - Integrating authentication with the Web UI
|
|
24
|
+
- [Migrating from Earlier Versions](./migration) - Guide for upgrading from previous versions
|