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 731a2962b5d5a6420064c97fb230732d55233614 Author: Sean B. Palmer <[email protected]> AuthorDate: Fri Feb 20 19:15:19 2026 +0000 Check for banned ASF accounts in more places --- atr/blueprints/get.py | 4 ++++ atr/blueprints/post.py | 4 ++++ atr/jwtoken.py | 13 ++++++++++--- atr/ldap.py | 13 +++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/atr/blueprints/get.py b/atr/blueprints/get.py index 396b5a33..f715defd 100644 --- a/atr/blueprints/get.py +++ b/atr/blueprints/get.py @@ -25,6 +25,7 @@ import asfquart.base as base import asfquart.session import quart +import atr.ldap as ldap import atr.log as log import atr.web as web @@ -39,6 +40,9 @@ def committer(path: str) -> Callable[[web.CommitterRouteFunction[Any]], web.Rout web_session = await asfquart.session.read() if web_session is None: raise base.ASFQuartException("Not authenticated", errorcode=401) + if (web_session.uid is None) or (not await ldap.is_active(web_session.uid)): + asfquart.session.clear() + raise base.ASFQuartException("Account is disabled", errorcode=401) enhanced_session = web.Committer(web_session) start_time_ns = time.perf_counter_ns() diff --git a/atr/blueprints/post.py b/atr/blueprints/post.py index e232b29e..8c9133a0 100644 --- a/atr/blueprints/post.py +++ b/atr/blueprints/post.py @@ -28,6 +28,7 @@ import pydantic import quart import atr.form +import atr.ldap as ldap import atr.log as log import atr.web as web @@ -42,6 +43,9 @@ def committer(path: str) -> Callable[[web.CommitterRouteFunction[Any]], web.Rout web_session = await asfquart.session.read() if web_session is None: raise base.ASFQuartException("Not authenticated", errorcode=401) + if (web_session.uid is None) or (not await ldap.is_active(web_session.uid)): + asfquart.session.clear() + raise base.ASFQuartException("Account is disabled", errorcode=401) enhanced_session = web.Committer(web_session) start_time_ns = time.perf_counter_ns() diff --git a/atr/jwtoken.py b/atr/jwtoken.py index 27bc937d..3f1c8dee 100644 --- a/atr/jwtoken.py +++ b/atr/jwtoken.py @@ -28,6 +28,7 @@ import jwt import quart import atr.config as config +import atr.ldap as ldap import atr.log as log import atr.util as util @@ -68,7 +69,7 @@ def require[**P, R](func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Aw async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: token = _extract_bearer_token(quart.request) try: - claims = verify(token) + claims = await verify(token) except jwt.ExpiredSignatureError as exc: raise base.ASFQuartException("Token has expired", errorcode=401) from exc except jwt.InvalidTokenError as exc: @@ -82,8 +83,8 @@ def require[**P, R](func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Aw return wrapper -def verify(token: str) -> dict[str, Any]: - return jwt.decode( +async def verify(token: str) -> dict[str, Any]: + claims = jwt.decode( token, _JWT_SECRET_KEY, algorithms=[_ALGORITHM], @@ -91,6 +92,12 @@ def verify(token: str) -> dict[str, Any]: audience=_ATR_JWT_AUDIENCE, options={"require": ["sub", "iss", "aud", "iat", "exp", "jti"]}, ) + asf_uid = claims.get("sub") + if not isinstance(asf_uid, str): + raise jwt.InvalidTokenError("Invalid Bearer JWT subject") + if not await ldap.is_active(asf_uid): + raise base.ASFQuartException("Account is disabled", errorcode=401) + return claims async def verify_github_oidc(token: str) -> dict[str, Any]: diff --git a/atr/ldap.py b/atr/ldap.py index 039f6f1d..4a3661d9 100644 --- a/atr/ldap.py +++ b/atr/ldap.py @@ -183,6 +183,19 @@ async def github_to_apache(github_numeric_uid: int) -> str: return ldap_uid_val[0] if isinstance(ldap_uid_val, list) else ldap_uid_val +async def is_active(asf_uid: str) -> bool: + import atr.config as config + + if get_bind_credentials() is None: + return True + if config.get().ALLOW_TESTS and (asf_uid == "test"): + return True + account = await account_lookup(asf_uid) + if account is None: + return False + return not is_banned(account) + + def is_banned(account: dict[str, str | list[str]]) -> bool: banned_attr = account.get("asf-banned", "no") # This is mostly for the type checker, but since asf-banned is missing from non-banned accounts, --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
