Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 0 additions & 152 deletions examples/logging_example.py

This file was deleted.

119 changes: 0 additions & 119 deletions examples/middleware-override-example.yaml

This file was deleted.

29 changes: 25 additions & 4 deletions src/agent/security/authenticators/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,24 +190,38 @@ async def _validate_via_introspection(self, token: str, request_info: dict[str,
InvalidCredentialsException: If token is invalid or introspection fails
"""
try:
async with AsyncOAuth2Client(client_id=self.client_id, client_secret=self.client_secret) as client:
# Call introspection endpoint
# GitHub expects JSON payload with "access_token" field, not form data
# Use basic auth with httpx directly instead of AsyncOAuth2Client
import httpx
import base64

auth = base64.b64encode(f"{self.client_id}:{self.client_secret}".encode()).decode()

async with httpx.AsyncClient() as client:
response = await client.post(
self.introspection_endpoint, data={"token": token, "token_type_hint": "access_token"}
self.introspection_endpoint,
headers={
"Authorization": f"Basic {auth}",
"Content-Type": "application/json"
},
json={"access_token": token}
)
response.raise_for_status()

introspection_data = response.json()

# Check if token is active
if not introspection_data.get("active", False):
# GitHub doesn't return 'active' field, but returns 200 status for valid tokens
# For standard OAuth2 introspection, check 'active' field
if "active" in introspection_data and not introspection_data.get("active", False):
raise InvalidCredentialsException("Unauthorized")

# Extract user information
user_id = (
introspection_data.get("sub")
or introspection_data.get("user_id")
or introspection_data.get("username")
or (introspection_data.get("user", {}).get("login") if introspection_data.get("user") else None)
or "unknown"
)

Expand All @@ -219,6 +233,13 @@ async def _validate_via_introspection(self, token: str, request_info: dict[str,
scopes = set(scope_value.split())
elif isinstance(scope_value, list):
scopes = set(scope_value)
elif "scopes" in introspection_data:
# GitHub returns scopes as "scopes" field
scope_value = introspection_data["scopes"]
if isinstance(scope_value, list):
scopes = set(scope_value)
elif isinstance(scope_value, str):
scopes = set(scope_value.split())

return AuthenticationResult(
success=True,
Expand Down
2 changes: 1 addition & 1 deletion src/agent/security/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def _validate_jwt_config(config: dict[str, Any]) -> None:
@staticmethod
def _validate_oauth2_config(config: dict[str, Any]) -> None:
"""Validate OAuth2 configuration."""
oauth2_config = config.get("oauth2", {})
oauth2_config = config

if not isinstance(oauth2_config, dict):
raise SecurityConfigurationException("OAuth2 config must be a dictionary")
Expand Down
Loading