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:
@@ -6,13 +6,12 @@ No service-account writes, password hashes, tokens or profile snapshots are stor
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signing
|
||||
from django.db import transaction
|
||||
from ldap3 import BASE, MODIFY_ADD, MODIFY_REPLACE
|
||||
from ldap3 import BASE, MODIFY_REPLACE
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from ldap3.operation.search import compile_filter, parse_filter
|
||||
from ldap3.protocol.controls import build_control
|
||||
@@ -22,12 +21,12 @@ from . import directory
|
||||
from .models import Account
|
||||
|
||||
|
||||
TEXT_ATTRIBUTES = (
|
||||
"description", "displayName", "givenName", "l", "labeledURI", "mobile", "o",
|
||||
"postalAddress", "postalCode", "roomNumber", "sn", "sshPublicKey", "st",
|
||||
"street", "telephoneNumber", "title",
|
||||
)
|
||||
READ_ATTRIBUTES = (*TEXT_ATTRIBUTES, "mail", "jpegPhoto", "entryUUID", "uid", "modifyTimestamp", "objectClass")
|
||||
# Only attributes the Dex LDAP connector reads back into ID-token claims are
|
||||
# editable: cn (nameAttr -> "name"). mail ("email") is changed through the
|
||||
# separate verified flow and uid ("preferred_username") is immutable. Other
|
||||
# directory attributes are left untouched, neither shown nor cleared.
|
||||
TEXT_ATTRIBUTES = ("cn",)
|
||||
READ_ATTRIBUTES = (*TEXT_ATTRIBUTES, "mail", "entryUUID", "uid", "modifyTimestamp")
|
||||
|
||||
|
||||
def _values(entry):
|
||||
@@ -89,10 +88,8 @@ def revision(account, values):
|
||||
|
||||
|
||||
def initial(values):
|
||||
def postal(value):
|
||||
# Decode once: a literal '\24' represented by '\5c24' must not become '$'.
|
||||
return re.sub(r"\\(24|5[cC])", lambda match: "$" if match.group(1) == "24" else "\\", str(value).replace("$", "\n"))
|
||||
return {name: ("\n\n".join(postal(value) for value in values[name]) if name == "postalAddress" else "\n".join(str(value) for value in values[name])) for name in TEXT_ATTRIBUTES}
|
||||
# Dex publishes the first cn value; saving collapses any extras to one.
|
||||
return {name: str(values[name][0]) if values[name] else "" for name in TEXT_ATTRIBUTES}
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -126,15 +123,6 @@ def update_profile(account, cleaned, expected_revision):
|
||||
new_values = cleaned[name]
|
||||
if new_values != values[name]:
|
||||
changes[name] = [(MODIFY_REPLACE, new_values)]
|
||||
if cleaned["sshPublicKey"] and "ldappublickey" not in {str(value).casefold() for value in values["objectClass"]}:
|
||||
# Fixed auxiliary class only, never arbitrary browser-supplied
|
||||
# schema/classes. Install the SSH schema before enabling edits.
|
||||
changes["objectClass"] = [(MODIFY_ADD, ["ldapPublicKey"])]
|
||||
photo = cleaned.get("jpegPhoto")
|
||||
if photo is not None:
|
||||
changes["jpegPhoto"] = [(MODIFY_REPLACE, [photo])]
|
||||
elif cleaned.get("remove_photo") and values["jpegPhoto"]:
|
||||
changes["jpegPhoto"] = [(MODIFY_REPLACE, [])]
|
||||
if changes and not conn.modify(account.ldap_dn, changes, controls=[_assertion(values)]):
|
||||
raise LDAPException("Profile modification rejected.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user