feat: add enterprise identity and channel integrations
This commit is contained in:
72
backend/apps/accounts/oidc.py
Normal file
72
backend/apps/accounts/oidc.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""OIDC 客户端构建和安全重定向辅助函数。"""
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from apps.accounts.models import EnterpriseIdentityProvider
|
||||
from apps.modelhub.crypto import decrypt_secret
|
||||
|
||||
|
||||
class OIDCConfigurationError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def provider_metadata_url(provider: EnterpriseIdentityProvider) -> str:
|
||||
if provider.metadata_url:
|
||||
return provider.metadata_url
|
||||
if provider.issuer:
|
||||
return f"{provider.issuer.rstrip('/')}/.well-known/openid-configuration"
|
||||
raise OIDCConfigurationError("OIDC 提供方缺少发现文档地址或 issuer")
|
||||
|
||||
|
||||
def build_oidc_client(provider: EnterpriseIdentityProvider):
|
||||
"""构建依赖发现文档和加密凭据的 OIDC 客户端。"""
|
||||
|
||||
try:
|
||||
from authlib.integrations.django_client import OAuth
|
||||
except ImportError as error:
|
||||
raise OIDCConfigurationError("服务端未安装 Authlib") from error
|
||||
|
||||
if not provider.client_id:
|
||||
raise OIDCConfigurationError("OIDC 提供方缺少 client_id")
|
||||
if not provider.credential_encrypted:
|
||||
raise OIDCConfigurationError("OIDC 提供方缺少 client secret")
|
||||
scopes = provider.scopes or ["openid", "profile", "email"]
|
||||
if "openid" not in scopes:
|
||||
raise OIDCConfigurationError("OIDC scopes 必须包含 openid")
|
||||
oauth = OAuth()
|
||||
return oauth.register(
|
||||
name=f"enterprise_oidc_{provider.pk}",
|
||||
client_id=provider.client_id,
|
||||
client_secret=decrypt_secret(provider.credential_encrypted),
|
||||
server_metadata_url=provider_metadata_url(provider),
|
||||
client_kwargs={
|
||||
"scope": " ".join(scopes),
|
||||
"token_endpoint_auth_method": provider.configuration.get(
|
||||
"token_endpoint_auth_method",
|
||||
"client_secret_basic",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def callback_uri(request, provider: EnterpriseIdentityProvider) -> str:
|
||||
path = f"/api/identity/auth/oidc/{provider.slug}/callback"
|
||||
if settings.OIDC_CALLBACK_BASE_URL:
|
||||
return f"{settings.OIDC_CALLBACK_BASE_URL}{path}"
|
||||
return request.build_absolute_uri(path)
|
||||
|
||||
|
||||
def normalize_return_to(value: str | None) -> str:
|
||||
"""只允许站内绝对路径,阻断协议相对地址和外部开放重定向。"""
|
||||
|
||||
if not value or not value.startswith("/") or value.startswith("//") or len(value) > 500:
|
||||
return "/"
|
||||
return value
|
||||
|
||||
|
||||
def frontend_redirect(return_to: str = "/", error: str = "") -> str:
|
||||
if error:
|
||||
return f"{settings.FRONTEND_APP_URL}/#/login?auth_error={quote(error[:240])}"
|
||||
return f"{settings.FRONTEND_APP_URL}/#{normalize_return_to(return_to)}"
|
||||
Reference in New Issue
Block a user