OIDC Integration: Supports Authentik officially, other solutions currently untested. Social login not implemented
This commit is contained in:
1 parent
dfea7e869b
commit
68bbb53976
28 files changed
+2365
-751
No files matched your search
+100
-2
@@ -5,12 +5,32 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import redirect, render
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from apps.keys.certificates import issue_certificate_for_key
|
||||
from apps.keys.models import SSHKey
|
||||
|
||||
from .forms import ErasureRequestForm, SSHKeyForm
|
||||
from .models import ErasureRequest
|
||||
from .models import ErasureRequest, ExternalIdentity
|
||||
|
||||
|
||||
def _oidc_provider_label() -> str:
|
||||
endpoint = (
|
||||
getattr(settings, "OIDC_OP_AUTHORIZATION_ENDPOINT", "")
|
||||
or getattr(settings, "OIDC_OP_DISCOVERY_ENDPOINT", "")
|
||||
or getattr(settings, "OIDC_OP_USER_ENDPOINT", "")
|
||||
or ""
|
||||
)
|
||||
if not endpoint:
|
||||
return "Identity Provider"
|
||||
parsed = urlparse(endpoint)
|
||||
host = (parsed.hostname or "").strip().lower()
|
||||
if not host:
|
||||
return "Identity Provider"
|
||||
parts = [part for part in host.split(".") if part and part != "www"]
|
||||
if len(parts) >= 2:
|
||||
return parts[-2].replace("-", " ").title()
|
||||
return host.replace("-", " ").title()
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@@ -59,9 +79,65 @@ def profile(request):
|
||||
key_form = SSHKeyForm()
|
||||
|
||||
ssh_keys = SSHKey.objects.filter(user=request.user).order_by("-created_at")
|
||||
external_identities = ExternalIdentity.objects.filter(user=request.user).order_by("-last_login_at", "provider_id")
|
||||
oidc_identity = external_identities.filter(provider_type=ExternalIdentity.ProviderType.OIDC).first()
|
||||
social_identity_map = {
|
||||
identity.provider_id.lower(): identity
|
||||
for identity in external_identities.filter(provider_type=ExternalIdentity.ProviderType.SOCIAL)
|
||||
}
|
||||
configured_social_map = {
|
||||
provider.get("id", "").strip().lower(): provider
|
||||
for provider in getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", [])
|
||||
if provider.get("id")
|
||||
}
|
||||
|
||||
social_provider_defaults = [
|
||||
("github", "GitHub"),
|
||||
("google", "Google"),
|
||||
("apple", "Apple"),
|
||||
("gitlab", "GitLab"),
|
||||
("microsoft", "Microsoft Entra ID"),
|
||||
]
|
||||
social_provider_cards = []
|
||||
seen_provider_ids = set()
|
||||
for provider_id, provider_name in social_provider_defaults:
|
||||
provider_config = configured_social_map.get(provider_id, {})
|
||||
identity = social_identity_map.get(provider_id)
|
||||
social_provider_cards.append(
|
||||
{
|
||||
"id": provider_id,
|
||||
"name": provider_name,
|
||||
"configured": bool(provider_config.get("login_url")),
|
||||
"login_url": provider_config.get("login_url", ""),
|
||||
"linked": bool(identity),
|
||||
"identity": identity,
|
||||
}
|
||||
)
|
||||
seen_provider_ids.add(provider_id)
|
||||
|
||||
for provider_id, provider_config in configured_social_map.items():
|
||||
if provider_id in seen_provider_ids:
|
||||
continue
|
||||
identity = social_identity_map.get(provider_id)
|
||||
social_provider_cards.append(
|
||||
{
|
||||
"id": provider_id,
|
||||
"name": provider_config.get("name", provider_id.title()),
|
||||
"configured": bool(provider_config.get("login_url")),
|
||||
"login_url": provider_config.get("login_url", ""),
|
||||
"linked": bool(identity),
|
||||
"identity": identity,
|
||||
}
|
||||
)
|
||||
|
||||
context = {
|
||||
"user": request.user,
|
||||
"auth_mode": getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid"),
|
||||
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)),
|
||||
"oidc_identity": oidc_identity,
|
||||
"oidc_provider_id": getattr(settings, "KEYWARDEN_OIDC_PROVIDER_ID", "oidc"),
|
||||
"oidc_issuer": getattr(settings, "KEYWARDEN_OIDC_ISSUER", ""),
|
||||
"social_provider_cards": social_provider_cards,
|
||||
"erasure_request": erasure_request,
|
||||
"erasure_form": erasure_form,
|
||||
"key_form": key_form,
|
||||
@@ -75,8 +151,30 @@ def login_view(request):
|
||||
auth_mode = getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid")
|
||||
if auth_mode == "oidc":
|
||||
return redirect("/oidc/authenticate/")
|
||||
|
||||
has_sso_options = bool(
|
||||
getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)
|
||||
or getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", [])
|
||||
)
|
||||
show_native_login = (
|
||||
request.method == "POST"
|
||||
or request.GET.get("native") == "1"
|
||||
or (auth_mode == "native" and not has_sso_options)
|
||||
)
|
||||
|
||||
# native or hybrid -> render Django's built-in login view
|
||||
return auth_views.LoginView.as_view(template_name="accounts/login.html")(request)
|
||||
return auth_views.LoginView.as_view(
|
||||
template_name="accounts/login.html",
|
||||
extra_context={
|
||||
"social_providers": getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", []),
|
||||
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)),
|
||||
"oidc_login_url": "/oidc/authenticate/",
|
||||
"oidc_provider_label": _oidc_provider_label(),
|
||||
"show_native_login": show_native_login,
|
||||
"can_use_native_login": auth_mode in {"native", "hybrid"},
|
||||
"show_sso_choice": has_sso_options and not show_native_login,
|
||||
},
|
||||
)(request)
|
||||
|
||||
|
||||
def logout_view(request):
|
||||
|
||||
Reference in new issue
Block a user