asf-tooling commented on issue #1057:
URL:
https://github.com/apache/tooling-trusted-releases/issues/1057#issuecomment-4409867428
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `bug_fix` • **Classification:** `actionable` • **Confidence:**
`medium`
**Application domain(s):** `authentication_authorization`,
`shared_infrastructure`
### Summary
The issue reports that the session cache (util.session_cache_read/write)
stores authorization data without TTL. However, @alitheg noted that the only
usage of this cache in principal.py is already gated on debug mode. The triage
note says 'make this test-mode only'. The fix is to gate the /user/cache GET
and POST routes on debug/test mode so they return 404 in production, making the
TTL concern moot. @dave2wave asked for clarification on which URLs specifically
use the debug authorization, so the exact gating mechanism needs confirmation.
### Where this lives in the code today
#### `atr/get/user.py` — `cache_get` (lines 30-46)
_needs modification_
GET /user/cache route renders session cache management page; needs to be
gated on test/debug mode
```python
@get.typed
async def cache_get(session: web.Committer, _user_cache:
Literal["user/cache"]) -> str:
"""
URL: /user/cache
"""
cache_data = await util.session_cache_read()
user_cached = session.uid in cache_data
block = htm.Block()
block.h1["Session cache management"]
block.p[
"""This page allows you to cache your ASFQuart session information
for use in
contexts where web authentication is not available, such as SSH and
rsync, the
API, and background tasks. This is intended for developers only."""
]
```
#### `atr/post/user.py` — `session_post` (lines 34-50)
_needs modification_
POST /user/cache route handles cache creation/deletion; needs to be gated on
test/debug mode
```python
@post.typed
async def session_post(
session: web.Committer, _user_cache: Literal["user/cache"],
user_cache_form: shared.user.UserCacheForm
) -> web.WerkzeugResponse:
"""
URL: /user/cache
"""
match user_cache_form:
case shared.user.CacheUserForm():
await _cache_session(session)
await quart.flash("Your session has been cached successfully",
"success")
case shared.user.DeleteCacheForm():
await _delete_session_cache(session)
await quart.flash("Your cached session has been deleted",
"success")
return await session.redirect(get.user.cache_get)
```
#### `atr/post/user.py` — `_cache_session` (lines 118-137)
_currently does this_
Writes session authorization data to JSON cache without TTL or expiration
metadata
```python
async def _cache_session(session: web.Committer) -> None:
cache_data = await util.session_cache_read()
session_data = {
"uid": session.uid,
"dn": session.dn,
"fullname": session.fullname,
"email": session.email,
"isMember": session.is_member,
"isChair": session.is_chair,
# "isRoot": session.is_root,
"pmcs": session.committees,
"projects": session.projects,
"mfa": session.mfa,
"roleaccount": session.is_role,
}
cache_data[session.uid] = session_data
await util.session_cache_write(cache_data)
```
#### `atr/sessions.py` — `Store.validate` (lines 205-223)
_currently does this_
The real session store already has idle timeout and max age checks - this is
separate from the JSON file cache the issue targets
```python
async def validate(self, hsid: str) -> sql.UserSession | None:
try:
cached = getattr(quart.g, "_user_session", None)
if isinstance(cached, sql.UserSession) and cached.sid_hash ==
hsid:
return cached
except RuntimeError:
pass
max_session_age = config.get().MAX_SESSION_AGE
async with db.session() as data:
statement =
sqlmodel.select(sql.UserSession).where(sql.UserSession.sid_hash == hsid)
result = await data.execute(statement)
user_session = result.scalar_one_or_none()
if user_session is None:
return None
now = time.time()
if user_session.uts < (now - _SESSION_IDLE_TIMEOUT):
await data.delete(user_session)
await data.commit()
return None
```
### Proposed approach
Per @alitheg's observation, the session cache in principal.py is already
gated on debug mode. The fix is to also gate the /user/cache GET and POST
routes on the same debug/test mode check, returning a 404 (or raising an
appropriate error) in production. This makes the whole TTL question irrelevant
since the feature would only be available in development/test environments.
The exact debug mode check mechanism needs to be confirmed (likely via
`atr.config.get()` or a similar pattern). Once gated, the session cache JSON
file will only exist on dev machines, not in production deployments. A comment
in the route noting this is debug-only would also help future developers.
### Suggested patches
#### `atr/get/user.py`
Gate the GET /user/cache route on debug/test mode to prevent production use
````diff
--- a/atr/get/user.py
+++ b/atr/get/user.py
@@ -18,6 +18,8 @@
from typing import Literal
import atr.blueprints.get as get
+import atr.config as config
+import asfquart.base as base
import atr.form as form
import atr.htm as htm
import atr.shared as shared
@@ -30,6 +32,8 @@ async def cache_get(session: web.Committer, _user_cache:
Literal["user/cache"]
"""
URL: /user/cache
"""
+ if not config.get().DEBUG: # TODO: confirm the exact debug/test mode
flag name
+ raise base.ASFQuartException("Not found", errorcode=404)
cache_data = await util.session_cache_read()
user_cached = session.uid in cache_data
````
#### `atr/post/user.py`
Gate the POST /user/cache route on debug/test mode to prevent production use
````diff
--- a/atr/post/user.py
+++ b/atr/post/user.py
@@ -18,6 +18,8 @@ from typing import Literal
import quart
+import atr.config as config
+import asfquart.base as base
import atr.blueprints.post as post
import atr.form as form
import atr.get as get
@@ -51,6 +53,8 @@ async def session_post(
"""
URL: /user/cache
"""
+ if not config.get().DEBUG: # TODO: confirm the exact debug/test mode
flag name
+ raise base.ASFQuartException("Not found", errorcode=404)
match user_cache_form:
case shared.user.CacheUserForm():
await _cache_session(session)
````
### Open questions
- What is the exact config attribute for debug/test mode? (e.g.,
config.get().DEBUG, config.get().TEST_MODE, or another mechanism)
- Which part of principal.py uses the session cache and how is the debug
gate implemented there? This would inform the correct pattern to replicate.
- Should the /user/cache nav link also be hidden when not in debug mode?
- Are there other callers of util.session_cache_read() outside of the
/user/cache routes that also need gating?
### Files examined
- `atr/post/user.py`
- `atr/util.py`
- `atr/sessions.py`
- `atr/get/user.py`
- `migrations/versions/0065_2026.04.09_56a8188f.py`
- `tests/unit/test_sessions.py`
- `tests/unit/test_cache.py`
- `atr/cache.py`
---
*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]