Coverage for apps/integrations/resolver.py: 92%
28 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
1"""Credential resolution (design D3): user creds -> org creds -> platform/env default.
3Returns the resolved provider + decrypted secret for a category, or None when nothing is
4configured beyond the dev default — callers then fall back to the dev stub, so local/dev and the
5existing credit/AUS suites keep working.
6"""
8from __future__ import annotations
10from dataclasses import dataclass, field
12from django.conf import settings
14from apps.integrations.catalog import providers_by_category
15from apps.integrations.encryption import decrypt_secret
16from apps.integrations.models import OrgIntegration, UserCredential
18# Env var that holds the platform-default provider for a category (the dev fallback).
19_ENV_DEFAULT_SETTING = {"credit": "CREDIT_PROVIDER", "aus": "AUS_ENGINE"}
22@dataclass(frozen=True)
23class ResolvedCredential:
24 provider: str
25 scope: str # "user" | "org" | "platform"
26 config: dict = field(default_factory=dict)
27 secret: str | None = None
30def resolve_credentials(category: str, user=None, org=None) -> ResolvedCredential | None:
31 codes = [p.code for p in providers_by_category(category)]
33 if user is not None and codes:
34 uc = UserCredential.objects.filter(user=user, provider__in=codes).first()
35 if uc is not None: 35 ↛ 38line 35 didn't jump to line 38 because the condition on line 35 was always true
36 return ResolvedCredential(uc.provider, "user", uc.config, decrypt_secret(uc.secret_enc))
38 if org is not None and codes:
39 oi = (
40 OrgIntegration.objects.filter(org_unit=org, provider__in=codes, enabled=True).first()
41 )
42 if oi is not None:
43 return ResolvedCredential(oi.provider, "org", oi.config, decrypt_secret(oi.secret_enc))
45 env_setting = _ENV_DEFAULT_SETTING.get(category)
46 provider = getattr(settings, env_setting, "dev") if env_setting else "dev"
47 if provider and provider != "dev": 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true
48 return ResolvedCredential(provider, "platform", {}, None)
49 return None