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,123 @@
|
||||
"""Audience-bound machine principal mapping and bounded revocation cache."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
from .errors import InvalidToken, RevokedMachinePrincipal, UnknownMachinePrincipal
|
||||
from .models import AuthenticatedPrincipal, ManagementRole, PrincipalKind, ValidatedClaims
|
||||
from .provider import OIDCProviderClient
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MachineClientBinding:
|
||||
subject: str
|
||||
client_id: UUID
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class PrincipalMapper:
|
||||
"""Maps trusted identity claims to roles, never to project/environment bindings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
role_group_map: Mapping[str, ManagementRole] | None = None,
|
||||
machine_subjects: Mapping[str, MachineClientBinding] | None = None,
|
||||
) -> None:
|
||||
self.role_group_map = dict(role_group_map or {})
|
||||
self.machine_subjects = dict(machine_subjects or {})
|
||||
|
||||
def map_claims(self, claims: ValidatedClaims) -> AuthenticatedPrincipal:
|
||||
machine = self.machine_subjects.get(claims.subject)
|
||||
if machine is not None:
|
||||
if not machine.enabled:
|
||||
raise UnknownMachinePrincipal()
|
||||
return AuthenticatedPrincipal(
|
||||
subject=claims.subject,
|
||||
issuer=claims.issuer,
|
||||
audience=claims.audience,
|
||||
kind=PrincipalKind.MACHINE_CLIENT,
|
||||
client_id=machine.client_id,
|
||||
machine_subject=claims.subject,
|
||||
expires_at=claims.expires_at,
|
||||
issued_at=claims.issued_at,
|
||||
)
|
||||
roles = frozenset(
|
||||
role
|
||||
for group in claims.groups
|
||||
for role in [self.role_group_map.get(group)]
|
||||
if role is not None
|
||||
)
|
||||
return AuthenticatedPrincipal(
|
||||
subject=claims.subject,
|
||||
issuer=claims.issuer,
|
||||
audience=claims.audience,
|
||||
kind=PrincipalKind.MANAGEMENT_USER,
|
||||
groups=claims.groups,
|
||||
roles=roles,
|
||||
expires_at=claims.expires_at,
|
||||
issued_at=claims.issued_at,
|
||||
)
|
||||
|
||||
|
||||
class RevocationCache:
|
||||
"""Caches revocation truth for at most 60 seconds; invalidation is immediate."""
|
||||
|
||||
def __init__(self, lookup: Callable[[UUID], bool], *, ttl_seconds: int = 60) -> None:
|
||||
self.lookup = lookup
|
||||
self.ttl_seconds = max(1, min(ttl_seconds, 60))
|
||||
self._values: dict[UUID, tuple[bool, float]] = {}
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def is_revoked(self, client_id: UUID) -> bool:
|
||||
now = time.monotonic()
|
||||
with self._lock:
|
||||
cached = self._values.get(client_id)
|
||||
if cached is not None and cached[1] > now:
|
||||
return cached[0]
|
||||
value = bool(self.lookup(client_id))
|
||||
with self._lock:
|
||||
self._values[client_id] = (value, now + self.ttl_seconds)
|
||||
return value
|
||||
|
||||
def invalidate(self, client_id: UUID) -> None:
|
||||
with self._lock:
|
||||
self._values.pop(client_id, None)
|
||||
|
||||
|
||||
class MachinePrincipalAuthenticator:
|
||||
"""Validates a Hub-audience bearer and enforces client registration/revocation."""
|
||||
|
||||
_bearer = re.compile(r"^Bearer ([A-Za-z0-9._~+/=-]{1,65536})$")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: OIDCProviderClient,
|
||||
mapper: PrincipalMapper,
|
||||
revocations: RevocationCache,
|
||||
) -> None:
|
||||
self.provider = provider
|
||||
self.mapper = mapper
|
||||
self.revocations = revocations
|
||||
|
||||
def authenticate(
|
||||
self,
|
||||
authorization_header: str | None,
|
||||
) -> AuthenticatedPrincipal:
|
||||
if not authorization_header:
|
||||
raise InvalidToken()
|
||||
match = self._bearer.fullmatch(authorization_header.strip())
|
||||
if match is None:
|
||||
raise InvalidToken()
|
||||
principal = self.mapper.map_claims(self.provider.validate_token(match.group(1)))
|
||||
if principal.kind is not PrincipalKind.MACHINE_CLIENT or principal.client_id is None:
|
||||
raise InvalidToken()
|
||||
if self.revocations.is_revoked(principal.client_id):
|
||||
raise RevokedMachinePrincipal()
|
||||
return principal
|
||||
Reference in new issue
Block a user