This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 5e3e525898317038ecddb09a8790ee69fea177fc
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Feb 4 15:11:53 2026 +0000

    Use LDAP to construct a session when browsing as another user
---
 atr/admin/__init__.py | 178 +++++++-------------------------------------------
 1 file changed, 23 insertions(+), 155 deletions(-)

diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 278c9c7..23bf9c5 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -31,7 +31,6 @@ import asfquart
 import asfquart.base as base
 import asfquart.session
 import htpy
-import ldap3.utils.conv as conv
 import pydantic
 import quart
 import sqlalchemy
@@ -40,7 +39,6 @@ import sqlmodel
 
 import atr.blueprints.admin as admin
 import atr.config as config
-import atr.datasources.apache as apache
 import atr.db as db
 import atr.db.interaction as interaction
 import atr.form as form
@@ -130,55 +128,36 @@ async def browse_as_post(session: web.Committer, 
browse_form: BrowseAsUserForm)
     if not (current_session := await asfquart.session.read()):
         raise base.ASFQuartException("Not authenticated", 401)
 
-    bind_dn, bind_password = principal.get_ldap_bind_dn_and_password()
-    ldap_params = ldap.SearchParameters(
-        uid_query=new_uid,
-        bind_dn_from_config=bind_dn,
-        bind_password_from_config=bind_password,
-    )
-    await asyncio.to_thread(ldap.search, ldap_params)
-
-    if not ldap_params.results_list:
-        await quart.flash(f"User '{new_uid}' not found in LDAP.", "error")
+    try:
+        committer = principal.Committer(new_uid)
+        await asyncio.to_thread(committer.verify)
+    except principal.CommitterError as exc:
+        await quart.flash(f"Unable to browse as '{new_uid}': {exc}", "error")
         return await session.redirect(browse_as_get)
 
-    ldap_projects_data = await apache.get_ldap_projects_data()
-    committee_data = await apache.get_active_committee_data()
-    ldap_data = ldap_params.results_list[0]
-    log.info(f"Current ASFQuart session data: {current_session}")
     admin_id = current_session.uid
-    if "admin" in current_session.metadata:
-        admin_id = current_session.metadata["admin"]
-    new_session_data = _session_data(
-        ldap_data,
-        new_uid,
-        current_session,
-        ldap_projects_data,
-        committee_data,
-        bind_dn,
-        bind_password,
-        {"admin": admin_id},
-    )
-    log_safe_data = _log_safe_session_data(
-        ldap_data, new_uid, ldap_projects_data, committee_data, bind_dn, 
bind_password, {"admin": admin_id}
-    )
-    log.info(f"New Quart cookie (not ASFQuart session) data: {log_safe_data}")
+    if isinstance(current_session.metadata, dict):
+        existing_admin = current_session.metadata.get("admin")
+        if isinstance(existing_admin, str) and existing_admin:
+            admin_id = existing_admin
+
     asfquart.session.clear()
     # TODO: Make this safer
     session_cookie = atr.models.session.CookieData(
-        uid=new_session_data["uid"],
-        dn=new_session_data.get("dn"),
-        fullname=new_session_data.get("fullname"),
-        email=new_session_data.get("email"),
-        isMember=new_session_data.get("isMember", False),
-        isChair=new_session_data.get("isChair", False),
-        isRoot=new_session_data.get("isRoot", False),
-        pmcs=new_session_data.get("pmcs", []),
-        projects=new_session_data.get("projects", []),
-        mfa=new_session_data.get("mfa", False),
-        roleaccount=new_session_data.get("roleaccount", 
new_session_data.get("isRole", False)),
-        metadata=new_session_data.get("metadata", {}),
+        uid=committer.uid,
+        dn=None,
+        fullname=committer.fullname or committer.uid,
+        email=committer.email,
+        isMember=committer.isMember,
+        isChair=committer.isChair,
+        isRoot=committer.isRoot,
+        pmcs=sorted(set(committer.pmcs)),
+        projects=sorted(set(committer.projects)),
+        mfa=current_session.mfa,
+        roleaccount=False,
+        metadata={"admin": admin_id},
     )
+    # log.info(f"New Session Cookie Data: {session_cookie}")
     util.write_quart_session_cookie(session_cookie)
 
     await quart.flash(
@@ -1076,57 +1055,6 @@ async def 
_get_filesystem_dirs_unfinished(filesystem_dirs: list[str]) -> None:
                         filesystem_dirs.append(version_dir_path)
 
 
-def _get_user_committees_from_ldap(uid: str, bind_dn: str, bind_password: str) 
-> set[str]:
-    escaped_uid = conv.escape_filter_chars(uid)
-    with ldap.Search(bind_dn, bind_password) as ldap_search:
-        result = ldap_search.search(
-            ldap_base="ou=project,ou=groups,dc=apache,dc=org",
-            ldap_scope="SUBTREE",
-            
ldap_query=f"(|(ownerUid={escaped_uid})(owner=uid={escaped_uid},ou=people,dc=apache,dc=org))",
-            ldap_attrs=["cn"],
-        )
-
-    committees = set()
-    for hit in result:
-        if not isinstance(hit, dict):
-            continue
-        pmc = hit.get("cn")
-        if not (isinstance(pmc, list) and (len(pmc) == 1)):
-            continue
-        project_name = pmc[0]
-        if project_name and isinstance(project_name, str):
-            committees.add(project_name)
-
-    return committees
-
-
-def _log_safe_session_data(
-    ldap_data: dict[str, Any],
-    new_uid: str,
-    ldap_projects: apache.LDAPProjectsData,
-    committee_data: apache.CommitteeData,
-    bind_dn: str,
-    bind_password: str,
-    metadata: dict[str, Any] | None = None,
-) -> dict[str, Any]:
-    # This version excludes "mfa" which CodeQL treats as sensitive
-    # It's just a bool, but we can't suppress the error at source
-    common = _session_data_common(ldap_data, new_uid, ldap_projects, 
committee_data, bind_dn, bind_password)
-    return {
-        "uid": common.uid,
-        "dn": None,
-        "fullname": common.fullname,
-        "email": common.email,
-        "isMember": common.is_member,
-        "isChair": common.is_chair,
-        "isRoot": common.is_root,
-        "pmcs": common.pmcs,
-        "projects": common.projects,
-        "isRole": False,
-        "metadata": metadata if metadata is not None else {},
-    }
-
-
 async def _ongoing_tasks(
     session: web.Committer, project_name: str, version_name: str, revision: str
 ) -> web.QuartResponse:
@@ -1138,66 +1066,6 @@ async def _ongoing_tasks(
         return web.TextResponse("")
 
 
-def _session_data(
-    ldap_data: dict[str, Any],
-    new_uid: str,
-    current_session: asfquart.session.ClientSession,
-    ldap_projects: apache.LDAPProjectsData,
-    committee_data: apache.CommitteeData,
-    bind_dn: str,
-    bind_password: str,
-    metadata: dict[str, Any] | None = None,
-) -> dict[str, Any]:
-    common = _session_data_common(ldap_data, new_uid, ldap_projects, 
committee_data, bind_dn, bind_password)
-    return {
-        "uid": common.uid,
-        "dn": None,
-        "fullname": common.fullname,
-        "email": common.email,
-        "isMember": common.is_member,
-        "isChair": common.is_chair,
-        "isRoot": common.is_root,
-        # WARNING: ASFQuart session.ClientSession uses "committees"
-        # But this is cookie, not ClientSession, data, and requires "pmcs"
-        "pmcs": common.pmcs,
-        "projects": common.projects,
-        "mfa": current_session.mfa,
-        "isRole": False,
-        "metadata": metadata if metadata is not None else {},
-    }
-
-
-def _session_data_common(
-    ldap_data: dict[str, Any],
-    new_uid: str,
-    ldap_projects: apache.LDAPProjectsData,
-    committee_data: apache.CommitteeData,
-    bind_dn: str,
-    bind_password: str,
-) -> SessionDataCommon:
-    # This is not quite accurate
-    # For example, this misses "tooling" for tooling members
-    projects = {p.name for p in ldap_projects.projects if (new_uid in 
p.members) or (new_uid in p.owners)}
-    # And this adds "incubator", which is not in the OAuth data
-    committees = _get_user_committees_from_ldap(new_uid, bind_dn, 
bind_password)
-
-    # Or asf-member-status?
-    is_member = bool(projects or committees)
-    is_root = False
-    is_chair = any(new_uid in (user.id for user in c.chair) for c in 
committee_data.committees)
-
-    return SessionDataCommon(
-        uid=ldap_data.get("uid", [new_uid])[0],
-        fullname=ldap_data.get("cn", [new_uid])[0],
-        email=f"{new_uid}@apache.org",
-        is_member=is_member,
-        is_chair=is_chair,
-        is_root=is_root,
-        pmcs=sorted(list(committees)),
-        projects=sorted(list(projects)),
-    )
-
-
 async def _update_keys(asf_uid: str) -> int:
     async def _log_process(process: asyncio.subprocess.Process) -> None:
         try:


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to