|
14 | 14 |
|
15 | 15 | """A FastAPI client for interacting with ADK remote agents and handling GCP authentication.""" |
16 | 16 |
|
| 17 | +import asyncio |
17 | 18 | import base64 |
18 | 19 | import importlib |
19 | 20 | import json |
|
32 | 33 | from fastapi.staticfiles import StaticFiles |
33 | 34 | from google.adk.auth import AuthConfig |
34 | 35 | from google.adk.runners import InMemoryRunner |
| 36 | +from google.api_core.client_options import ClientOptions |
35 | 37 | import google.auth |
36 | 38 | import google.auth.transport.requests |
| 39 | +from google.cloud.agentidentitycredentials_v1 import AuthProviderCredentialsServiceClient |
| 40 | +from google.cloud.agentidentitycredentials_v1 import FinalizeCredentialsRequest |
37 | 41 | from google.genai import types |
38 | | -import httpx |
39 | 42 | from pydantic import BaseModel |
40 | 43 | import uvicorn |
41 | 44 | import vertexai |
42 | 45 |
|
43 | | -TARGET_HOST = ( |
44 | | - os.environ.get("IAM_CONNECTOR_CREDENTIALS_TARGET_HOST") |
45 | | - or "iamconnectorcredentials.googleapis.com" |
46 | | -) |
47 | | - |
48 | 46 | # Add agent project directory to path to allow importing local agents |
49 | 47 | AGENT_PROJECT_DIR = os.environ.get("AGENT_PROJECT_DIR") or os.path.dirname( |
50 | 48 | os.path.dirname(os.path.abspath(__file__)) |
@@ -375,6 +373,10 @@ async def validate_user_id(request: Request): |
375 | 373 | auth_provider_name = request.query_params.get( |
376 | 374 | "connector_name" |
377 | 375 | ) or request.query_params.get("auth_provider_name") |
| 376 | + if auth_provider_name: |
| 377 | + auth_provider_name = auth_provider_name.replace( |
| 378 | + "/connectors/", "/authProviders/" |
| 379 | + ) |
378 | 380 |
|
379 | 381 | print( |
380 | 382 | f"Callback received: user_id_validation_state={user_id_validation_state}," |
@@ -408,51 +410,47 @@ async def validate_user_id(request: Request): |
408 | 410 | } |
409 | 411 |
|
410 | 412 | try: |
411 | | - url = ( |
412 | | - f"https://{TARGET_HOST}/v1alpha/{auth_provider_name}" |
413 | | - "/credentials:finalize" |
| 413 | + state_bytes = base64.urlsafe_b64decode( |
| 414 | + user_id_validation_state + "=" * (-len(user_id_validation_state) % 4) |
414 | 415 | ) |
415 | | - headers = { |
416 | | - "Content-Type": "application/json", |
417 | | - } |
418 | | - payload = { |
419 | | - "userId": user_id, |
420 | | - "userIdValidationState": user_id_validation_state, |
421 | | - "consentNonce": consent_nonce, |
422 | | - } |
423 | 416 |
|
424 | | - print(f"Calling FinalizeCredentials via HTTP POST to: {url}") |
425 | | - print(f"Headers: {headers}") |
426 | | - print(f"Payload: {payload}") |
427 | | - |
428 | | - async with httpx.AsyncClient() as client: |
429 | | - response = await client.post(url, json=payload, headers=headers) |
430 | | - |
431 | | - print(f"HTTP Response Status: {response.status_code}") |
432 | | - print(f"HTTP Response Body: {response.text}") |
433 | | - |
434 | | - if response.status_code == 200: |
435 | | - # Return a simple HTML page to indicate OAuth success |
436 | | - html_content = """ |
437 | | - <!DOCTYPE html> |
438 | | - <html> |
439 | | - <head> |
440 | | - <title>Authorization Successful</title> |
441 | | - </head> |
442 | | - <body> |
443 | | - <p>Authorization successful! You can close this window.</p> |
444 | | - </body> |
445 | | - </html> |
446 | | - """ |
447 | | - return HTMLResponse(content=html_content) |
448 | | - else: |
449 | | - return { |
450 | | - "status": "error", |
451 | | - "message": f"HTTP Error {response.status_code}: {response.text}", |
452 | | - } |
| 417 | + client_options = None |
| 418 | + if host := os.environ.get("AGENT_IDENTITY_CREDENTIALS_TARGET_HOST"): |
| 419 | + client_options = ClientOptions(api_endpoint=host) |
| 420 | + |
| 421 | + client = AuthProviderCredentialsServiceClient( |
| 422 | + client_options=client_options, transport="rest" |
| 423 | + ) |
| 424 | + |
| 425 | + finalize_request = FinalizeCredentialsRequest( |
| 426 | + auth_provider=auth_provider_name, |
| 427 | + user_id=user_id, |
| 428 | + user_id_validation_state=state_bytes, |
| 429 | + consent_nonce=consent_nonce, |
| 430 | + ) |
| 431 | + |
| 432 | + print( |
| 433 | + "Calling FinalizeCredentials via AuthProviderCredentialsServiceClient" |
| 434 | + f" for auth_provider: {auth_provider_name}" |
| 435 | + ) |
| 436 | + await asyncio.to_thread(client.finalize_credentials, finalize_request) |
| 437 | + |
| 438 | + # Return a simple HTML page to indicate OAuth success |
| 439 | + html_content = """ |
| 440 | + <!DOCTYPE html> |
| 441 | + <html> |
| 442 | + <head> |
| 443 | + <title>Authorization Successful</title> |
| 444 | + </head> |
| 445 | + <body> |
| 446 | + <p>Authorization successful! You can close this window.</p> |
| 447 | + </body> |
| 448 | + </html> |
| 449 | + """ |
| 450 | + return HTMLResponse(content=html_content) |
453 | 451 |
|
454 | 452 | except Exception as e: |
455 | | - print(f"Error calling FinalizeCredentials via HTTP: {e}") |
| 453 | + print(f"Error finalizing credentials: {e}") |
456 | 454 | return { |
457 | 455 | "status": "error", |
458 | 456 | "message": f"Failed to finalize credentials: {str(e)}", |
|
0 commit comments