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]

Reply via email to