asf-tooling commented on issue #615:
URL:
https://github.com/apache/tooling-trusted-releases/issues/615#issuecomment-4410139713
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `discussion` • **Classification:** `no_action` •
**Confidence:** `high`
**Application domain(s):** `shared_infrastructure`,
`authentication_authorization`
### Summary
Issue #615 is an ongoing discussion about which ATR components should be
upstreamed to shared ASF libraries (`asfquart` and `asfpy`). The team has
identified `atr/jwtoken.py`, `atr/principal.py`, `atr/ldap.py`, and
`atr/log.py` as candidates. @gstein strongly supports JWT/secret management and
logging as priorities for asfquart. @alitheg linked an external PR
(infrastructure-asfquart#49). @dave2wave's most recent checklist (2026-04-08)
identifies session cache work, ASVS audit issues, pubsub, and 'noisey tokens'
as the asfquart items. Work is happening primarily in external repositories,
not this one.
### Where this lives in the code today
#### `atr/jwtoken.py` — `issue` (lines 71-89)
_currently does this_
JWT issuance logic identified as a candidate for upstreaming to asfquart as
a shared token scheme.
```python
def issue(uid: str, *, ttl: int = _ATR_JWT_TTL, pat_hash: str | None = None)
-> str:
# audit_guidance no explicit typ header or token_type claim is added:
the aud claim (_ATR_JWT_AUDIENCE)
# already acts as an explicit token type discriminator, and ATR issues
only one JWT type verified
# by a single internal verifier — the RFC 9068 typ header is relevant to
multi-issuer OAuth2 RS
# deployments, which this is not
now = datetime.datetime.now(tz=datetime.UTC)
payload = {
"sub": uid,
"iss": _ATR_JWT_ISSUER,
"aud": _ATR_JWT_AUDIENCE,
"iat": now,
"nbf": now,
"exp": now + datetime.timedelta(seconds=ttl),
"jti": secrets.token_hex(128 // 8),
}
if pat_hash:
payload["atr_th"] = pat_hash
log.auth_event("jwt_issuance", uid, pat_hash=pat_hash if pat_hash else
None)
return jwt.encode(payload, _signing_key(), algorithm=_ALGORITHM)
```
#### `atr/principal.py` — `Committer` (lines 66-80)
_currently does this_
Principal/committer verification via LDAP, identified for upstreaming to
provide group membership tests in shared libraries.
```python
class Committer:
"""Verifies and loads a committer's credentials via LDAP."""
def __init__(self, user: safe.AsfUid) -> None:
self.user = user
self.uid = str(user)
self.dn = LDAP_DN % user
self.email = f"{user}@apache.org"
self.fullname: str = ""
self.emails: list[str] = []
self.altemails: list[str] = []
self.isMember: bool = False
self.isChair: bool = False
self.pmcs: list[str] = []
self.projects: list[str] = []
```
#### `atr/ldap.py` — `Search` (lines 84-111)
_currently does this_
LDAP search context manager and query utilities, candidate for upstreaming
to asfquart/asfpy.
```python
class Search:
def __init__(self, ldap_bind_dn: str, ldap_bind_password: str):
self._bind_dn = ldap_bind_dn
self._bind_password = ldap_bind_password
self._conn: ldap3.Connection | None = None
def __enter__(self):
server = ldap3.Server(LDAP_SERVER_HOST, use_ssl=True,
tls=_tls_config)
self._conn = ldap3.Connection(
server,
user=self._bind_dn,
password=self._bind_password,
auto_bind=True,
check_names=False,
)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._conn and self._conn.bound:
self._conn.unbind()
def search(
self,
ldap_base: str,
ldap_scope: Literal["BASE", "LEVEL", "SUBTREE"],
ldap_query: str = "(objectClass=*)",
ldap_attrs: list[str] | None = None,
) -> list[Result]:
```
#### `atr/ldap.py` — `handle_update` (lines 261-284)
_currently does this_
Pubsub LDAP event handling - @dave2wave identified pubsub as likely
candidate for upstreaming to asfquart.
```python
async def handle_update(payload: dict) -> None:
import atr.log as log
try:
parsed = PubSubPayload.model_validate(payload)
except schema.pydantic.ValidationError:
log.warning(f"Failed to parse LDAP pubsub payload with DN:
{payload.get('dn', '<missing>')}")
return
uid = _extract_uid_from_pubsub(parsed)
if uid is None:
log.debug(f"Ignoring LDAP pubsub event with no uid: {parsed.dn}")
return
was_banned = bool(parsed.old_attributes.asf_banned)
now_banned = bool(parsed.new_attributes.asf_banned)
if now_banned and (not was_banned):
log.info(f"LDAP pubsub: user {uid} has been deactivated")
log.auth_event("account_deactivated", uid)
await _revoke_all_credentials(uid, log)
elif was_banned and (not now_banned):
log.info(f"LDAP pubsub: user {uid} has been reactivated")
log.auth_event("account_reactivated", uid)
```
#### `atr/log.py` — `auth_event` (lines 61-71)
_currently does this_
Structured authentication event logging with contextvar support, identified
by @gstein as desirable for upstreaming.
```python
def auth_event(event: str, asfuid: str | None = None, **kwargs):
request_user_id = get_context("user_id")
request_asf_id = get_context("asfuid")
admin_user_id = get_context("admin_id")
user_id = asfuid or request_user_id or request_asf_id
kwargs = {**kwargs, "event": event}
if user_id:
kwargs["request_user_id"] = user_id
if admin_user_id:
kwargs["admin_user_id"] = admin_user_id
_auth_log(**kwargs)
```
#### `atr/principal.py` — `AuthoriserASFQuart` (lines 217-234)
_currently does this_
Group membership tests for principals - the issue specifically calls out
'Missing group membership tests for principals who are members of a particular
ldap group' as needing to be upstreamed.
```python
class AuthoriserASFQuart:
def __init__(self):
self.__cache = cache
def is_member_of(self, asf_uid: str, committee_key: str) -> bool:
return committee_key in self.__cache.member_of[asf_uid]
def is_participant_of(self, asf_uid: str, committee_key: str) -> bool:
return committee_key in self.__cache.participant_of[asf_uid]
def member_of(self, asf_uid: str) -> frozenset[str]:
return self.__cache.member_of[asf_uid]
def participant_of(self, asf_uid: str) -> frozenset[str]:
return self.__cache.participant_of[asf_uid]
def member_of_and_participant_of(self, asf_uid: str) ->
tuple[frozenset[str], frozenset[str]]:
return self.__cache.member_of[asf_uid],
self.__cache.participant_of[asf_uid]
```
#### `typestubs/asfquart/ldap.pyi` — `LDAPClient` (lines 17-22)
_currently does this_
Type stub showing the current asfquart LDAP client interface that ATR's
richer LDAP utilities would supplement or replace upstream.
```python
class LDAPClient:
def __init__(self, username: str, password: str) -> None: ...
async def get_affiliations(self):
"""Scans for which projects this user is a part of. Returns a dict
with memberships of each
pmc/committer role (member/owner in LDAP)"""
...
```
### Proposed approach
This is a tracking/discussion issue for work that happens primarily in
external repositories (infrastructure-asfquart and infrastructure-asfpy). No
code changes should be made in this repository as part of this issue — the goal
is to extract and generalize the identified components for use across ASF
projects. @alitheg has already contributed infrastructure-asfquart PR #49.
@dave2wave's latest checklist (session cache work, ASVS audit issues, pubsub,
noisey tokens) represents the current priorities for asfquart upstreaming. The
issue serves as a coordination point and should remain open until the team is
satisfied that the relevant components have been upstreamed or explicitly
decided against.
### Open questions
- What is the status of infrastructure-asfquart PR #49 linked by @alitheg?
- What does 'noisey tokens' refer to in @dave2wave's checklist — is this
about token logging verbosity or a specific token mechanism?
- Which of the referenced L1 fix issues (#596, #601, #548, #544, #543, #541)
have already been upstreamed vs. still pending?
- What is issue #233 that @dave2wave says should land in asfquart?
- Is the 'session cache work' item referring to the Cache class in
atr/principal.py or something else?
_The agent reviewed this issue and is not proposing patches in this run.
Review the existing-code citations and open questions above before deciding
next steps._
### Files examined
- `atr/jwtoken.py`
- `atr/principal.py`
- `atr/ldap.py`
- `atr/log.py`
- `tests/unit/test_ldap.py`
- `typestubs/asfquart/ldap.pyi`
- `atr/config.py`
- `typestubs/asfquart/__init__.pyi`
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]