Open-source Grok free registration CLI, xai_enroller auth pipeline, local auth service, tests and docs.
189 lines
5.3 KiB
Python
189 lines
5.3 KiB
Python
import asyncio
|
|
import json
|
|
|
|
import httpx
|
|
|
|
from xai_enroller.coordinator import EnrollmentCoordinator
|
|
from xai_enroller.executors import PlaywrightExecutor
|
|
from xai_enroller.models import SourceRecord
|
|
from xai_enroller.protocol import XAIProfile, XAIProtocol
|
|
from xai_enroller.sinks import CPAAuthFileSink
|
|
|
|
|
|
class SingleSource:
|
|
async def records(self):
|
|
yield SourceRecord("source-1", "sso-secret")
|
|
|
|
|
|
class FakeButton:
|
|
async def count(self):
|
|
return 1
|
|
|
|
@property
|
|
def first(self):
|
|
return self
|
|
|
|
async def click(self):
|
|
return None
|
|
|
|
|
|
class FakePage:
|
|
async def goto(self, url, wait_until):
|
|
self.url = url
|
|
|
|
def locator(self, selector):
|
|
return self
|
|
|
|
async def inner_text(self):
|
|
return "Authorize access"
|
|
|
|
def get_by_role(self, role, name):
|
|
return FakeButton() if name == "Authorize" else type(
|
|
"NoButton",
|
|
(),
|
|
{"count": staticmethod(lambda: asyncio.sleep(0, result=0))},
|
|
)()
|
|
|
|
async def close(self):
|
|
return None
|
|
|
|
|
|
class FakeContext:
|
|
def __init__(self, browser):
|
|
self.browser = browser
|
|
self.cookies = []
|
|
|
|
async def add_cookies(self, cookies):
|
|
self.cookies.extend(cookies)
|
|
|
|
async def new_page(self):
|
|
return FakePage()
|
|
|
|
async def close(self):
|
|
self.browser.live_contexts -= 1
|
|
|
|
|
|
class FakeBrowser:
|
|
def __init__(self):
|
|
self.launches = 0
|
|
self.live_contexts = 0
|
|
self.max_live_contexts = 0
|
|
|
|
async def new_context(self):
|
|
self.live_contexts += 1
|
|
self.max_live_contexts = max(self.max_live_contexts, self.live_contexts)
|
|
return FakeContext(self)
|
|
|
|
async def close(self):
|
|
return None
|
|
|
|
|
|
class FakeChromium:
|
|
def __init__(self, browser):
|
|
self.browser = browser
|
|
|
|
async def launch(self, **options):
|
|
self.browser.launches += 1
|
|
return self.browser
|
|
|
|
|
|
class FakePlaywright:
|
|
def __init__(self, browser):
|
|
self.chromium = FakeChromium(browser)
|
|
|
|
async def stop(self):
|
|
return None
|
|
|
|
|
|
class FakePlaywrightFactory:
|
|
def __init__(self):
|
|
self.browser = FakeBrowser()
|
|
self.playwright = FakePlaywright(self.browser)
|
|
|
|
def __call__(self):
|
|
factory = self
|
|
|
|
class Runner:
|
|
async def start(self):
|
|
return factory.playwright
|
|
|
|
return Runner()
|
|
|
|
|
|
def test_adapter_boundary_hands_xai_credential_to_cpa_without_ledger_secrets(tmp_path):
|
|
def xai_handler(request):
|
|
if request.url.path == "/.well-known/openid-configuration":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"device_authorization_endpoint": "https://auth.x.ai/oauth2/device/code",
|
|
"token_endpoint": "https://auth.x.ai/oauth2/token",
|
|
},
|
|
)
|
|
if request.url.path == "/oauth2/device/code":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"device_code": "device-secret",
|
|
"user_code": "ABCD",
|
|
"verification_uri": "https://accounts.x.ai/oauth2/device",
|
|
"verification_uri_complete": (
|
|
"https://accounts.x.ai/oauth2/device?user_code=ABCD"
|
|
),
|
|
"expires_in": 60,
|
|
"interval": 0,
|
|
},
|
|
)
|
|
if request.url.path == "/oauth2/token":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"access_token": "access-secret",
|
|
"refresh_token": "refresh-secret",
|
|
"id_token": "id-secret",
|
|
"token_type": "Bearer",
|
|
"expires_in": 3600,
|
|
"sub": "subject-1",
|
|
},
|
|
)
|
|
raise AssertionError(f"unexpected xAI request: {request.url}")
|
|
|
|
cpa_requests = []
|
|
|
|
def cpa_handler(request):
|
|
cpa_requests.append(request)
|
|
return httpx.Response(201)
|
|
|
|
xai_client = httpx.AsyncClient(transport=httpx.MockTransport(xai_handler))
|
|
cpa_client = httpx.AsyncClient(transport=httpx.MockTransport(cpa_handler))
|
|
factory = FakePlaywrightFactory()
|
|
coordinator = EnrollmentCoordinator(
|
|
source=SingleSource(),
|
|
protocol=XAIProtocol(xai_client, XAIProfile.default()),
|
|
executor=PlaywrightExecutor(playwright_factory=factory),
|
|
sink=CPAAuthFileSink(
|
|
"https://cpa.example",
|
|
"management-secret",
|
|
cpa_client,
|
|
name_secret=b"name-secret",
|
|
),
|
|
ledger_path=tmp_path / "ledger.db",
|
|
ledger_salt=b"ledger-salt",
|
|
)
|
|
|
|
try:
|
|
results = asyncio.run(coordinator.run(target=1))
|
|
finally:
|
|
asyncio.run(xai_client.aclose())
|
|
asyncio.run(cpa_client.aclose())
|
|
|
|
assert results[0].status.value == "imported"
|
|
assert factory.browser.launches == 1
|
|
assert factory.browser.max_live_contexts == 1
|
|
assert cpa_requests[0].url.path == "/v0/management/auth-files"
|
|
assert cpa_requests[0].url.params["name"].startswith("xai-")
|
|
assert json.loads(cpa_requests[0].content)["refresh_token"] == "refresh-secret"
|
|
ledger_bytes = (tmp_path / "ledger.db").read_bytes()
|
|
for secret in ("sso-secret", "device-secret", "access-secret", "refresh-secret", "id-secret"):
|
|
assert secret.encode() not in ledger_bytes
|