Profile: only edit fields Dex reads back

The Dex LDAP connector publishes uid, cn and mail as claims, so the
profile page now offers just Name (cn), the verified email change and
the password. The photo endpoint, SSH keys, addresses, phones and
other extended attributes are no longer read or written; existing LDAP
values are left untouched. Apache's profile upload limit drops back to
the 256 KiB used by other member routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 21:42:57 -05:00
co-authored by Claude Opus 5.5
parent 67d8caccce
commit d73f7d1b80
9 changed files with 114 additions and 507 deletions
+2 -29
View File
@@ -11,7 +11,6 @@ import stripe
from authlib.integrations.django_client import OAuth
from django.conf import settings
from django.core.validators import validate_email
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.mail import send_mail
from django.db import ProgrammingError, transaction
from django.http import HttpResponse
@@ -275,7 +274,7 @@ def logout(request):
def _profile_context(account, values):
return {"account": account, "profile_loaded": True, "form": ProfileForm(initial=profiles.initial(values)), "email_form": ProfileEmailForm(), "password_form": ProfilePasswordForm(), "has_photo": bool(values["jpegPhoto"]), "email_values": values["mail"], "revision": profiles.revision(account, values)}
return {"account": account, "profile_loaded": True, "form": ProfileForm(initial=profiles.initial(values)), "email_form": ProfileEmailForm(), "password_form": ProfilePasswordForm(), "email_values": values["mail"], "revision": profiles.revision(account, values)}
@sensitive_post_parameters()
@@ -302,7 +301,7 @@ def profile(request):
if action not in forms:
return HttpResponse(status=400)
form_key, form_class = forms[action]
form = form_class(request.POST, request.FILES) if action == "profile" else form_class(request.POST)
form = form_class(request.POST)
context[form_key] = form
if form.is_valid():
try:
@@ -336,32 +335,6 @@ def profile(request):
return render(request, "profile.html", context)
@require_GET
@account_required
def profile_photo(request):
if limited("profile-photo", str(request.member.pk), 120):
return HttpResponse(status=429)
try:
values = profiles.read_profile(request.member)
if not values["jpegPhoto"]:
return HttpResponse(status=404)
# Existing LDAP photos are untrusted too: rewrite before browser delivery.
raw = values["jpegPhoto"][0]
if not isinstance(raw, bytes):
raise ValueError("Invalid stored image.")
form = ProfileForm(files={"jpegPhoto": SimpleUploadedFile("photo.jpg", raw, content_type="image/jpeg")})
form.is_valid()
image = form.cleaned_data.get("jpegPhoto")
if not image:
return HttpResponse(status=404)
response = HttpResponse(image, content_type="image/jpeg")
response["Content-Disposition"] = 'inline; filename="profile.jpg"'
return response
except Exception as exc:
logger.error("Profile photo failed account=%s kind=%s", request.member.pk, type(exc).__name__)
return HttpResponse(status=404)
@require_GET
@account_required
def verify_profile_email(request, token):