Publish curated ITWorx-ModelForge source from 990401737f96c8d46a9f7b0d84994516cadcb6a0

This commit is contained in:
Public Source Publisher committed 2026-09-30 09:23:17 +00:00
1 parent 7082ab955a
commit 848f0d8f8a
434 files changed
+67298 -4315

No files matched your search

+311
View File
@@ -0,0 +1,311 @@
from __future__ import annotations
import time
from unittest.mock import Mock
import pytest
import redis
from fastapi import HTTPException
from fastapi.testclient import TestClient
from pydantic import SecretStr
from modelforge_api.api.authorization import authenticate_operator_token
from modelforge_api.api.routes import operator_login
from modelforge_api.main import app
from modelforge_api.services.operator_password import (
hash_password,
issue_session,
parse_password_hash,
verify_password,
verify_session,
)
from modelforge_api.services.startup_validation import validate_settings
from modelforge_api.settings import Settings, get_settings
PASSWORD = "synthetic password fixture" # noqa: S105 - synthetic test input
BOOTSTRAP = "synthetic-bootstrap-fixture-" + "x" * 32
@pytest.fixture(scope="module")
def verifier() -> str:
return hash_password(PASSWORD)
@pytest.fixture
def settings(verifier: str) -> Settings:
return Settings(
_env_file=None,
env="test",
operator_api_key=SecretStr(BOOTSTRAP),
operator_password_hash=SecretStr(verifier),
)
@pytest.fixture
def client(settings: Settings, monkeypatch: pytest.MonkeyPatch):
connection = Mock()
connection.__enter__ = Mock(return_value=connection)
connection.__exit__ = Mock(return_value=False)
connection.eval.return_value = 1
monkeypatch.setattr(operator_login.redis.Redis, "from_url", Mock(return_value=connection))
app.dependency_overrides[get_settings] = lambda: settings
yield TestClient(app), connection
app.dependency_overrides.pop(get_settings, None)
def test_salted_password_and_wrong_value(verifier: str):
assert verify_password(PASSWORD, verifier)
assert not verify_password("wrong", verifier)
assert hash_password(PASSWORD) != verifier
assert len(parse_password_hash(verifier)[0]) == 16
@pytest.mark.parametrize(
"encoded",
[
"",
"scrypt$2$8$1$a$b",
"x" * 10000,
"scrypt$999999999$8$1$a$b",
"scrypt$32768$8$1$" + "a" * 66,
],
)
def test_malformed_or_weak_hash_rejected(encoded: str):
with pytest.raises(ValueError):
parse_password_hash(encoded)
configured = Settings(_env_file=None, operator_password_hash=SecretStr(encoded))
problems = validate_settings(configured).problems
assert (
any(p.setting == "MODELFORGE_OPERATOR_PASSWORD_HASH" for p in problems)
if encoded
else not any(p.setting == "MODELFORGE_OPERATOR_PASSWORD_HASH" for p in problems)
)
assert all(encoded not in p.message for p in problems) if encoded else True
def test_sessions_signed_expiring_rotating(verifier: str):
token, expiry = issue_session(BOOTSTRAP, verifier, now=1000)
assert expiry == 1900
assert verify_session(token, BOOTSTRAP, verifier, now=1000)
assert verify_session(token, BOOTSTRAP, verifier, now=1899)
assert not verify_session(token, BOOTSTRAP, verifier, now=1900)
assert not verify_session(token, BOOTSTRAP, verifier, now=999)
assert not verify_session(token, BOOTSTRAP + "rotated", verifier, now=1100)
assert not verify_session(token, BOOTSTRAP, hash_password(PASSWORD), now=1100)
assert not verify_session(token.replace("1900", "9900"), BOOTSTRAP, verifier, now=1100)
for malformed in [
"mfpw_",
"mfpw_" + "x" * 2000,
token + ".",
"mfsvc_" + token,
"mfpw_1.901.😃.😃",
]:
assert not verify_session(malformed, BOOTSTRAP, verifier, now=1100)
def test_login_session_and_legacy_key(client, settings: Settings):
http, connection = client
response = http.post("/api/v1/operator/login", json={"password": PASSWORD})
assert response.status_code == 200
assert response.headers["cache-control"] == "no-store"
result = response.json()
assert result["token_type"] == "bearer" # noqa: S105 - protocol type
assert PASSWORD not in response.text and BOOTSTRAP not in response.text
assert result["expires_in"] == 900
token = result["access_token"]
principal = authenticate_operator_token(settings, token)
assert principal.authentication_method == "password_session"
assert principal.role == "admin"
assert principal.expires_at is not None
identity = http.get("/api/v1/operator/identity", headers={"Authorization": f"Bearer {token}"})
assert identity.status_code == 200
assert identity.json()["authentication_method"] == "password_session"
assert identity.json()["credential_expires_at"] == result["expires_at"]
assert (
authenticate_operator_token(settings, BOOTSTRAP).authentication_method
== "legacy_admin_token"
)
for headers in [{"Authorization": f"Bearer {token}"}, {"X-ModelForge-Admin-Token": token}]:
# Routing 404 proves the protected request passed the authentication boundary.
assert http.get("/api/v1/not-a-route", headers=headers).status_code == 404
assert (
http.get("/api/v1/not-a-route", headers={"X-ModelForge-Admin-Token": PASSWORD}).status_code
== 401
)
connection.eval.assert_called_once()
@pytest.mark.parametrize(
"payload",
[
{},
{"password": ""},
{"password": None},
{"password": 123},
{"password": ["do-not-echo"]},
{"password": "z" * 1025},
{"password": PASSWORD, "do-not-echo": PASSWORD},
],
)
def test_invalid_login_redacted(client, payload):
http, connection = client
response = http.post("/api/v1/operator/login", json=payload)
assert response.status_code == 422
assert PASSWORD not in response.text and "do-not-echo" not in response.text
assert response.json()["error"]["details"] == {}
assert response.headers["cache-control"] == "no-store"
connection.eval.assert_called_once()
def test_wrong_password_and_no_config(client, settings: Settings):
http, _ = client
response = http.post("/api/v1/operator/login", json={"password": "wrong"})
assert response.status_code == 401 and "wrong" not in response.text
settings.operator_password_hash = None
assert http.post("/api/v1/operator/login", json={"password": PASSWORD}).status_code == 503
def test_limiter_runs_before_parser_and_ignores_forwarded_ip(client):
http, connection = client
connection.eval.side_effect = [1, 0, 0]
assert http.post("/api/v1/operator/login", content="not json").status_code == 422
assert (
http.post(
"/api/v1/operator/login",
json={"password": PASSWORD},
headers={"X-Forwarded-For": "203.0.113.1"},
).status_code
== 429
)
assert (
http.post(
"/api/v1/operator/login",
json={"password": PASSWORD},
headers={"X-Forwarded-For": "203.0.113.2"},
).status_code
== 429
)
assert len({call.args[-1] for call in connection.eval.call_args_list}) == 1
script = connection.eval.call_args.args[0]
assert "redis.call('INCR', KEYS[1])" in script
assert "redis.call('EXPIRE', KEYS[1], 60)" in script
assert "global > 30" in script and "peer > 5" in script
def test_redis_failure_fails_closed(client):
http, connection = client
connection.eval.side_effect = redis.ConnectionError("secret topology must not leak")
response = http.post("/api/v1/operator/login", json={"password": PASSWORD})
assert response.status_code == 503
assert "topology" not in response.text and PASSWORD not in response.text
def test_invalid_session_never_falls_back(settings: Settings):
for token in ["mfpw_fake", PASSWORD, None]:
with pytest.raises(HTTPException) as exc:
authenticate_operator_token(settings, token)
assert exc.value.status_code == 401
token, _ = issue_session(
BOOTSTRAP, settings.operator_password_hash.get_secret_value(), now=int(time.time()) - 1000
)
with pytest.raises(HTTPException):
authenticate_operator_token(settings, token)
def test_rotations_reject_existing_session(client, settings: Settings):
http, _ = client
token = http.post("/api/v1/operator/login", json={"password": PASSWORD}).json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
settings.operator_password_hash = SecretStr(hash_password(PASSWORD))
assert http.get("/api/v1/not-a-route", headers=headers).status_code == 401
token = http.post("/api/v1/operator/login", json={"password": PASSWORD}).json()["access_token"]
settings.operator_api_key = SecretStr(BOOTSTRAP + "rotated")
assert (
http.get("/api/v1/not-a-route", headers={"Authorization": f"Bearer {token}"}).status_code
== 401
)
def test_login_body_bounded_before_parsing(client):
http, connection = client
response = http.post("/api/v1/operator/login", content="x" * 9000)
assert response.status_code == 413
assert response.headers["cache-control"] == "no-store"
assert "x" * 100 not in response.text
connection.eval.assert_called_once()
def test_login_requires_strong_bootstrap(client, settings: Settings):
http, _ = client
settings.operator_api_key = SecretStr("short fixture")
assert http.post("/api/v1/operator/login", json={"password": PASSWORD}).status_code == 503
def test_cli_stdin_outputs_hash_only():
import subprocess
import sys
from pathlib import Path
root = Path(__file__).resolve().parents[2]
result = subprocess.run( # noqa: S603 - fixed local CLI and synthetic stdin
[sys.executable, str(root / "scripts/operator_password.py")],
input=PASSWORD + "\n",
text=True,
capture_output=True,
check=False,
)
assert result.returncode == 0 and result.stderr == ""
assert PASSWORD not in result.stdout
assert verify_password(PASSWORD, result.stdout.strip())
bad = subprocess.run( # noqa: S603 - fixed local CLI and synthetic stdin
[sys.executable, str(root / "scripts/operator_password.py")],
input="z" * 10000,
text=True,
capture_output=True,
check=False,
)
assert bad.returncode == 2 and bad.stdout == "" and "z" * 10 not in bad.stderr
def test_empty_optional_password_hash_is_unset():
for value in ["", SecretStr("")]:
configured = Settings(_env_file=None, operator_password_hash=value)
assert configured.operator_password_hash is None
assert not any(
p.setting == "MODELFORGE_OPERATOR_PASSWORD_HASH"
for p in validate_settings(configured).problems
)
def test_legacy_key_with_session_prefix_remains_accepted(settings: Settings):
legacy = "mfpw_" + "synthetic-key-fixture" * 3
settings.operator_api_key = SecretStr(legacy)
assert (
authenticate_operator_token(settings, legacy).authentication_method == "legacy_admin_token"
)
def test_password_configuration_requires_signing_key_in_production(verifier: str):
configured = Settings(_env_file=None, env="production", operator_api_key=None,
operator_password_hash=SecretStr(verifier))
assert any(p.setting == "MODELFORGE_OPERATOR_API_KEY" and "session signing" in p.message
for p in validate_settings(configured).problems)
@pytest.mark.parametrize("bootstrap", [None, "", "short", "x" * 31, "x" * 32])
def test_named_admin_cannot_replace_password_signing_key(verifier: str, bootstrap: str | None):
import hashlib
import json
from datetime import UTC, datetime, timedelta
identities = SecretStr(json.dumps([{
"credential_id": "fixture-admin", "subject": "fixture-person", "role": "admin",
"token_sha256": hashlib.sha256(("mfop_" + "a" * 43).encode()).hexdigest(),
"expires_at": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
}]))
configured = Settings(_env_file=None, env="production", operator_identities=identities,
operator_api_key=None if bootstrap is None else SecretStr(bootstrap),
operator_password_hash=SecretStr(verifier))
signing_problems = [p for p in validate_settings(configured).problems
if p.setting == "MODELFORGE_OPERATOR_API_KEY" and "session signing" in p.message]
assert bool(signing_problems) == (bootstrap is None or len(bootstrap) < 32)