from __future__ import annotations import json import os import secrets import stat import time from collections.abc import Iterator from contextlib import contextmanager from pathlib import Path from threading import Lock from typing import Any from sqlmodel import Session, select from ..connectors.authentik import AuthentikConnector from ..core.config import settings from ..db.models import Policy from ..schemas.forge_oidc import ( FORGE_OIDC_ACTION, FORGE_OIDC_BINDING_SECRET_REF, FORGE_OIDC_CLIENT_ID, FORGE_OIDC_SECRET_REF, FORGE_OIDC_TARGET, ForgeOidcBindingV1, ForgeOidcProvisionResultV1, ForgeOidcScopeMappingIdsV1, ) from .audit_logger import log_event from .policy_engine import evaluate_policy from .secret_store import get_secret, has_secret, secret_storage_status, set_secret _SECRET_LOCK = Lock() _PROVISION_LOCK_FILENAME = ".forge-oidc-provision.lock" _PROVISION_LOCK_TIMEOUT_SECONDS = 30.0 _PROVISION_LOCK_POLL_INTERVAL_SECONDS = 0.05 _FORGE_OIDC_LOCK_ROOT = Path("/run/itworx-appops/forge-oidc-provision") class _ProvisionLockUnavailable(RuntimeError): pass def _is_reparse_point(stat_result: os.stat_result) -> bool: attributes = getattr(stat_result, "st_file_attributes", 0) reparse_flag = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400) return bool(attributes & reparse_flag) def _windows_current_user_sid() -> bytes: """Return the effective Windows token user SID without shelling out.""" import ctypes from ctypes import wintypes advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) advapi32.OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)] advapi32.OpenProcessToken.restype = wintypes.BOOL advapi32.GetTokenInformation.argtypes = [ wintypes.HANDLE, wintypes.DWORD, ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), ] advapi32.GetTokenInformation.restype = wintypes.BOOL advapi32.GetLengthSid.argtypes = [ctypes.c_void_p] advapi32.GetLengthSid.restype = wintypes.DWORD kernel32.GetCurrentProcess.argtypes = [] kernel32.GetCurrentProcess.restype = wintypes.HANDLE kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL token = wintypes.HANDLE() if not advapi32.OpenProcessToken( kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token) ): raise OSError(ctypes.get_last_error(), "OpenProcessToken failed") try: required = wintypes.DWORD() advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required)) if not required.value: raise OSError(ctypes.get_last_error(), "GetTokenInformation failed") token_user = ctypes.create_string_buffer(required.value) if not advapi32.GetTokenInformation( token, 1, token_user, required.value, ctypes.byref(required) ): raise OSError(ctypes.get_last_error(), "GetTokenInformation failed") sid = ctypes.cast(token_user, ctypes.POINTER(ctypes.c_void_p)).contents.value sid_length = advapi32.GetLengthSid(ctypes.c_void_p(sid)) if not sid or not sid_length: raise OSError("invalid current-user SID") return ctypes.string_at(sid, sid_length) finally: kernel32.CloseHandle(token) def _windows_private_path(path: Path, *, require_current_owner: bool) -> None: """Reject Windows paths whose owner/DACL lets an untrusted principal mutate them.""" import ctypes from ctypes import wintypes advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) advapi32.GetNamedSecurityInfoW.argtypes = [ wintypes.LPWSTR, wintypes.DWORD, wintypes.DWORD, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_void_p), ] advapi32.GetNamedSecurityInfoW.restype = wintypes.DWORD advapi32.GetLengthSid.argtypes = [ctypes.c_void_p] advapi32.GetLengthSid.restype = wintypes.DWORD advapi32.ConvertStringSidToSidW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(ctypes.c_void_p)] advapi32.ConvertStringSidToSidW.restype = wintypes.BOOL advapi32.GetAclInformation.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, wintypes.DWORD, wintypes.DWORD, ] advapi32.GetAclInformation.restype = wintypes.BOOL advapi32.GetAce.argtypes = [ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(ctypes.c_void_p)] advapi32.GetAce.restype = wintypes.BOOL kernel32.LocalFree.argtypes = [ctypes.c_void_p] kernel32.LocalFree.restype = ctypes.c_void_p owner = ctypes.c_void_p() dacl = ctypes.c_void_p() descriptor = ctypes.c_void_p() # SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION result = advapi32.GetNamedSecurityInfoW( str(path), 1, 0x1 | 0x4, ctypes.byref(owner), None, ctypes.byref(dacl), None, ctypes.byref(descriptor), ) if result != 0 or not owner.value or not dacl.value: raise OSError(int(result or ctypes.get_last_error()), "security descriptor unavailable") try: current_sid = _windows_current_user_sid() sid_size = advapi32.GetLengthSid(owner) owner_sid = ctypes.string_at(owner.value, sid_size) if require_current_owner and owner_sid != current_sid: raise OSError("path owner is not the current user") # SYSTEM and local Administrators are trusted operating-system principals. trusted_sids = {current_sid} for sid_text in ( "S-1-5-18", # LocalSystem "S-1-5-32-544", # Builtin Administrators "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464", # TrustedInstaller ): sid_buffer = ctypes.c_void_p() if not advapi32.ConvertStringSidToSidW(sid_text, ctypes.byref(sid_buffer)): raise OSError(ctypes.get_last_error(), "trusted SID unavailable") try: sid_size = advapi32.GetLengthSid(sid_buffer) trusted_sids.add(ctypes.string_at(sid_buffer.value, sid_size)) finally: kernel32.LocalFree(sid_buffer) # Reject any explicit or inherited allow ACE outside the trusted set that # can create/replace/delete this path or write to a lock file. class _AclSizeInformation(ctypes.Structure): _fields_ = [ ("AceCount", wintypes.DWORD), ("AclBytesInUse", wintypes.DWORD), ("AclBytesFree", wintypes.DWORD), ] acl_info = _AclSizeInformation() if not advapi32.GetAclInformation( dacl, ctypes.byref(acl_info), ctypes.sizeof(acl_info), 2, ): raise OSError(ctypes.get_last_error(), "ACL unavailable") write_rights = ( 0x00000002 # FILE_WRITE_DATA / FILE_ADD_FILE | 0x00000004 # FILE_APPEND_DATA / FILE_ADD_SUBDIRECTORY | 0x00000010 # FILE_WRITE_EA | 0x00000040 # FILE_DELETE_CHILD | 0x00000100 # FILE_WRITE_ATTRIBUTES | 0x00010000 # DELETE | 0x00040000 # WRITE_DAC | 0x00080000 # WRITE_OWNER | 0x10000000 # GENERIC_ALL | 0x40000000 # GENERIC_WRITE ) for index in range(acl_info.AceCount): ace = ctypes.c_void_p() if not advapi32.GetAce(dacl, index, ctypes.byref(ace)): raise OSError(ctypes.get_last_error(), "ACL entry unavailable") header = ctypes.cast(ace, ctypes.POINTER(ctypes.c_ubyte * 2)).contents ace_type = int(header[0]) if ace_type != 0: # ACCESS_ALLOWED_ACE if ace_type not in {1, 2, 3, 6, 7, 8, 10, 12, 13, 14}: # Deny and audit entries do not grant rights. Unknown ACEs # could grant access and therefore make this path untrusted. raise OSError("unsupported ACL entry") continue mask = ctypes.cast( ctypes.c_void_p(ace.value + 4), ctypes.POINTER(wintypes.DWORD) ).contents.value sid_address = ace.value + 8 sid_size = advapi32.GetLengthSid(ctypes.c_void_p(sid_address)) ace_sid = ctypes.string_at(sid_address, sid_size) if mask & write_rights and ace_sid not in trusted_sids: raise OSError("path ACL grants write access to an untrusted principal") finally: if descriptor.value: kernel32.LocalFree(descriptor) def _lock_root() -> Path: # Linux containers share this private runtime path among their worker # processes. Windows keeps the equivalent lock beside its protected data. return Path(settings.data_dir) if os.name == "nt" else _FORGE_OIDC_LOCK_ROOT def _validate_lock_data_dir(data_dir: Path) -> None: if not data_dir.is_absolute(): raise OSError("lock directory must be absolute") # Inspect each existing path component without following symlinks/reparse points. components = [Path(data_dir.anchor)] for part in data_dir.parts[1:]: components.append(components[-1] / part) private_components = {data_dir, data_dir.parent} for index, component in enumerate(components): try: component_stat = os.lstat(component) except FileNotFoundError: if os.name == "nt" or component not in private_components: raise OSError("lock directory path is missing") from None try: os.mkdir(component, 0o700) except FileExistsError: # Another worker may have created the same fixed private child; # validate its metadata below before using it. pass component_stat = os.lstat(component) if _is_reparse_point(component_stat) or not stat.S_ISDIR(component_stat.st_mode): raise OSError("lock directory contains an unsafe path component") is_private_component = component in private_components if os.name == "nt": _windows_private_path(component, require_current_owner=component == data_dir) continue effective_uid = os.geteuid() if component_stat.st_uid not in {effective_uid, 0}: raise OSError("lock directory path has an untrusted owner") mode = stat.S_IMODE(component_stat.st_mode) if is_private_component: if component_stat.st_uid != effective_uid or mode != 0o700: raise OSError("lock directory is not private to its owner") elif mode & 0o022 and not ( component_stat.st_uid == 0 and mode & stat.S_ISVTX and mode & 0o002 ): # Root-owned sticky directories such as /tmp safely contain private, # owner-checked children; other writable ancestors are not trusted. raise OSError("lock directory has a writable untrusted ancestor") def _validate_lock_file(lock_fd: int, lock_path: Path) -> None: opened = os.fstat(lock_fd) named = os.lstat(lock_path) if ( _is_reparse_point(named) or not stat.S_ISREG(opened.st_mode) or not stat.S_ISREG(named.st_mode) or not os.path.samestat(opened, named) or opened.st_nlink != 1 or opened.st_size > 1 ): raise OSError("lock file is not a private regular file") if os.name == "nt": _windows_private_path(lock_path, require_current_owner=True) return if opened.st_uid != os.geteuid() or stat.S_IMODE(opened.st_mode) & 0o077: raise OSError("lock file is not private to its owner") @contextmanager def _forge_oidc_process_lock() -> Iterator[None]: """Serialize provisioning across workers in one AppOps runtime.""" deadline = time.monotonic() + _PROVISION_LOCK_TIMEOUT_SECONDS thread_locked = _SECRET_LOCK.acquire( timeout=max(0.0, deadline - time.monotonic()) ) if not thread_locked: raise _ProvisionLockUnavailable from None lock_fd: int | None = None locked = False try: try: lock_root = _lock_root() _validate_lock_data_dir(lock_root) lock_path = lock_root / _PROVISION_LOCK_FILENAME flags = os.O_RDWR if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW if hasattr(os, "O_CLOEXEC"): flags |= os.O_CLOEXEC try: lock_fd = os.open(str(lock_path), flags | os.O_CREAT | os.O_EXCL, 0o600) except FileExistsError: lock_fd = os.open(str(lock_path), flags) _validate_lock_file(lock_fd, lock_path) if os.name == "nt": import msvcrt if os.fstat(lock_fd).st_size == 0: os.write(lock_fd, b"\0") _validate_lock_file(lock_fd, lock_path) while True: os.lseek(lock_fd, 0, os.SEEK_SET) try: msvcrt.locking(lock_fd, msvcrt.LK_NBLCK, 1) break except OSError: if time.monotonic() >= deadline: raise TimeoutError("provision_lock_timeout") from None time.sleep( min( _PROVISION_LOCK_POLL_INTERVAL_SECONDS, max(0.0, deadline - time.monotonic()), ) ) else: import fcntl while True: try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) break except BlockingIOError: if time.monotonic() >= deadline: raise TimeoutError("provision_lock_timeout") from None time.sleep( min( _PROVISION_LOCK_POLL_INTERVAL_SECONDS, max(0.0, deadline - time.monotonic()), ) ) locked = True except Exception: # noqa: BLE001 - all lock setup errors fail closed. # Keep platform-specific path/ACL errors internal and fail closed # with the stable service error emitted by the caller. if lock_fd is not None: try: os.close(lock_fd) except OSError: pass lock_fd = None raise _ProvisionLockUnavailable from None yield finally: if lock_fd is not None: if locked: try: if os.name == "nt": import msvcrt os.lseek(lock_fd, 0, os.SEEK_SET) msvcrt.locking(lock_fd, msvcrt.LK_UNLCK, 1) else: import fcntl fcntl.flock(lock_fd, fcntl.LOCK_UN) except OSError: # Closing the descriptor also releases its OS lock. pass try: os.close(lock_fd) except OSError: pass if thread_locked: _SECRET_LOCK.release() def _new_client_secret() -> str: return secrets.token_urlsafe(48) def _result( *, status: str, message: str, success: bool = False, secret_created: bool = False, application_id: str | None = None, provider_id: str | None = None, ) -> dict[str, Any]: return ForgeOidcProvisionResultV1( success=success, status=status, message=message, secret_created=secret_created, application_id=application_id, provider_id=provider_id, ).model_dump(mode="json", by_alias=True) def _exact_policy_allowed(session: Session) -> bool: rows = session.exec( select(Policy).where( (Policy.action == FORGE_OIDC_ACTION) & (Policy.target == FORGE_OIDC_TARGET) ) ).all() if len(rows) != 1: return False policy = rows[0] if policy.manual != "allowed" or policy.voice != "blocked" or policy.risk != "critical": return False decision = evaluate_policy( session, action=FORGE_OIDC_ACTION, target=FORGE_OIDC_TARGET, source="manual", ) return ( decision.get("decision") == "allowed" and decision.get("scope_decision") == "allowed" and decision.get("policy_id") == policy.id ) def _safe_connector_id(value: Any) -> str | None: candidate = str(value or "") if not candidate or len(candidate) > 128: return None if not all(character.isalnum() or character in "._:-" for character in candidate): return None return candidate def _audit( session: Session, *, identity: str, decision: str, reason: str, result: str, secret_created: bool = False, ) -> None: log_event( session, source="manual", identity=identity, action_id=FORGE_OIDC_ACTION, target=FORGE_OIDC_TARGET, target_type="authentik_application", risk="critical", policy_decision=decision, reason=reason, result=result, redacted_metadata={ "clientId": FORGE_OIDC_CLIENT_ID, "secretRef": FORGE_OIDC_SECRET_REF, "secretCreated": secret_created, }, ) def _blocked( session: Session, *, identity: str, message: str, status: str = "blocked", decision: str = "blocked", secret_created: bool = False, ) -> dict[str, Any]: _audit( session, identity=identity, decision=decision, reason=message, result=status, secret_created=secret_created, ) return _result(status=status, message=message, secret_created=secret_created) def _existing_secret(session: Session) -> str | None: if not has_secret(session, FORGE_OIDC_SECRET_REF): return None return get_secret(session, FORGE_OIDC_SECRET_REF, touch=False) def _persist_forge_oidc_binding( session: Session, *, scope_mapping_ids: Any, provider_id: str | None = None, ) -> bool: """Persist one validated mapping tuple while preserving a known provider ID.""" from .setup_service import _apply_forge_oidc_binding_to_runtime try: mappings = ForgeOidcScopeMappingIdsV1.model_validate(scope_mapping_ids) except Exception: # noqa: BLE001 - invalid binding data is never serialized. return False binding_exists = has_secret(session, FORGE_OIDC_BINDING_SECRET_REF) binding_valid = _apply_forge_oidc_binding_to_runtime(session) if binding_exists and not binding_valid: return False current_ids = { "openid": settings.apis.get("AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING"), "email": settings.apis.get("AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING"), "profile": settings.apis.get("AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING"), } new_ids = {key: str(getattr(mappings, key)) for key in ("openid", "email", "profile")} if binding_valid and current_ids != new_ids: return False current_provider_id = settings.apis.get("AUTHENTIK_FORGE_OIDC_PROVIDER_ID") if binding_valid else None if current_provider_id is not None and provider_id is not None and current_provider_id != provider_id: return False selected_provider_id = provider_id if provider_id is not None else current_provider_id try: binding = ForgeOidcBindingV1( version=1, providerId=selected_provider_id, scopeMappingIds=new_ids, ) set_secret( session, FORGE_OIDC_BINDING_SECRET_REF, json.dumps(binding.model_dump(mode="json", by_alias=True), separators=(",", ":"), sort_keys=True), used_by=["authentik:itworx-forge"], metadata={"managedBy": "appops-forge-oidc-binding/v1"}, ) except Exception: # noqa: BLE001 - storage and serialization errors stay private. session.rollback() return False return _apply_forge_oidc_binding_to_runtime(session) def _connector_outcome( session: Session, *, identity: str, outcome: Any, secret_created: bool, ) -> dict[str, Any]: if not isinstance(outcome, dict): status, message = "failed", "forge_oidc_connector_result_invalid" success = False else: raw_status = outcome.get("status") mappings = { "created": ("created", "forge_oidc_created", True), "already_configured": ("already_configured", "forge_oidc_already_configured", True), "blocked": ("blocked", "forge_oidc_connector_blocked", False), "conflict": ("conflict", "forge_oidc_resource_conflict", False), "failed": ("failed", "forge_oidc_provisioning_failed", False), "partial_failure": ("partial_failure", "forge_oidc_partial_failure", False), } status, message, success = mappings.get( raw_status, ("failed", "forge_oidc_connector_result_invalid", False), ) if outcome.get("success") is not True: success = False elif status not in {"created", "already_configured"}: status, message, success = "failed", "forge_oidc_connector_result_invalid", False application_id = _safe_connector_id(outcome.get("applicationId")) if isinstance(outcome, dict) else None provider_id = _safe_connector_id(outcome.get("providerId")) if isinstance(outcome, dict) else None audit_result = "success" if success else status _audit( session, identity=identity, decision="allowed", reason=message, result=audit_result, secret_created=secret_created, ) return _result( status=status, message=message, success=success, secret_created=secret_created, application_id=application_id, provider_id=provider_id, ) def provision_forge_oidc( session: Session, *, actor_identity: str, authentik: Any | None = None, ) -> dict[str, Any]: """Provision only the fixed Forge OIDC client after exact-target governance.""" identity = actor_identity.strip() if isinstance(actor_identity, str) else "" if not identity: return _blocked( session, identity="system", message="forge_oidc_operator_identity_required", ) try: with _forge_oidc_process_lock(): # Re-evaluate governance after entering the runtime lock so concurrent # Forge provisioning attempts cannot interleave preflight and writes. if not _exact_policy_allowed(session): return _blocked( session, identity=identity, message="forge_oidc_exact_target_policy_required", ) if secret_storage_status().get("productionReady") is not True: return _blocked( session, identity=identity, message="forge_oidc_secret_store_not_production_ready", ) return _provision_forge_oidc_locked( session, identity=identity, authentik=authentik, ) except _ProvisionLockUnavailable: return _blocked( session, identity=identity, message="forge_oidc_provision_lock_unavailable", status="failed", ) def _provision_forge_oidc_locked( session: Session, *, identity: str, authentik: Any | None, ) -> dict[str, Any]: if authentik is None: try: from .setup_service import apply_stored_connector_settings apply_stored_connector_settings(session, {"authentik"}) except Exception: # noqa: BLE001 - never expose connector or stored-secret errors. return _blocked( session, identity=identity, message="forge_oidc_connector_configuration_unavailable", status="failed", decision="allowed", ) connector = authentik or AuthentikConnector() # A present but unreadable or tampered binding must never be repaired from # live discovery: its IDs are protected state, not caller-selected inputs. from .setup_service import _apply_forge_oidc_binding_to_runtime binding_exists = has_secret(session, FORGE_OIDC_BINDING_SECRET_REF) binding_valid = _apply_forge_oidc_binding_to_runtime(session) if binding_exists and not binding_valid: return _blocked( session, identity=identity, message="forge_oidc_binding_invalid", status="conflict", decision="allowed", ) secret_created = False client_secret = _existing_secret(session) if client_secret is None and has_secret(session, FORGE_OIDC_SECRET_REF): return _blocked( session, identity=identity, message="forge_oidc_existing_client_secret_unavailable", status="conflict", decision="allowed", ) if settings.apis.get("AUTHENTIK_FORGE_OIDC_PROVIDER_ID") is not None and client_secret is None: return _blocked( session, identity=identity, message="forge_oidc_existing_client_secret_missing", status="conflict", decision="allowed", ) try: prerequisites = connector.ensure_forge_oidc_prerequisites( expected_client_secret=client_secret, ) except Exception: # noqa: BLE001 - remote connector errors are returned only as a safe status. prerequisites = {"success": False, "status": "failed"} if not isinstance(prerequisites, dict) or prerequisites.get("success") is not True: return _connector_outcome( session, identity=identity, outcome=prerequisites, secret_created=False, ) mapping_ids = prerequisites.get("scopeMappingIds") try: validated_mapping_ids = ForgeOidcScopeMappingIdsV1.model_validate(mapping_ids) except Exception: # noqa: BLE001 - connector output is treated as untrusted. return _connector_outcome( session, identity=identity, outcome={"success": False, "status": "conflict", "message": "forge_oidc_scope_mapping_id_invalid"}, secret_created=False, ) serialized_mapping_ids = { key: str(getattr(validated_mapping_ids, key)) for key in ("openid", "email", "profile") } if not _persist_forge_oidc_binding(session, scope_mapping_ids=serialized_mapping_ids): return _connector_outcome( session, identity=identity, outcome={ "success": False, "status": "partial_failure", "message": "forge_oidc_binding_persist_failed", }, secret_created=False, ) try: preflight = connector.inspect_forge_oidc_application( expected_client_secret=client_secret, ) except Exception: # noqa: BLE001 - remote connector errors are returned only as a safe status. preflight = {"success": False, "status": "failed"} if not isinstance(preflight, dict) or preflight.get("success") is not True: return _connector_outcome( session, identity=identity, outcome=preflight, secret_created=False, ) if preflight.get("status") not in {"ready", "provider_orphan", "already_configured"}: return _connector_outcome( session, identity=identity, outcome={"success": False, "status": "failed"}, secret_created=False, ) if preflight.get("status") != "ready" and client_secret is None: return _blocked( session, identity=identity, message="forge_oidc_existing_client_secret_missing", status="conflict", decision="allowed", ) if client_secret is None: client_secret = _new_client_secret() if not 32 <= len(client_secret) <= 255 or not client_secret.isascii() or not client_secret.isprintable(): return _blocked( session, identity=identity, message="forge_oidc_client_secret_generation_failed", status="failed", decision="allowed", ) try: set_secret( session, FORGE_OIDC_SECRET_REF, client_secret, used_by=["authentik:itworx-forge"], metadata={"managedBy": "appops-forge-oidc/v1"}, ) except Exception: # noqa: BLE001 - secret-store errors are intentionally sanitized. session.rollback() return _blocked( session, identity=identity, message="forge_oidc_client_secret_store_failed", status="failed", decision="allowed", ) secret_created = True elif ( not isinstance(client_secret, str) or not 32 <= len(client_secret) <= 255 or not client_secret.isascii() or not client_secret.isprintable() ): return _blocked( session, identity=identity, message="forge_oidc_existing_client_secret_unavailable", status="conflict", decision="allowed", ) try: outcome = connector.ensure_forge_oidc_application( client_secret=client_secret, persist_provider_id=lambda provider_id: _persist_forge_oidc_binding( session, scope_mapping_ids=serialized_mapping_ids, provider_id=provider_id, ), ) except Exception: # noqa: BLE001 - no connector response/body crosses the route boundary. outcome = {"success": False, "status": "failed"} return _connector_outcome( session, identity=identity, outcome=outcome, secret_created=secret_created, )