Skip to content

Commit 585021f

Browse files
sammuliclaude
andcommitted
harden(auth): create token cache file 0600 from the start; backfill tests
Use os.open() with O_CREAT|O_TRUNC|0o600 in _write_cache so the temp token file is never briefly world-readable under a permissive umask. Add two tests for custom bearer-env-var derivation and read-through (test_auth.py), plus an end-to-end auto_login test that runs setup_environment(auto_login=True) through to ensure_token (test_environment.py). 130 passed / 6 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f073a44 commit 585021f

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

fdp/auth.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ def _write_cache(handle, token: str) -> None:
176176
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
177177
os.chmod(path.parent, 0o700)
178178
tmp = path.with_suffix(".token.tmp")
179-
tmp.write_text(token)
179+
# Create with 0o600 from the start so the token is never briefly
180+
# world-readable under a permissive umask. O_TRUNC overwrites any
181+
# stale tmp from a crashed run; the explicit chmod fixes the mode of
182+
# such a pre-existing file (O_CREAT won't change an existing file's mode).
183+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
184+
with os.fdopen(fd, "w") as f:
185+
f.write(token)
180186
os.chmod(tmp, 0o600)
181187
os.replace(tmp, path)
182188

tests/test_auth.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ def test_unusable_legacy_file_warns(self):
126126
def test_all_empty_returns_none(self):
127127
self.assertIsNone(auth.get_valid_token(_bearer_handle()))
128128

129+
def test_bearer_env_custom_name(self):
130+
hint = SimpleNamespace(kind="bearer_token", env="FDP_TOKEN_X")
131+
loc = SimpleNamespace(auth=hint)
132+
handle = SimpleNamespace(schema=SimpleNamespace(
133+
name="x", pelican_root="p", locators=[loc]))
134+
self.assertEqual(auth.bearer_env(handle), "FDP_TOKEN_X")
135+
136+
def test_custom_env_var_used(self):
137+
os.environ["FDP_TOKEN_X"] = "ctok"
138+
self.addCleanup(os.environ.pop, "FDP_TOKEN_X", None)
139+
hint = SimpleNamespace(kind="bearer_token", env="FDP_TOKEN_X")
140+
loc = SimpleNamespace(auth=hint)
141+
handle = SimpleNamespace(schema=SimpleNamespace(
142+
name="x", pelican_root="p", locators=[loc]))
143+
self.assertEqual(auth.get_valid_token(handle), "ctok")
144+
129145

130146
class TestLoginLogout(unittest.TestCase):
131147
def setUp(self):

tests/test_environment.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,28 @@ def test_no_warn_when_opted_out(self):
198198
[w for w in caught if issubclass(w.category, UserWarning)],
199199
[])
200200

201+
def test_auto_login_sets_token_end_to_end(self):
202+
token = _unexpired_jwt()
203+
from fdp import auth
204+
205+
def fake_login(handle, write=False):
206+
cache = Path.home() / ".fdp" / "cache"
207+
cache.mkdir(parents=True, exist_ok=True)
208+
(cache / f"{handle.schema.name}.token").write_text(token)
209+
return auth.CachedToken(handle.schema.name, "read",
210+
auth.decode_exp(token))
211+
212+
with tempfile.TemporaryDirectory() as td:
213+
home = Path(td)
214+
(home / ".fdp").mkdir()
215+
with mock.patch.object(Path, "home", return_value=home):
216+
os.environ.pop("BEARER_TOKEN", None)
217+
with mock.patch("fdp.auth.login", side_effect=fake_login), \
218+
mock.patch("fdp.auth._auto_login_allowed",
219+
return_value=True):
220+
setup_environment(auto_login=True)
221+
self.assertEqual(os.environ["BEARER_TOKEN"], token)
222+
201223

202224
_MAST_TEST_YAML = """\
203225
schema_version: 1

0 commit comments

Comments
 (0)