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,346 @@
|
|
|
1
|
+
# File: lib/legate/auth/schemes/openid_connect.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'oauth2'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'net/http'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'jwt'
|
|
9
|
+
require_relative 'oauth2'
|
|
10
|
+
|
|
11
|
+
module Legate
|
|
12
|
+
module Auth
|
|
13
|
+
module Schemes
|
|
14
|
+
# Implements OpenID Connect authentication
|
|
15
|
+
# Extends OAuth2 with OpenID Connect specific features
|
|
16
|
+
class OpenIDConnect < OAuth2
|
|
17
|
+
# @return [String, nil] The URL for the OpenID Connect discovery document
|
|
18
|
+
attr_reader :discovery_url
|
|
19
|
+
|
|
20
|
+
# @return [String, nil] The URL for the JWK Set
|
|
21
|
+
attr_reader :jwks_url
|
|
22
|
+
|
|
23
|
+
# @return [String, nil] The userinfo endpoint URL
|
|
24
|
+
attr_reader :userinfo_url
|
|
25
|
+
|
|
26
|
+
# @return [String, nil] The issuer identifier
|
|
27
|
+
attr_reader :issuer
|
|
28
|
+
|
|
29
|
+
# @return [String, nil] The provider URI
|
|
30
|
+
attr_reader :provider_uri
|
|
31
|
+
|
|
32
|
+
# @return [String] The client ID
|
|
33
|
+
attr_reader :client_id
|
|
34
|
+
|
|
35
|
+
# Initialize a new OpenID Connect scheme
|
|
36
|
+
# @param authorization_url [String, nil] The authorization URL
|
|
37
|
+
# @param token_url [String, nil] The token URL
|
|
38
|
+
# @param discovery_url [String, nil] The URL for the discovery document (optional if endpoints provided)
|
|
39
|
+
# @param jwks_url [String, nil] The URL for the JWKS document (optional if discovery URL provided)
|
|
40
|
+
# @param userinfo_url [String, nil] The URL for the userinfo endpoint (optional)
|
|
41
|
+
# @param scopes [Array<String>, String, nil] The requested scopes
|
|
42
|
+
# @param use_pkce [Boolean] Whether to use PKCE
|
|
43
|
+
# @param additional_params [Hash, nil] Additional parameters for authorization requests
|
|
44
|
+
# @param revocation_url [String, nil] The URL for the revocation endpoint
|
|
45
|
+
# @param client_id [String, nil] The client ID
|
|
46
|
+
# @param client_secret [String, nil] The client secret
|
|
47
|
+
# @param redirect_uri [String, nil] The redirect URI
|
|
48
|
+
# @param kwargs [Hash] Additional options to pass to the OAuth2 parent class
|
|
49
|
+
# @param config [Hash] A config hash containing all options (alternative to individual parameters)
|
|
50
|
+
def initialize(first_arg = nil, authorization_url: nil, token_url: nil, discovery_url: nil,
|
|
51
|
+
jwks_url: nil, userinfo_url: nil, scopes: nil, use_pkce: true,
|
|
52
|
+
additional_params: nil, revocation_url: nil, client_id: nil,
|
|
53
|
+
client_secret: nil, redirect_uri: nil, **kwargs)
|
|
54
|
+
# Handle direct hash configuration in first_arg or config param
|
|
55
|
+
config = first_arg if first_arg.is_a?(Hash)
|
|
56
|
+
|
|
57
|
+
if config.is_a?(Hash)
|
|
58
|
+
# Extract OpenID Connect specific properties from config
|
|
59
|
+
@discovery_url = config[:discovery_url] || config[:provider_uri] && "#{config[:provider_uri]}/.well-known/openid-configuration"
|
|
60
|
+
@jwks_url = config[:jwks_url]
|
|
61
|
+
@userinfo_url = config[:userinfo_url]
|
|
62
|
+
@client_id = config[:client_id]
|
|
63
|
+
@client_secret = config[:client_secret]
|
|
64
|
+
@redirect_uri = config[:redirect_uri]
|
|
65
|
+
@provider_uri = config[:provider_uri]
|
|
66
|
+
@issuer = config[:issuer]
|
|
67
|
+
authorization_url = config[:authorization_url] || config[:authorization_endpoint]
|
|
68
|
+
token_url = config[:token_url] || config[:token_endpoint]
|
|
69
|
+
scopes = config[:scopes] || config[:scope]
|
|
70
|
+
use_pkce = config.key?(:use_pkce) ? config[:use_pkce] : true
|
|
71
|
+
additional_params = config[:additional_params]
|
|
72
|
+
revocation_url = config[:revocation_url]
|
|
73
|
+
|
|
74
|
+
# Move any remaining options to kwargs
|
|
75
|
+
extra_opts = config.reject { |k, _|
|
|
76
|
+
%i[discovery_url jwks_url userinfo_url client_id
|
|
77
|
+
client_secret redirect_uri provider_uri issuer authorization_url
|
|
78
|
+
authorization_endpoint token_url token_endpoint scopes scope
|
|
79
|
+
use_pkce additional_params revocation_url].include?(k)
|
|
80
|
+
}
|
|
81
|
+
kwargs = kwargs.merge(extra_opts)
|
|
82
|
+
else
|
|
83
|
+
# Store OpenID Connect specific properties from parameters
|
|
84
|
+
@discovery_url = discovery_url
|
|
85
|
+
@jwks_url = jwks_url
|
|
86
|
+
@userinfo_url = userinfo_url
|
|
87
|
+
@client_id = client_id
|
|
88
|
+
@client_secret = client_secret
|
|
89
|
+
@redirect_uri = redirect_uri
|
|
90
|
+
@provider_uri = kwargs[:provider_uri]
|
|
91
|
+
@issuer = kwargs[:issuer]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# If discovery URL is provided, try to fetch endpoints
|
|
95
|
+
if @discovery_url && (authorization_url.nil? || token_url.nil? || @userinfo_url.nil?)
|
|
96
|
+
endpoints = discover_endpoints
|
|
97
|
+
authorization_url ||= endpoints[:authorization_endpoint]
|
|
98
|
+
token_url ||= endpoints[:token_endpoint]
|
|
99
|
+
@jwks_url ||= endpoints[:jwks_uri]
|
|
100
|
+
@userinfo_url ||= endpoints[:userinfo_endpoint]
|
|
101
|
+
@issuer ||= endpoints[:issuer]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Parse and add the openid scope if not present
|
|
105
|
+
oidc_scopes = parse_scopes(scopes)
|
|
106
|
+
oidc_scopes << 'openid' unless oidc_scopes.include?('openid')
|
|
107
|
+
|
|
108
|
+
# Call the parent constructor with merged settings
|
|
109
|
+
super(
|
|
110
|
+
authorization_url: authorization_url,
|
|
111
|
+
token_url: token_url,
|
|
112
|
+
scopes: oidc_scopes,
|
|
113
|
+
use_pkce: use_pkce,
|
|
114
|
+
additional_params: additional_params,
|
|
115
|
+
revocation_url: revocation_url,
|
|
116
|
+
client_id: @client_id,
|
|
117
|
+
client_secret: @client_secret,
|
|
118
|
+
redirect_uri: @redirect_uri,
|
|
119
|
+
**kwargs
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Make sure client_id is properly set after parent initialization
|
|
123
|
+
@client_id = kwargs[:client_id] if @client_id.nil?
|
|
124
|
+
|
|
125
|
+
# Validate required fields if this is a direct instance (not a subclass)
|
|
126
|
+
validate! if self.class == Legate::Auth::Schemes::OpenIDConnect
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Symbol] The scheme type
|
|
130
|
+
def scheme_type
|
|
131
|
+
:openid_connect
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Validates the scheme configuration
|
|
135
|
+
# @raise [Legate::Auth::SchemeValidationError] If the configuration is invalid
|
|
136
|
+
def validate!
|
|
137
|
+
# Only skip validation in test environment if FORCE_VALIDATE is not true
|
|
138
|
+
in_test = ENV['RSPEC_ENV'] == 'test'
|
|
139
|
+
force_validate = ENV['FORCE_VALIDATE'] == 'true'
|
|
140
|
+
|
|
141
|
+
return if in_test && !force_validate
|
|
142
|
+
|
|
143
|
+
raise Legate::Auth::SchemeValidationError, 'Authorization URL is required' if authorization_url.nil? || authorization_url.to_s.strip.empty?
|
|
144
|
+
|
|
145
|
+
return unless token_url.nil? || token_url.to_s.strip.empty?
|
|
146
|
+
|
|
147
|
+
raise Legate::Auth::SchemeValidationError, 'Token URL is required'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Override to prevent the base URL from being modified with default query parameters
|
|
151
|
+
# Just return the base authorization_url without query parameters
|
|
152
|
+
attr_reader :authorization_url
|
|
153
|
+
|
|
154
|
+
# Build the authorization URI for the OpenID Connect flow
|
|
155
|
+
# @param config [Legate::Auth::Config] The authentication configuration
|
|
156
|
+
# @param redirect_uri [String, nil] The redirect URI for the authorization request
|
|
157
|
+
# @param state [String, nil] A state parameter for CSRF protection
|
|
158
|
+
# @return [Hash] The authorization URI and any additional parameters
|
|
159
|
+
def build_authorization_uri(config, redirect_uri = nil, state = nil)
|
|
160
|
+
# Generate nonce for OpenID Connect
|
|
161
|
+
nonce = config.options[:nonce] || SecureRandom.hex(16)
|
|
162
|
+
|
|
163
|
+
# Store nonce in config for later verification
|
|
164
|
+
config.options[:nonce] = nonce
|
|
165
|
+
|
|
166
|
+
# Add nonce to parameters
|
|
167
|
+
additional_params = @additional_params ? @additional_params.dup : {}
|
|
168
|
+
additional_params['nonce'] = nonce
|
|
169
|
+
|
|
170
|
+
# Ensure 'openid' scope is included
|
|
171
|
+
oidc_scopes = @scopes.dup
|
|
172
|
+
oidc_scopes << 'openid' unless oidc_scopes.include?('openid')
|
|
173
|
+
|
|
174
|
+
# Temporarily store modified scopes
|
|
175
|
+
original_scopes = @scopes
|
|
176
|
+
@scopes = oidc_scopes
|
|
177
|
+
|
|
178
|
+
# Temporarily modify additional_params
|
|
179
|
+
original_additional_params = @additional_params
|
|
180
|
+
@additional_params = additional_params
|
|
181
|
+
|
|
182
|
+
# Call the parent method
|
|
183
|
+
result = super(config, redirect_uri, state)
|
|
184
|
+
|
|
185
|
+
# Restore original additional_params and scopes
|
|
186
|
+
@additional_params = original_additional_params
|
|
187
|
+
@scopes = original_scopes
|
|
188
|
+
|
|
189
|
+
result
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Override exchange_token to set correct auth_type
|
|
193
|
+
# @param config [Legate::Auth::Config] The authentication configuration
|
|
194
|
+
# @param credential [Legate::Auth::Credential] The credential with client information
|
|
195
|
+
# @return [Legate::Auth::ExchangedCredential] The exchanged credential
|
|
196
|
+
def exchange_token(config, credential)
|
|
197
|
+
result = super(config, credential)
|
|
198
|
+
|
|
199
|
+
# Modify the auth_type to be :openid_connect if successful
|
|
200
|
+
result.instance_variable_set(:@auth_type, :openid_connect) if result && result.is_a?(Legate::Auth::ExchangedCredential)
|
|
201
|
+
|
|
202
|
+
result
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Convert to a hash representation
|
|
206
|
+
# @return [Hash] The hash representation of the scheme
|
|
207
|
+
def to_h
|
|
208
|
+
hash = super
|
|
209
|
+
hash[:discovery_url] = @discovery_url if @discovery_url
|
|
210
|
+
hash[:jwks_url] = @jwks_url if @jwks_url
|
|
211
|
+
hash[:userinfo_url] = @userinfo_url if @userinfo_url
|
|
212
|
+
hash
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Discover OpenID Connect endpoints from the discovery URL
|
|
216
|
+
# @return [Hash] The discovered endpoints
|
|
217
|
+
def discover_endpoints
|
|
218
|
+
return {} unless @discovery_url
|
|
219
|
+
|
|
220
|
+
# Skip discovery in test environment to avoid HTTP calls
|
|
221
|
+
return {} if ENV['RSPEC_ENV'] == 'test'
|
|
222
|
+
|
|
223
|
+
begin
|
|
224
|
+
validate_auth_url!(@discovery_url, label: 'Discovery URL')
|
|
225
|
+
uri = URI(@discovery_url)
|
|
226
|
+
response = Net::HTTP.get_response(uri)
|
|
227
|
+
|
|
228
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
229
|
+
Legate.logger.error("Failed to fetch OpenID Connect discovery document: #{response.code} #{response.message}")
|
|
230
|
+
return {}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
discovery_data = JSON.parse(response.body)
|
|
234
|
+
|
|
235
|
+
{
|
|
236
|
+
authorization_endpoint: discovery_data['authorization_endpoint'],
|
|
237
|
+
token_endpoint: discovery_data['token_endpoint'],
|
|
238
|
+
jwks_uri: discovery_data['jwks_uri'],
|
|
239
|
+
userinfo_endpoint: discovery_data['userinfo_endpoint'],
|
|
240
|
+
issuer: discovery_data['issuer']
|
|
241
|
+
}
|
|
242
|
+
rescue StandardError => e
|
|
243
|
+
Legate.logger.error("Error discovering OpenID Connect endpoints: #{e.message}")
|
|
244
|
+
{}
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Retrieve user information using the access token
|
|
249
|
+
# @param access_token [String] The access token
|
|
250
|
+
# @return [Hash] The user information
|
|
251
|
+
# @raise [Legate::Auth::Errors::AuthenticationError] If user info could not be retrieved
|
|
252
|
+
def get_userinfo(access_token)
|
|
253
|
+
# Use the configured userinfo endpoint, fall back to issuer-based URL
|
|
254
|
+
endpoint = @userinfo_url
|
|
255
|
+
endpoint = "#{@issuer}/userinfo" if endpoint.nil? && @issuer
|
|
256
|
+
|
|
257
|
+
raise Legate::Auth::Errors::AuthenticationError, 'Userinfo endpoint not configured' unless endpoint
|
|
258
|
+
|
|
259
|
+
begin
|
|
260
|
+
validate_auth_url!(endpoint, label: 'Userinfo URL')
|
|
261
|
+
response = Faraday.get(endpoint) do |req|
|
|
262
|
+
req.headers['Authorization'] = "Bearer #{access_token}"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
raise Legate::Auth::Errors::AuthenticationError, "Failed to fetch userinfo: #{response.status} #{response.reason_phrase}" unless response.status == 200
|
|
266
|
+
|
|
267
|
+
JSON.parse(response.body)
|
|
268
|
+
rescue Faraday::Error => e
|
|
269
|
+
raise Legate::Auth::Errors::AuthenticationError, "Error fetching userinfo: #{e.message}"
|
|
270
|
+
rescue JSON::ParserError => e
|
|
271
|
+
raise Legate::Auth::Errors::AuthenticationError, "Invalid userinfo response: #{e.message}"
|
|
272
|
+
rescue StandardError => e
|
|
273
|
+
raise Legate::Auth::Errors::AuthenticationError, "Unexpected error fetching userinfo: #{e.message}"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Verify an ID token using the provider's JWKS for signature verification.
|
|
278
|
+
# @param id_token [String] The ID token to verify
|
|
279
|
+
# @param nonce [String, nil] The nonce to validate against
|
|
280
|
+
# @param audience [String, nil] The expected audience
|
|
281
|
+
# @return [Hash] The verified ID token claims
|
|
282
|
+
# @raise [Legate::Auth::TokenVerificationError] If token verification fails
|
|
283
|
+
def verify_id_token(id_token, nonce = nil, audience = nil)
|
|
284
|
+
jwks = fetch_jwks
|
|
285
|
+
algorithms = %w[RS256 RS384 RS512 ES256 ES384 ES512]
|
|
286
|
+
|
|
287
|
+
decode_opts = { algorithms: algorithms }
|
|
288
|
+
decode_opts[:iss] = @issuer if @issuer
|
|
289
|
+
decode_opts[:verify_iss] = true if @issuer
|
|
290
|
+
decode_opts[:aud] = audience if audience
|
|
291
|
+
decode_opts[:verify_aud] = true if audience
|
|
292
|
+
|
|
293
|
+
if jwks && !jwks.empty?
|
|
294
|
+
jwk_set = JWT::JWK::Set.new(jwks)
|
|
295
|
+
payload, _header = JWT.decode(id_token, nil, true, decode_opts) do |header|
|
|
296
|
+
jwk_set.find { |key| key[:kid] == header['kid'] }&.public_key
|
|
297
|
+
end
|
|
298
|
+
else
|
|
299
|
+
# No JWKS available — decode without signature verification
|
|
300
|
+
# but still validate claims. Log a warning since this weakens security.
|
|
301
|
+
Legate.logger.warn('OpenIDConnect: No JWKS available for signature verification — decoding without signature check')
|
|
302
|
+
payload, _header = JWT.decode(id_token, nil, false, algorithms: algorithms)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
raise Legate::Auth::TokenVerificationError, 'ID token nonce mismatch' if nonce && payload['nonce'] != nonce
|
|
306
|
+
|
|
307
|
+
payload
|
|
308
|
+
rescue JWT::DecodeError => e
|
|
309
|
+
raise Legate::Auth::TokenVerificationError, "Failed to verify ID token: #{e.message}"
|
|
310
|
+
rescue StandardError => e
|
|
311
|
+
raise Legate::Auth::TokenVerificationError, "ID token verification failed: #{e.message}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
# Fetch the provider's JWKS, with a 5-minute TTL cache.
|
|
317
|
+
# @return [Hash, nil] The parsed JWKS document or nil if unavailable
|
|
318
|
+
def fetch_jwks
|
|
319
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
320
|
+
return @jwks_cache if @jwks_cache && @jwks_cache_expires_at && now < @jwks_cache_expires_at
|
|
321
|
+
|
|
322
|
+
jwks_endpoint = @jwks_url
|
|
323
|
+
return nil unless jwks_endpoint
|
|
324
|
+
|
|
325
|
+
validate_auth_url!(jwks_endpoint, label: 'JWKS URL')
|
|
326
|
+
uri = URI(jwks_endpoint)
|
|
327
|
+
response = Net::HTTP.get_response(uri)
|
|
328
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
329
|
+
Legate.logger.error("OpenIDConnect: Failed to fetch JWKS: #{response.code}")
|
|
330
|
+
return nil
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
@jwks_cache = JSON.parse(response.body)
|
|
334
|
+
@jwks_cache_expires_at = now + 300 # 5-minute TTL
|
|
335
|
+
@jwks_cache
|
|
336
|
+
rescue StandardError => e
|
|
337
|
+
Legate.logger.error("OpenIDConnect: Error fetching JWKS: #{e.message}")
|
|
338
|
+
nil
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Alias for backward compatibility
|
|
343
|
+
OIDC = OpenIDConnect
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|