You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/02/04 15:12:19 UTC
(tooling-trusted-releases) branch sbp updated (4cbc209 -> 5e3e525)
This is an automated email from the ASF dual-hosted git repository.
sbp pushed a change to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
from 4cbc209 Store data posted from GitHub during upload
add 12eb419 Add check clean to Makefile for cleaning pre-commit cache
add f994f84 #283: Add admin ID to session metadata and include in log and audit log kwargs
add 58dfcad #283: Make sure original admin ID is preserved across multiple masquerades
new 5e3e525 Use LDAP to construct a session when browsing as another user
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
Makefile | 5 +-
atr/admin/__init__.py | 179 +++++++-----------------------------------------
atr/log.py | 7 +-
atr/server.py | 2 +
atr/storage/__init__.py | 6 ++
5 files changed, 42 insertions(+), 157 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org
(tooling-trusted-releases) 01/01: Use LDAP to construct a session when browsing as another user
Posted by sb...@apache.org.
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 <se...@miscoranda.com>
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: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org