Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,214 @@
|
||||
"""Deterministic offline OIDC provider used by integration and E2E tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any, cast
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import jwt
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeKey:
|
||||
kid: str
|
||||
private_key: Any
|
||||
|
||||
|
||||
class FakeOIDCProvider:
|
||||
"""A local MockTransport provider; it never uses real credentials or network I/O."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
issuer: str = "https://fake.oidc.test",
|
||||
audience: str = "itworx-mcp-hub",
|
||||
user_subject: str = "user-management-1",
|
||||
user_groups: tuple[str, ...] = ("hub-viewer",),
|
||||
persona_groups: dict[str, tuple[str, ...]] | None = None,
|
||||
token_ttl_seconds: int = 300,
|
||||
) -> None:
|
||||
self.issuer = issuer
|
||||
self._base_url = issuer.rstrip("/")
|
||||
self.audience = audience
|
||||
self.user_subject = user_subject
|
||||
self.user_groups = user_groups
|
||||
self._token_ttl_seconds = token_ttl_seconds
|
||||
# Test-only: lets a real browser flow (GET /authorize, unlike
|
||||
# authorize() which is called directly from Python) select which
|
||||
# group claim to issue via a "persona" query param, so E2E tests can
|
||||
# exercise multiple roles against one running fake-provider process
|
||||
# without touching the real BFF login route to add a hint parameter.
|
||||
self._persona_groups = persona_groups or {}
|
||||
self._key = self._new_key("fake-key-1")
|
||||
self._codes: dict[str, tuple[str, str, tuple[str, ...]]] = {}
|
||||
|
||||
@staticmethod
|
||||
def _new_key(kid: str) -> _FakeKey:
|
||||
return _FakeKey(
|
||||
kid=kid,
|
||||
private_key=rsa.generate_private_key(public_exponent=65537, key_size=2048),
|
||||
)
|
||||
|
||||
def rotate_keys(self, kid: str = "fake-key-rotated") -> None:
|
||||
self._key = self._new_key(kid)
|
||||
|
||||
def transport(self) -> httpx.MockTransport:
|
||||
return httpx.MockTransport(self._handle)
|
||||
|
||||
def asgi_app(self) -> Any:
|
||||
"""A real ASGI app serving the same endpoints as transport(), for
|
||||
E2E harnesses where an actual browser must navigate to /authorize
|
||||
(a MockTransport only intercepts server-to-server httpx calls)."""
|
||||
from starlette.applications import Starlette
|
||||
from starlette.requests import Request as StarletteRequest
|
||||
from starlette.responses import Response as StarletteResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
async def endpoint(request: StarletteRequest) -> StarletteResponse:
|
||||
body = await request.body()
|
||||
httpx_request = httpx.Request(
|
||||
request.method,
|
||||
str(request.url),
|
||||
headers=list(request.headers.items()),
|
||||
content=body,
|
||||
)
|
||||
httpx_response = self._handle(httpx_request)
|
||||
headers = {
|
||||
key: value
|
||||
for key, value in httpx_response.headers.items()
|
||||
if key.lower() not in {"content-length", "transfer-encoding"}
|
||||
}
|
||||
return StarletteResponse(
|
||||
content=httpx_response.content,
|
||||
status_code=httpx_response.status_code,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
return Starlette(
|
||||
routes=[Route("/{path:path}", endpoint, methods=["GET", "POST"])],
|
||||
)
|
||||
|
||||
def _issue_code(self, challenge: str, nonce: str, groups: tuple[str, ...]) -> str:
|
||||
code = f"fake-code-{uuid4().hex}"
|
||||
self._codes[code] = (challenge, nonce, groups)
|
||||
return code
|
||||
|
||||
def authorize(self, authorization_url: str, *, now: datetime | None = None) -> str:
|
||||
query = parse_qs(urlparse(authorization_url).query)
|
||||
state = query.get("state", [""])[0]
|
||||
challenge = query.get("code_challenge", [""])[0]
|
||||
if not state or not challenge:
|
||||
raise ValueError("invalid fake authorization request")
|
||||
return self._issue_code(challenge, query.get("nonce", [""])[0], self.user_groups)
|
||||
|
||||
def issue_machine_token(
|
||||
self,
|
||||
subject: str,
|
||||
*,
|
||||
now: datetime | None = None,
|
||||
issuer: str | None = None,
|
||||
audience: str | None = None,
|
||||
) -> str:
|
||||
reference = now or datetime.now(UTC)
|
||||
return self._encode(
|
||||
{
|
||||
"sub": subject,
|
||||
"client_id": subject,
|
||||
"iss": issuer or self.issuer,
|
||||
"aud": audience or self.audience,
|
||||
"iat": int(reference.timestamp()),
|
||||
"exp": int((reference + timedelta(minutes=5)).timestamp()),
|
||||
}
|
||||
)
|
||||
|
||||
def _encode(self, claims: dict[str, Any]) -> str:
|
||||
return jwt.encode(
|
||||
claims, self._key.private_key, algorithm="RS256", headers={"kid": self._key.kid}
|
||||
)
|
||||
|
||||
def _public_jwk(self) -> dict[str, Any]:
|
||||
raw = cast(
|
||||
dict[str, Any],
|
||||
json.loads(jwt.algorithms.RSAAlgorithm.to_jwk(self._key.private_key.public_key())),
|
||||
)
|
||||
return raw | {
|
||||
"kid": self._key.kid,
|
||||
"use": "sig",
|
||||
"alg": "RS256",
|
||||
}
|
||||
|
||||
def _handle(self, request: httpx.Request) -> httpx.Response:
|
||||
path = urlparse(str(request.url)).path
|
||||
base_path = urlparse(self._base_url).path.rstrip("/")
|
||||
if request.method == "GET" and path == f"{base_path}/.well-known/openid-configuration":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"issuer": self.issuer,
|
||||
"authorization_endpoint": f"{self._base_url}/authorize",
|
||||
"token_endpoint": f"{self._base_url}/token",
|
||||
"jwks_uri": f"{self._base_url}/jwks",
|
||||
"id_token_signing_alg_values_supported": ["RS256"],
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
if request.method == "GET" and path == f"{base_path}/jwks":
|
||||
return httpx.Response(200, json={"keys": [self._public_jwk()]}, request=request)
|
||||
if request.method == "GET" and path == f"{base_path}/authorize":
|
||||
query = parse_qs(urlparse(str(request.url)).query)
|
||||
redirect_uri = query.get("redirect_uri", [""])[0]
|
||||
state = query.get("state", [""])[0]
|
||||
challenge = query.get("code_challenge", [""])[0]
|
||||
nonce = query.get("nonce", [""])[0]
|
||||
persona = query.get("persona", [""])[0]
|
||||
if not redirect_uri or not state or not challenge:
|
||||
return httpx.Response(
|
||||
400, text="invalid fake authorization request", request=request
|
||||
)
|
||||
groups = self._persona_groups.get(persona, self.user_groups)
|
||||
code = self._issue_code(challenge, nonce, groups)
|
||||
separator = "&" if "?" in redirect_uri else "?"
|
||||
location = f"{redirect_uri}{separator}code={code}&state={state}"
|
||||
return httpx.Response(302, headers={"location": location}, request=request)
|
||||
if request.method == "POST" and path == f"{base_path}/token":
|
||||
form = parse_qs(request.content.decode("utf-8"))
|
||||
code = form.get("code", [""])[0]
|
||||
verifier = form.get("code_verifier", [""])[0]
|
||||
expected = self._codes.pop(code, None)
|
||||
if expected is None:
|
||||
return httpx.Response(400, json={"error": "invalid_grant"}, request=request)
|
||||
challenge = jwt.utils.base64url_encode(
|
||||
hashlib.sha256(verifier.encode()).digest()
|
||||
).decode()
|
||||
if challenge != expected[0]:
|
||||
return httpx.Response(400, json={"error": "invalid_grant"}, request=request)
|
||||
now = datetime.now(UTC)
|
||||
id_token = self._encode(
|
||||
{
|
||||
"sub": self.user_subject,
|
||||
"iss": self.issuer,
|
||||
"aud": self.audience,
|
||||
"iat": int(now.timestamp()),
|
||||
"exp": int((now + timedelta(seconds=self._token_ttl_seconds)).timestamp()),
|
||||
"nonce": expected[1],
|
||||
"groups": list(expected[2]),
|
||||
}
|
||||
)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"access_token": id_token,
|
||||
"id_token": id_token,
|
||||
"token_type": "Bearer",
|
||||
"expires_in": self._token_ttl_seconds,
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
return httpx.Response(404, request=request)
|
||||
Reference in new issue
Block a user