160 lines
4.3 KiB
Python
160 lines
4.3 KiB
Python
"""Regression tests for fail-closed bootstrap and production auth configuration."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
|
|
def _configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
*,
|
|
app_env: str,
|
|
secret: str,
|
|
bootstrap: str,
|
|
debug: bool | None = None,
|
|
):
|
|
from app.config import Config
|
|
|
|
monkeypatch.setattr(Config, "APP_ENV", app_env)
|
|
monkeypatch.setattr(Config, "DATA_DIR", tmp_path)
|
|
monkeypatch.setattr(Config, "SECRET_KEY", secret)
|
|
monkeypatch.setattr(Config, "BOOTSTRAP_ADMIN_PASSWORD", bootstrap)
|
|
monkeypatch.setattr(
|
|
Config, "FLASK_DEBUG", app_env != "production" if debug is None else debug
|
|
)
|
|
return Config
|
|
|
|
|
|
def test_production_empty_store_requires_bootstrap_password(monkeypatch, tmp_path):
|
|
config = _configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="production",
|
|
secret="production-secret-0123456789abcdef0123456789abcdef",
|
|
bootstrap="",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
with pytest.raises(RuntimeError, match="BOOTSTRAP_ADMIN_PASSWORD"):
|
|
create_app()
|
|
|
|
|
|
def test_production_rejects_default_jwt_secret(monkeypatch, tmp_path):
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="production",
|
|
secret="short-invalid-secret",
|
|
bootstrap="bootstrap-password-0123456789",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
with pytest.raises(RuntimeError, match="JWT_SECRET"):
|
|
create_app()
|
|
|
|
|
|
def test_debug_off_cannot_downgrade_runtime_to_development(
|
|
monkeypatch, tmp_path
|
|
):
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="development",
|
|
debug=False,
|
|
secret="short-secret",
|
|
bootstrap="bootstrap-password-0123456789",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
with pytest.raises(RuntimeError, match="JWT_SECRET"):
|
|
create_app()
|
|
|
|
|
|
def test_debug_off_requires_strong_bootstrap_password(
|
|
monkeypatch, tmp_path
|
|
):
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="development",
|
|
debug=False,
|
|
secret="production-secret-0123456789abcdef0123456789abcdef",
|
|
bootstrap="short",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
with pytest.raises(RuntimeError, match="BOOTSTRAP_ADMIN_PASSWORD"):
|
|
create_app()
|
|
|
|
|
|
def test_existing_user_store_does_not_need_bootstrap_password(
|
|
monkeypatch, tmp_path
|
|
):
|
|
from app.auth.users import UserStore
|
|
|
|
seed_store = UserStore(tmp_path)
|
|
seed_store.create_org("Existing Organization", org_id="org-default")
|
|
seed_store.create_user(
|
|
org_id="org-default",
|
|
username="existing-admin",
|
|
password="existing-password",
|
|
name="Existing Admin",
|
|
role="super_admin",
|
|
must_setup=False,
|
|
)
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="production",
|
|
secret="production-secret-0123456789abcdef0123456789abcdef",
|
|
bootstrap="",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
app = create_app()
|
|
|
|
assert app.extensions["user_store"].get_user("existing-admin")["role"] == "super_admin"
|
|
|
|
|
|
def test_test_mode_uses_injected_bootstrap_password(monkeypatch, tmp_path):
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="test",
|
|
secret="pytest-secret-0123456789abcdef0123456789abcdef",
|
|
bootstrap="pytest-bootstrap-password",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
app = create_app()
|
|
user_store = app.extensions["user_store"]
|
|
|
|
assert user_store.get_user("admin")["must_setup"] is True
|
|
|
|
|
|
def test_default_runtime_environment_is_fail_closed(monkeypatch):
|
|
from app.config import Config
|
|
|
|
monkeypatch.delenv("APP_ENV", raising=False)
|
|
monkeypatch.delenv("FLASK_DEBUG", raising=False)
|
|
monkeypatch.setattr(Config, "APP_ENV", "production")
|
|
monkeypatch.setattr(Config, "FLASK_DEBUG", False)
|
|
monkeypatch.setattr(Config, "SECRET_KEY", "")
|
|
|
|
with pytest.raises(RuntimeError, match="JWT_SECRET"):
|
|
Config.validate_runtime_security()
|
|
|
|
|
|
def test_known_bootstrap_placeholder_is_rejected(monkeypatch, tmp_path):
|
|
_configure(
|
|
monkeypatch,
|
|
tmp_path,
|
|
app_env="production",
|
|
secret="production-secret-0123456789abcdef0123456789abcdef",
|
|
bootstrap="replace_with_a_strong_initial_password",
|
|
)
|
|
from app.factory import create_app
|
|
|
|
with pytest.raises(RuntimeError, match="BOOTSTRAP_ADMIN_PASSWORD"):
|
|
create_app()
|