Compare commits

...

1 Commits

Author SHA1 Message Date
openhands
c8ca73f428 Fix issue #8067: [Bug]: PAT with SSO results in 'invalid github token' 2025-04-24 17:51:55 +00:00
3 changed files with 51 additions and 2 deletions

View File

@@ -52,11 +52,16 @@ class GitHubService(BaseGitService, GitService):
if not self.token:
self.token = await self.get_latest_token()
return {
headers = {
'Authorization': f'Bearer {self.token.get_secret_value() if self.token else ""}',
'Accept': 'application/vnd.github.v3+json',
}
# Add SSO header to indicate we support SSO authentication
headers['X-GitHub-SSO'] = 'required'
return headers
def _has_token_expired(self, status_code: int) -> bool:
return status_code == 401

View File

@@ -1,3 +1,4 @@
import httpx
from pydantic import SecretStr
from openhands.integrations.github.github_service import GitHubService
@@ -25,7 +26,15 @@ async def validate_provider_token(
github_service = GitHubService(token=token, base_domain=base_domain)
await github_service.get_user()
return ProviderType.GITHUB
except Exception:
except Exception as e:
# Check if this is an SSO error
if isinstance(e, httpx.HTTPStatusError):
response = e.response
# Check for SSO-related error codes and headers
if response.status_code == 403:
# If we get a 403 with SSO headers, it's a valid token that just needs SSO authorization
if 'X-GitHub-SSO' in response.headers:
return ProviderType.GITHUB
pass
# Try GitLab next

View File

@@ -0,0 +1,35 @@
import httpx
import pytest
from pydantic import SecretStr
from openhands.integrations.provider import ProviderType
from openhands.integrations.utils import validate_provider_token
@pytest.mark.asyncio
async def test_github_sso_token_validation():
# Mock a response that would come from GitHub when using an SSO token
class MockResponse:
def __init__(self, status_code, headers):
self.status_code = status_code
self.headers = headers
class MockHTTPError(httpx.HTTPStatusError):
def __init__(self, response):
self.response = response
# Test case 1: Valid token with SSO
with pytest.raises(Exception) as exc_info:
# This will raise an exception that we'll catch and inspect
await validate_provider_token(SecretStr("test-sso-token"))
# If the exception is an HTTPStatusError with a 403 and SSO headers,
# it should still be considered a valid GitHub token
if isinstance(exc_info.value, httpx.HTTPStatusError):
response = exc_info.value.response
if response.status_code == 403 and "X-GitHub-SSO" in response.headers:
assert True # Test passed
return
# If we get here, the test failed
assert False, "SSO token validation did not handle the SSO case correctly"