Private JWK files were written under the default umask (observed 0664 — group and world readable). Create the key directory 0700, chmod private key files (private_jwks.json, token_jwks.json) to 0600 after they are written, and refuse to start if a pre-existing private key is group/world accessible. Tests now use an isolated per-test key directory. Refs: porchlight-91i Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from porchlight.config import Settings
|
|
from porchlight.oidc.claims import PorchlightUserInfo
|
|
from porchlight.oidc.provider import create_oidc_server
|
|
|
|
|
|
def test_create_server_has_endpoints() -> None:
|
|
key_path = Path("test_keys_provider")
|
|
key_path.mkdir(exist_ok=True)
|
|
try:
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
server = create_oidc_server(settings)
|
|
assert "authorization" in server.endpoint
|
|
assert "token" in server.endpoint
|
|
assert "userinfo" in server.endpoint
|
|
assert "provider_config" in server.endpoint
|
|
finally:
|
|
shutil.rmtree(key_path, ignore_errors=True)
|
|
|
|
|
|
def test_create_server_has_issuer() -> None:
|
|
key_path = Path("test_keys_issuer")
|
|
key_path.mkdir(exist_ok=True)
|
|
try:
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
server = create_oidc_server(settings)
|
|
assert server.context.issuer == "http://localhost:8000"
|
|
finally:
|
|
shutil.rmtree(key_path, ignore_errors=True)
|
|
|
|
|
|
def test_create_server_jwks_available() -> None:
|
|
key_path = Path("test_keys_jwks")
|
|
key_path.mkdir(exist_ok=True)
|
|
try:
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
server = create_oidc_server(settings)
|
|
keys = server.keyjar.export_jwks()
|
|
assert "keys" in keys
|
|
assert len(keys["keys"]) > 0
|
|
finally:
|
|
shutil.rmtree(key_path, ignore_errors=True)
|
|
|
|
|
|
def test_create_server_userinfo_is_porchlight() -> None:
|
|
key_path = Path("test_keys_userinfo")
|
|
key_path.mkdir(exist_ok=True)
|
|
try:
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
server = create_oidc_server(settings)
|
|
assert isinstance(server.context.userinfo, PorchlightUserInfo)
|
|
finally:
|
|
shutil.rmtree(key_path, ignore_errors=True)
|
|
|
|
|
|
def test_signing_key_files_have_strict_permissions(tmp_path: Path) -> None:
|
|
key_path = tmp_path / "keys"
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
create_oidc_server(settings)
|
|
|
|
assert (key_path.stat().st_mode & 0o077) == 0, "key directory must not be group/world accessible"
|
|
for name in ("private_jwks.json", "token_jwks.json"):
|
|
f = key_path / name
|
|
assert f.exists()
|
|
assert (f.stat().st_mode & 0o077) == 0, f"{name} must be 0600"
|
|
|
|
|
|
def test_startup_fails_on_world_readable_private_key(tmp_path: Path) -> None:
|
|
key_path = tmp_path / "keys"
|
|
key_path.mkdir()
|
|
# Simulate a pre-existing private key left group/world readable.
|
|
leaked = key_path / "private_jwks.json"
|
|
leaked.write_text("{}")
|
|
os.chmod(leaked, 0o644)
|
|
|
|
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
|
|
with pytest.raises(RuntimeError, match="permission"):
|
|
create_oidc_server(settings)
|