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,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'legate'
|
|
5
|
+
require 'legate/auth'
|
|
6
|
+
require 'legate/auth/runner'
|
|
7
|
+
require 'legate/auth/schemes/oauth2'
|
|
8
|
+
require 'legate/tool_context'
|
|
9
|
+
require 'legate/web/server'
|
|
10
|
+
require 'launchy'
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
|
|
13
|
+
# This example demonstrates the fiber-based authentication flow
|
|
14
|
+
# It shows how to:
|
|
15
|
+
# 1. Set up an authentication runner
|
|
16
|
+
# 2. Use the auth_session method within a tool
|
|
17
|
+
# 3. Handle authentication requests and responses
|
|
18
|
+
# 4. Launch a browser for OAuth2 authorization
|
|
19
|
+
# 5. Start a temporary web server to receive the OAuth2 callback
|
|
20
|
+
|
|
21
|
+
class ExampleTool < Legate::Tool::Base
|
|
22
|
+
name :example_tool
|
|
23
|
+
description 'Example tool demonstrating fiber-based authentication'
|
|
24
|
+
version '1.0.0'
|
|
25
|
+
|
|
26
|
+
parameter :action, type: :symbol, required: true,
|
|
27
|
+
description: 'Action to perform (call_api, handle_response, run_with_server)'
|
|
28
|
+
parameter :request_id, type: :string, required: false,
|
|
29
|
+
description: 'The authentication request ID for handling responses'
|
|
30
|
+
parameter :response_uri, type: :string, required: false,
|
|
31
|
+
description: 'The response URI from the OAuth2 callback'
|
|
32
|
+
parameter :client_id, type: :string, required: false,
|
|
33
|
+
description: 'OAuth2 client ID'
|
|
34
|
+
parameter :client_secret, type: :string, required: false,
|
|
35
|
+
description: 'OAuth2 client secret'
|
|
36
|
+
|
|
37
|
+
def execute
|
|
38
|
+
case parameters[:action]
|
|
39
|
+
when :call_api
|
|
40
|
+
# Use with_authentication to create a fiber context with auth support
|
|
41
|
+
context.with_authentication do
|
|
42
|
+
call_api_with_auth
|
|
43
|
+
end
|
|
44
|
+
when :handle_response
|
|
45
|
+
# Handle an authentication response
|
|
46
|
+
handle_auth_response
|
|
47
|
+
when :run_with_server
|
|
48
|
+
# Run with a local server to automatically handle the OAuth2 callback
|
|
49
|
+
run_with_local_server
|
|
50
|
+
else
|
|
51
|
+
raise Legate::ToolError, "Unknown action: #{parameters[:action]}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def call_api_with_auth
|
|
58
|
+
# Define an OAuth2 scheme
|
|
59
|
+
oauth2_scheme = create_oauth2_scheme
|
|
60
|
+
|
|
61
|
+
# Define a credential
|
|
62
|
+
credential = create_credential
|
|
63
|
+
|
|
64
|
+
# Start an authentication session
|
|
65
|
+
# This will yield the fiber if authentication is needed
|
|
66
|
+
token = context.auth_session(
|
|
67
|
+
oauth2_scheme,
|
|
68
|
+
credential,
|
|
69
|
+
redirect_uri: 'http://localhost:3000/auth/callback'
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# If we get here, we have a valid token
|
|
73
|
+
if token
|
|
74
|
+
# Simulate an API call with the token
|
|
75
|
+
api_call_result = simulate_api_call(token)
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
status: :success,
|
|
79
|
+
message: 'Successfully authenticated and called API',
|
|
80
|
+
token_type: token.token_type,
|
|
81
|
+
expires_in: token.expires_in,
|
|
82
|
+
scope: token.scope,
|
|
83
|
+
api_result: api_call_result
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
{ status: :error, message: 'Failed to authenticate' }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def handle_auth_response
|
|
91
|
+
# Get required parameters
|
|
92
|
+
request_id = parameters[:request_id]
|
|
93
|
+
response_uri = parameters[:response_uri]
|
|
94
|
+
|
|
95
|
+
raise Legate::ToolArgumentError, 'Both request_id and response_uri are required' unless request_id && response_uri
|
|
96
|
+
|
|
97
|
+
# Create a response object
|
|
98
|
+
response = { 'response_uri' => response_uri }
|
|
99
|
+
|
|
100
|
+
# Handle the authentication response
|
|
101
|
+
result = context.handle_auth_response(request_id, response)
|
|
102
|
+
|
|
103
|
+
# Return the result
|
|
104
|
+
{
|
|
105
|
+
status: result[:status],
|
|
106
|
+
message: result[:status] == :completed ? 'Authentication completed' : 'Authentication in progress',
|
|
107
|
+
details: result
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run_with_local_server
|
|
112
|
+
# Create a web server to handle the OAuth2 callback
|
|
113
|
+
server = create_callback_server
|
|
114
|
+
|
|
115
|
+
begin
|
|
116
|
+
# Start the server
|
|
117
|
+
server_thread = Thread.new { server.start }
|
|
118
|
+
|
|
119
|
+
# Use with_authentication to create a fiber context with auth support
|
|
120
|
+
result = context.with_authentication do
|
|
121
|
+
# This callback will be called when an authentication request is yielded
|
|
122
|
+
|
|
123
|
+
# Call API with authentication
|
|
124
|
+
call_api_with_auth
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
result
|
|
128
|
+
ensure
|
|
129
|
+
# Stop the server
|
|
130
|
+
server.stop
|
|
131
|
+
# Wait for server thread to finish
|
|
132
|
+
server_thread.join if server_thread
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def create_oauth2_scheme
|
|
137
|
+
Legate::Auth::Schemes::OAuth2.new(
|
|
138
|
+
authorization_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
139
|
+
token_url: 'https://oauth2.googleapis.com/token',
|
|
140
|
+
scopes: %w[email profile],
|
|
141
|
+
use_pkce: true,
|
|
142
|
+
additional_params: {
|
|
143
|
+
prompt: 'consent',
|
|
144
|
+
access_type: 'offline'
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def create_credential
|
|
150
|
+
client_id = parameters[:client_id] || ENV['OAUTH_CLIENT_ID'] || 'your-client-id'
|
|
151
|
+
client_secret = parameters[:client_secret] || ENV['OAUTH_CLIENT_SECRET'] || 'your-client-secret'
|
|
152
|
+
|
|
153
|
+
Legate::Auth::Credential.new(
|
|
154
|
+
auth_type: :oauth2,
|
|
155
|
+
client_id: client_id,
|
|
156
|
+
client_secret: client_secret
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def simulate_api_call(token)
|
|
161
|
+
# In a real application, you would make an actual API call using the token
|
|
162
|
+
# This is just a simulation
|
|
163
|
+
{
|
|
164
|
+
api_name: 'Example API',
|
|
165
|
+
called_at: Time.now.iso8601,
|
|
166
|
+
authorization: "#{token.token_type} #{token.access_token[0..5]}...[truncated]"
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def create_callback_server
|
|
171
|
+
# Create a simple web server to handle OAuth2 callbacks
|
|
172
|
+
server = Legate::Web::Server.new(port: 3000)
|
|
173
|
+
|
|
174
|
+
# Add a route to handle the OAuth2 callback
|
|
175
|
+
server.add_route('GET', '/auth/callback') do |req, res|
|
|
176
|
+
# Extract the authorization code from the query parameters
|
|
177
|
+
code = req.query['code']
|
|
178
|
+
state = req.query['state']
|
|
179
|
+
error = req.query['error']
|
|
180
|
+
|
|
181
|
+
if error
|
|
182
|
+
res.status = 400
|
|
183
|
+
res.body = "Authentication failed: #{error}"
|
|
184
|
+
elsif code
|
|
185
|
+
# Convert the request to a response URI
|
|
186
|
+
response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
|
|
187
|
+
|
|
188
|
+
# Find the active auth request
|
|
189
|
+
active_requests = context.instance_variable_get(:@auth_runner)&.instance_variable_get(:@active_coordinators)
|
|
190
|
+
request_id = active_requests&.keys&.first
|
|
191
|
+
|
|
192
|
+
if request_id
|
|
193
|
+
# Handle the auth response
|
|
194
|
+
result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
|
|
195
|
+
|
|
196
|
+
# Show a success page
|
|
197
|
+
res.status = 200
|
|
198
|
+
res.body = '<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the application.</p></body></html>'
|
|
199
|
+
else
|
|
200
|
+
res.status = 400
|
|
201
|
+
res.body = 'No active authentication request found'
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
res.status = 400
|
|
205
|
+
res.body = 'Missing required parameters'
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
server
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def handle_auth_request_with_browser(auth_request)
|
|
213
|
+
# Extract the authorization URL from the auth request
|
|
214
|
+
if auth_request[:auth_request][:type] == 'authorization_request'
|
|
215
|
+
auth_url = auth_request[:auth_request][:url]
|
|
216
|
+
|
|
217
|
+
# Open the browser with the authorization URL
|
|
218
|
+
puts "Opening browser to authorize: #{auth_url}"
|
|
219
|
+
Launchy.open(auth_url)
|
|
220
|
+
|
|
221
|
+
puts 'Waiting for OAuth2 callback...'
|
|
222
|
+
else
|
|
223
|
+
puts "Unhandled auth request type: #{auth_request[:auth_request][:type]}"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Return nil to continue waiting for the callback
|
|
227
|
+
nil
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Register the tool
|
|
232
|
+
Legate.register_tool(ExampleTool)
|
|
233
|
+
|
|
234
|
+
# Example usage:
|
|
235
|
+
# 1. First call with action: :call_api
|
|
236
|
+
# - This will start authentication and yield an auth request
|
|
237
|
+
# 2. Use the request_id from the first call and pass it along with a response_uri
|
|
238
|
+
# - Call with action: :handle_response, request_id: "...", response_uri: "..."
|
|
239
|
+
# 3. Or use action: :run_with_server to launch a browser and handle the callback automatically
|
|
240
|
+
if $PROGRAM_NAME == __FILE__
|
|
241
|
+
# Setup a session service for the example
|
|
242
|
+
require 'legate/session_service/in_memory'
|
|
243
|
+
session_service = Legate::SessionService::InMemory.new
|
|
244
|
+
|
|
245
|
+
# Create a tool context with the session service
|
|
246
|
+
context = Legate::ToolContext.new(session_service: session_service)
|
|
247
|
+
|
|
248
|
+
# Create and run the tool
|
|
249
|
+
tool = ExampleTool.new
|
|
250
|
+
|
|
251
|
+
begin
|
|
252
|
+
# Run with a local server to automatically handle the OAuth2 callback
|
|
253
|
+
# This is the most user-friendly approach for a CLI tool
|
|
254
|
+
if ARGV.include?('--with-server')
|
|
255
|
+
result = tool.run(context, action: :run_with_server)
|
|
256
|
+
puts "Result: #{result.inspect}"
|
|
257
|
+
else
|
|
258
|
+
# Attempt to call API - this will yield for authentication
|
|
259
|
+
result = tool.run(context, action: :call_api)
|
|
260
|
+
|
|
261
|
+
# The result will include auth_request if authentication is needed
|
|
262
|
+
if result.is_a?(Hash) && result[:auth_request]
|
|
263
|
+
puts 'Authentication required. Please visit:'
|
|
264
|
+
puts result[:auth_request][:url] if result[:auth_request][:url]
|
|
265
|
+
puts "\nAfter completing authentication, run:"
|
|
266
|
+
puts "ruby #{__FILE__} --handle-response --request-id #{result[:request_id]} --response-uri 'PASTE_RESPONSE_URI_HERE'"
|
|
267
|
+
else
|
|
268
|
+
puts "Result: #{result.inspect}"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
rescue StandardError => e
|
|
272
|
+
puts "Error: #{e.class}: #{e.message}"
|
|
273
|
+
puts e.backtrace.join("\n") if ENV['DEBUG']
|
|
274
|
+
|
|
275
|
+
if e.message.include?('Authentication session not available')
|
|
276
|
+
puts "\nNote: This example requires proper OAuth2 credentials."
|
|
277
|
+
puts 'You can provide them as environment variables:'
|
|
278
|
+
puts "OAUTH_CLIENT_ID='your-client-id' OAUTH_CLIENT_SECRET='your-client-secret' ruby #{__FILE__} --with-server"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'legate'
|
|
5
|
+
require 'legate/auth'
|
|
6
|
+
require 'legate/auth/runner'
|
|
7
|
+
require 'legate/auth/schemes/openid_connect'
|
|
8
|
+
require 'legate/tool_context'
|
|
9
|
+
require 'legate/web/server'
|
|
10
|
+
require 'launchy'
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
require 'json'
|
|
13
|
+
|
|
14
|
+
# This example demonstrates the fiber-based OIDC (OpenID Connect) authentication flow
|
|
15
|
+
# It shows how to:
|
|
16
|
+
# 1. Set up an authentication runner with OIDC
|
|
17
|
+
# 2. Use the auth_session method within a tool
|
|
18
|
+
# 3. Handle authentication requests and responses
|
|
19
|
+
# 4. Retrieve user information from the identity provider
|
|
20
|
+
# 5. Launch a browser for OIDC authorization
|
|
21
|
+
# 6. Start a temporary web server to receive the OIDC callback
|
|
22
|
+
|
|
23
|
+
class OIDCExampleTool < Legate::Tool::Base
|
|
24
|
+
name :oidc_example_tool
|
|
25
|
+
description 'Example tool demonstrating fiber-based OIDC authentication'
|
|
26
|
+
version '1.0.0'
|
|
27
|
+
|
|
28
|
+
parameter :action, type: :symbol, required: true,
|
|
29
|
+
description: 'Action to perform (call_api, handle_response, run_with_server)'
|
|
30
|
+
parameter :request_id, type: :string, required: false,
|
|
31
|
+
description: 'The authentication request ID for handling responses'
|
|
32
|
+
parameter :response_uri, type: :string, required: false,
|
|
33
|
+
description: 'The response URI from the OIDC callback'
|
|
34
|
+
parameter :client_id, type: :string, required: false,
|
|
35
|
+
description: 'OIDC client ID'
|
|
36
|
+
parameter :client_secret, type: :string, required: false,
|
|
37
|
+
description: 'OIDC client secret'
|
|
38
|
+
parameter :provider, type: :string, required: false, default: 'google',
|
|
39
|
+
description: 'OIDC provider (google, auth0, etc.)'
|
|
40
|
+
|
|
41
|
+
def execute
|
|
42
|
+
case parameters[:action]
|
|
43
|
+
when :call_api
|
|
44
|
+
# Use with_authentication to create a fiber context with auth support
|
|
45
|
+
context.with_authentication do
|
|
46
|
+
call_api_with_auth
|
|
47
|
+
end
|
|
48
|
+
when :handle_response
|
|
49
|
+
# Handle an authentication response
|
|
50
|
+
handle_auth_response
|
|
51
|
+
when :run_with_server
|
|
52
|
+
# Run with a local server to automatically handle the OIDC callback
|
|
53
|
+
run_with_local_server
|
|
54
|
+
else
|
|
55
|
+
raise Legate::ToolError, "Unknown action: #{parameters[:action]}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def call_api_with_auth
|
|
62
|
+
# Define an OIDC scheme
|
|
63
|
+
oidc_scheme = create_oidc_scheme
|
|
64
|
+
|
|
65
|
+
# Define a credential
|
|
66
|
+
credential = create_credential
|
|
67
|
+
|
|
68
|
+
# Start an authentication session
|
|
69
|
+
# This will yield the fiber if authentication is needed
|
|
70
|
+
token = context.auth_session(
|
|
71
|
+
oidc_scheme,
|
|
72
|
+
credential,
|
|
73
|
+
redirect_uri: 'http://localhost:3000/auth/callback'
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# If we get here, we have a valid token
|
|
77
|
+
if token
|
|
78
|
+
# Get user info from the token metadata or fetch it if needed
|
|
79
|
+
user_info = token.metadata&.dig(:userinfo) || fetch_user_info(token, oidc_scheme)
|
|
80
|
+
|
|
81
|
+
# Simulate an API call with the token
|
|
82
|
+
api_call_result = simulate_api_call(token)
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
status: :success,
|
|
86
|
+
message: 'Successfully authenticated and called API',
|
|
87
|
+
token_type: token.token_type,
|
|
88
|
+
expires_in: token.expires_in,
|
|
89
|
+
scope: token.scope,
|
|
90
|
+
user_info: user_info,
|
|
91
|
+
api_result: api_call_result
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
{ status: :error, message: 'Failed to authenticate' }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle_auth_response
|
|
99
|
+
# Get required parameters
|
|
100
|
+
request_id = parameters[:request_id]
|
|
101
|
+
response_uri = parameters[:response_uri]
|
|
102
|
+
|
|
103
|
+
raise Legate::ToolArgumentError, 'Both request_id and response_uri are required' unless request_id && response_uri
|
|
104
|
+
|
|
105
|
+
# Create a response object
|
|
106
|
+
response = { 'response_uri' => response_uri }
|
|
107
|
+
|
|
108
|
+
# Handle the authentication response
|
|
109
|
+
result = context.handle_auth_response(request_id, response)
|
|
110
|
+
|
|
111
|
+
# Return the result
|
|
112
|
+
{
|
|
113
|
+
status: result[:status],
|
|
114
|
+
message: result[:status] == :completed ? 'Authentication completed' : 'Authentication in progress',
|
|
115
|
+
details: result
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def run_with_local_server
|
|
120
|
+
# Create a web server to handle the OIDC callback
|
|
121
|
+
server = create_callback_server
|
|
122
|
+
|
|
123
|
+
begin
|
|
124
|
+
# Start the server
|
|
125
|
+
server_thread = Thread.new { server.start }
|
|
126
|
+
|
|
127
|
+
# Use with_authentication to create a fiber context with auth support
|
|
128
|
+
result = context.with_authentication do
|
|
129
|
+
# This callback will be called when an authentication request is yielded
|
|
130
|
+
|
|
131
|
+
# Call API with authentication
|
|
132
|
+
call_api_with_auth
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
result
|
|
136
|
+
ensure
|
|
137
|
+
# Stop the server
|
|
138
|
+
server.stop
|
|
139
|
+
# Wait for server thread to finish
|
|
140
|
+
server_thread.join if server_thread
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def create_oidc_scheme
|
|
145
|
+
# Configure based on the selected provider
|
|
146
|
+
case parameters[:provider]&.downcase
|
|
147
|
+
when 'google'
|
|
148
|
+
Legate::Auth::Schemes::OpenIDConnect.new(
|
|
149
|
+
authorization_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
150
|
+
token_url: 'https://oauth2.googleapis.com/token',
|
|
151
|
+
userinfo_url: 'https://openidconnect.googleapis.com/v1/userinfo',
|
|
152
|
+
scopes: %w[openid email profile],
|
|
153
|
+
fetch_userinfo: true,
|
|
154
|
+
use_pkce: true,
|
|
155
|
+
additional_params: {
|
|
156
|
+
prompt: 'consent',
|
|
157
|
+
access_type: 'offline'
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
when 'auth0'
|
|
161
|
+
# Your Auth0 domain - replace with your actual domain
|
|
162
|
+
domain = ENV['AUTH0_DOMAIN'] || 'your-domain.auth0.com'
|
|
163
|
+
|
|
164
|
+
Legate::Auth::Schemes::OpenIDConnect.new(
|
|
165
|
+
authorization_url: "https://#{domain}/authorize",
|
|
166
|
+
token_url: "https://#{domain}/oauth/token",
|
|
167
|
+
userinfo_url: "https://#{domain}/userinfo",
|
|
168
|
+
scopes: %w[openid email profile],
|
|
169
|
+
fetch_userinfo: true,
|
|
170
|
+
use_pkce: true
|
|
171
|
+
)
|
|
172
|
+
else
|
|
173
|
+
# Default to a generic configuration
|
|
174
|
+
Legate::Auth::Schemes::OpenIDConnect.new(
|
|
175
|
+
authorization_url: 'https://example.com/authorize',
|
|
176
|
+
token_url: 'https://example.com/token',
|
|
177
|
+
userinfo_url: 'https://example.com/userinfo',
|
|
178
|
+
scopes: %w[openid email profile],
|
|
179
|
+
fetch_userinfo: true,
|
|
180
|
+
use_pkce: true
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def create_credential
|
|
186
|
+
client_id = parameters[:client_id] || ENV['OIDC_CLIENT_ID'] || 'your-client-id'
|
|
187
|
+
client_secret = parameters[:client_secret] || ENV['OIDC_CLIENT_SECRET'] || 'your-client-secret'
|
|
188
|
+
|
|
189
|
+
Legate::Auth::Credential.new(
|
|
190
|
+
auth_type: :oidc,
|
|
191
|
+
client_id: client_id,
|
|
192
|
+
client_secret: client_secret
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def fetch_user_info(token, oidc_scheme)
|
|
197
|
+
return {} unless token&.access_token && oidc_scheme.userinfo_url
|
|
198
|
+
|
|
199
|
+
begin
|
|
200
|
+
oidc_scheme.fetch_userinfo(token)
|
|
201
|
+
rescue Legate::Auth::Error => e
|
|
202
|
+
Legate.logger.warn("Failed to fetch userinfo: #{e.message}")
|
|
203
|
+
{ error: e.message }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def simulate_api_call(token)
|
|
208
|
+
# In a real application, you would make an actual API call using the token
|
|
209
|
+
# This is just a simulation
|
|
210
|
+
{
|
|
211
|
+
api_name: 'Example OIDC API',
|
|
212
|
+
called_at: Time.now.iso8601,
|
|
213
|
+
authorization: "#{token.token_type} #{token.access_token[0..5]}...[truncated]"
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def create_callback_server
|
|
218
|
+
# Create a simple web server to handle OIDC callbacks
|
|
219
|
+
server = Legate::Web::Server.new(port: 3000)
|
|
220
|
+
|
|
221
|
+
# Add a route to handle the OIDC callback
|
|
222
|
+
server.add_route('GET', '/auth/callback') do |req, res|
|
|
223
|
+
# Extract the authorization code from the query parameters
|
|
224
|
+
code = req.query['code']
|
|
225
|
+
state = req.query['state']
|
|
226
|
+
error = req.query['error']
|
|
227
|
+
|
|
228
|
+
if error
|
|
229
|
+
res.status = 400
|
|
230
|
+
res.body = "Authentication failed: #{error}"
|
|
231
|
+
elsif code
|
|
232
|
+
# Convert the request to a response URI
|
|
233
|
+
response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
|
|
234
|
+
|
|
235
|
+
# Find the active auth request
|
|
236
|
+
active_requests = context.instance_variable_get(:@auth_runner)&.instance_variable_get(:@active_coordinators)
|
|
237
|
+
request_id = active_requests&.keys&.first
|
|
238
|
+
|
|
239
|
+
if request_id
|
|
240
|
+
# Handle the auth response
|
|
241
|
+
result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
|
|
242
|
+
|
|
243
|
+
# Show a success page with user info if available
|
|
244
|
+
res.status = 200
|
|
245
|
+
|
|
246
|
+
# Try to extract user info if available
|
|
247
|
+
user_info = result[:credential]&.metadata&.dig(:userinfo)
|
|
248
|
+
user_info_html = ''
|
|
249
|
+
|
|
250
|
+
if user_info
|
|
251
|
+
user_info_html = "<div style='margin-top: 20px; padding: 10px; background-color: #f5f5f5; border-radius: 5px;'>"
|
|
252
|
+
user_info_html += '<h2>User Information</h2>'
|
|
253
|
+
user_info_html += '<ul>'
|
|
254
|
+
|
|
255
|
+
# Display some common OIDC user info fields
|
|
256
|
+
user_info_html += "<li><strong>Name:</strong> #{user_info['name']}</li>" if user_info['name']
|
|
257
|
+
user_info_html += "<li><strong>Email:</strong> #{user_info['email']}</li>" if user_info['email']
|
|
258
|
+
user_info_html += "<li><strong>Picture:</strong> <img src='#{user_info['picture']}' width='50' height='50' style='border-radius: 50%;'/></li>" if user_info['picture']
|
|
259
|
+
|
|
260
|
+
user_info_html += '</ul></div>'
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
res.body = <<~HTML
|
|
264
|
+
<!DOCTYPE html>
|
|
265
|
+
<html>
|
|
266
|
+
<head>
|
|
267
|
+
<title>Authentication Successful</title>
|
|
268
|
+
<style>
|
|
269
|
+
body { font-family: Arial, sans-serif; margin: 40px; text-align: center; }
|
|
270
|
+
h1 { color: #2c3e50; }
|
|
271
|
+
.container { max-width: 600px; margin: 0 auto; }
|
|
272
|
+
.success { color: #27ae60; }
|
|
273
|
+
</style>
|
|
274
|
+
</head>
|
|
275
|
+
<body>
|
|
276
|
+
<div class="container">
|
|
277
|
+
<h1>Authentication Successful</h1>
|
|
278
|
+
<p class="success">✓ You have successfully authenticated</p>
|
|
279
|
+
<p>You can close this window and return to the application.</p>
|
|
280
|
+
#{user_info_html}
|
|
281
|
+
</div>
|
|
282
|
+
</body>
|
|
283
|
+
</html>
|
|
284
|
+
HTML
|
|
285
|
+
else
|
|
286
|
+
res.status = 400
|
|
287
|
+
res.body = 'No active authentication request found'
|
|
288
|
+
end
|
|
289
|
+
else
|
|
290
|
+
res.status = 400
|
|
291
|
+
res.body = 'Missing required parameters'
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
server
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def handle_auth_request_with_browser(auth_request)
|
|
299
|
+
# Extract the authorization URL from the auth request
|
|
300
|
+
if auth_request[:auth_request][:type] == 'authorization_request'
|
|
301
|
+
auth_url = auth_request[:auth_request][:url]
|
|
302
|
+
|
|
303
|
+
# Open the browser with the authorization URL
|
|
304
|
+
puts "Opening browser to authorize: #{auth_url}"
|
|
305
|
+
Launchy.open(auth_url)
|
|
306
|
+
|
|
307
|
+
puts 'Waiting for OIDC callback...'
|
|
308
|
+
else
|
|
309
|
+
puts "Unhandled auth request type: #{auth_request[:auth_request][:type]}"
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Return nil to continue waiting for the callback
|
|
313
|
+
nil
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Register the tool
|
|
318
|
+
Legate.register_tool(OIDCExampleTool)
|
|
319
|
+
|
|
320
|
+
# Example usage:
|
|
321
|
+
# 1. Run with a local server to automatically handle the OIDC callback:
|
|
322
|
+
# ruby fiber_oidc_example.rb --with-server --provider google
|
|
323
|
+
#
|
|
324
|
+
# 2. Set credentials via environment variables:
|
|
325
|
+
# OIDC_CLIENT_ID='your-client-id' OIDC_CLIENT_SECRET='your-client-secret' ruby fiber_oidc_example.rb --with-server
|
|
326
|
+
if $PROGRAM_NAME == __FILE__
|
|
327
|
+
# Setup a session service for the example
|
|
328
|
+
require 'legate/session_service/in_memory'
|
|
329
|
+
session_service = Legate::SessionService::InMemory.new
|
|
330
|
+
|
|
331
|
+
# Create a tool context with the session service
|
|
332
|
+
context = Legate::ToolContext.new(session_service: session_service)
|
|
333
|
+
|
|
334
|
+
# Process command line arguments
|
|
335
|
+
provider = 'google' # Default provider
|
|
336
|
+
|
|
337
|
+
ARGV.each_with_index do |arg, index|
|
|
338
|
+
provider = ARGV[index + 1] if arg == '--provider' && ARGV[index + 1]
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Create and run the tool
|
|
342
|
+
tool = OIDCExampleTool.new
|
|
343
|
+
|
|
344
|
+
begin
|
|
345
|
+
# Run with a local server to automatically handle the OIDC callback
|
|
346
|
+
if ARGV.include?('--with-server')
|
|
347
|
+
puts "Starting OIDC authentication flow with provider: #{provider}"
|
|
348
|
+
result = tool.run(context, action: :run_with_server, provider: provider)
|
|
349
|
+
puts "\nResult:"
|
|
350
|
+
begin
|
|
351
|
+
puts JSON.pretty_generate(result)
|
|
352
|
+
rescue StandardError
|
|
353
|
+
puts result.inspect
|
|
354
|
+
end
|
|
355
|
+
elsif ARGV.include?('--handle-response')
|
|
356
|
+
# Extract request_id and response_uri from args
|
|
357
|
+
request_id = nil
|
|
358
|
+
response_uri = nil
|
|
359
|
+
|
|
360
|
+
ARGV.each_with_index do |arg, index|
|
|
361
|
+
if arg == '--request-id' && ARGV[index + 1]
|
|
362
|
+
request_id = ARGV[index + 1]
|
|
363
|
+
elsif arg == '--response-uri' && ARGV[index + 1]
|
|
364
|
+
response_uri = ARGV[index + 1]
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if request_id && response_uri
|
|
369
|
+
result = tool.run(context, action: :handle_response, request_id: request_id, response_uri: response_uri)
|
|
370
|
+
puts "Result: #{result.inspect}"
|
|
371
|
+
else
|
|
372
|
+
puts 'Error: --request-id and --response-uri parameters are required for --handle-response'
|
|
373
|
+
end
|
|
374
|
+
else
|
|
375
|
+
# Attempt to call API - this will yield for authentication
|
|
376
|
+
result = tool.run(context, action: :call_api, provider: provider)
|
|
377
|
+
|
|
378
|
+
# The result will include auth_request if authentication is needed
|
|
379
|
+
if result.is_a?(Hash) && result[:auth_request]
|
|
380
|
+
puts 'Authentication required. Please visit:'
|
|
381
|
+
puts result[:auth_request][:url] if result[:auth_request][:url]
|
|
382
|
+
puts "\nAfter completing authentication, run:"
|
|
383
|
+
puts "ruby #{__FILE__} --handle-response --request-id #{result[:request_id]} --response-uri 'PASTE_RESPONSE_URI_HERE'"
|
|
384
|
+
else
|
|
385
|
+
puts 'Result:'
|
|
386
|
+
begin
|
|
387
|
+
puts JSON.pretty_generate(result)
|
|
388
|
+
rescue StandardError
|
|
389
|
+
puts result.inspect
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
rescue StandardError => e
|
|
394
|
+
puts "Error: #{e.class}: #{e.message}"
|
|
395
|
+
puts e.backtrace.join("\n") if ENV['DEBUG']
|
|
396
|
+
|
|
397
|
+
if e.message.include?('Authentication session not available')
|
|
398
|
+
puts "\nNote: This example requires proper OIDC credentials."
|
|
399
|
+
puts 'You can provide them as environment variables:'
|
|
400
|
+
puts "OIDC_CLIENT_ID='your-client-id' OIDC_CLIENT_SECRET='your-client-secret' ruby #{__FILE__} --with-server --provider google"
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|