chore: prepare repository for public release
This commit is contained in:
@@ -20,23 +20,30 @@ from app.services.segmentation_service import SegmentationService
|
||||
from app.services.job_service import JobService
|
||||
|
||||
|
||||
def auth_client(monkeypatch, *, guest_access: bool = False) -> TestClient:
|
||||
def auth_client(
|
||||
monkeypatch,
|
||||
*,
|
||||
guest_access: bool = False,
|
||||
require_https: bool = False,
|
||||
base_url: str = "http://testserver",
|
||||
) -> TestClient:
|
||||
password_hash = AuthService.hash_password(
|
||||
"correct horse battery staple",
|
||||
salt=b"geointel-test-salt",
|
||||
iterations=100_000,
|
||||
)
|
||||
monkeypatch.setenv("GEOINTEL_AUTH_ENABLED", "true")
|
||||
monkeypatch.setenv("GEOINTEL_AUTH_REQUIRE_HTTPS", "true" if require_https else "false")
|
||||
monkeypatch.setenv("GEOINTEL_AUTH_USERNAME", "operator")
|
||||
monkeypatch.setenv("GEOINTEL_AUTH_PASSWORD_HASH", password_hash)
|
||||
monkeypatch.setenv("GEOINTEL_AUTH_SESSION_SECRET", "test-session-secret-that-is-long-enough")
|
||||
monkeypatch.setenv("GEOINTEL_GUEST_ACCESS_ENABLED", "true" if guest_access else "false")
|
||||
monkeypatch.setenv("GEOINTEL_GUEST_DISPLAY_NAME", "Gast")
|
||||
monkeypatch.setenv("GEOINTEL_GUEST_SESSION_TTL_SECONDS", "7200")
|
||||
return TestClient(create_app())
|
||||
return TestClient(create_app(), base_url=base_url)
|
||||
|
||||
|
||||
def test_guest_access_defaults_on_when_operator_authentication_is_enabled(monkeypatch) -> None:
|
||||
def test_guest_access_defaults_off_when_operator_authentication_is_enabled(monkeypatch) -> None:
|
||||
password_hash = AuthService.hash_password(
|
||||
"correct horse battery staple",
|
||||
salt=b"geointel-test-salt",
|
||||
@@ -53,7 +60,7 @@ def test_guest_access_defaults_on_when_operator_authentication_is_enabled(monkey
|
||||
|
||||
assert session.status_code == 200
|
||||
assert session.json()["data"]["authentication_required"] is True
|
||||
assert session.json()["data"]["guest_access_enabled"] is True
|
||||
assert session.json()["data"]["guest_access_enabled"] is False
|
||||
|
||||
|
||||
def test_guest_default_is_inactive_but_valid_when_operator_authentication_is_disabled(monkeypatch) -> None:
|
||||
@@ -135,6 +142,35 @@ def test_login_uses_http_only_session_cookie_and_logout_revokes_browser_access(m
|
||||
assert protected_after_logout.status_code == 401
|
||||
|
||||
|
||||
def test_operator_login_can_require_https(monkeypatch) -> None:
|
||||
insecure_client = auth_client(monkeypatch, require_https=True)
|
||||
rejected = insecure_client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"username": "operator", "password": "correct horse battery staple"},
|
||||
)
|
||||
spoofed = insecure_client.post(
|
||||
"/api/v1/auth/login",
|
||||
headers={"x-forwarded-proto": "https", "x-real-ip": "203.0.113.9"},
|
||||
json={"username": "operator", "password": "correct horse battery staple"},
|
||||
)
|
||||
|
||||
secure_client = auth_client(
|
||||
monkeypatch,
|
||||
require_https=True,
|
||||
base_url="https://testserver",
|
||||
)
|
||||
accepted = secure_client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"username": "operator", "password": "correct horse battery staple"},
|
||||
)
|
||||
|
||||
assert rejected.status_code == 426
|
||||
assert rejected.json()["error"] == "AUTH_HTTPS_REQUIRED"
|
||||
assert spoofed.status_code == 426
|
||||
assert accepted.status_code == 200
|
||||
assert "secure" in accepted.headers["set-cookie"].lower()
|
||||
|
||||
|
||||
def test_guest_login_exposes_models_but_rejects_management_and_cross_project_requests(monkeypatch) -> None:
|
||||
project_id = UUID("00000000-0000-0000-0000-000000000123")
|
||||
demo = DemoWorkflowResponse(
|
||||
@@ -460,6 +496,10 @@ def test_unraid_runtime_carries_only_hashed_operator_credentials() -> None:
|
||||
assert "GEOINTEL_AUTH_PASSWORD_HASH=" in example
|
||||
assert "GEOINTEL_AUTHENTIK_CLIENT_SECRET=" in example
|
||||
assert "GEOINTEL_AUTH_PASSWORD=" not in runner
|
||||
assert "GEOINTEL_GUEST_ACCESS_ENABLED=true" in example
|
||||
assert 'GEOINTEL_GUEST_ACCESS_ENABLED="${GEOINTEL_GUEST_ACCESS_ENABLED:-true}"' in runner
|
||||
assert "GEOINTEL_AUTH_ENABLED=true" in example
|
||||
assert "GEOINTEL_AUTH_REQUIRE_HTTPS=true" in example
|
||||
assert "GEOINTEL_GUEST_ACCESS_ENABLED=false" in example
|
||||
assert 'GEOINTEL_AUTH_ENABLED="${GEOINTEL_AUTH_ENABLED:-true}"' in runner
|
||||
assert 'GEOINTEL_AUTH_REQUIRE_HTTPS="${GEOINTEL_AUTH_REQUIRE_HTTPS:-true}"' in runner
|
||||
assert 'GEOINTEL_GUEST_ACCESS_ENABLED="${GEOINTEL_GUEST_ACCESS_ENABLED:-false}"' in runner
|
||||
assert "/api/v1/auth/session" in browser_smoke
|
||||
|
||||
@@ -128,8 +128,8 @@ def test_compose_exposes_frontend_on_configurable_host_port_with_cors_origin() -
|
||||
compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8")
|
||||
env_example = (ROOT / ".env.example").read_text(encoding="utf-8")
|
||||
|
||||
assert '"${GEOINTEL_FRONTEND_PORT:-1202}:80"' in compose
|
||||
assert '"${GEOINTEL_BACKEND_PORT:-8000}:8000"' in compose
|
||||
assert '"${GEOINTEL_BIND_ADDRESS:-127.0.0.1}:${GEOINTEL_FRONTEND_PORT:-1202}:80"' in compose
|
||||
assert '"${GEOINTEL_BIND_ADDRESS:-127.0.0.1}:${GEOINTEL_BACKEND_PORT:-8000}:8000"' in compose
|
||||
assert "CORS_ORIGINS: ${GEOINTEL_CORS_ORIGINS:-http://localhost:1202,http://127.0.0.1:1202}" in compose
|
||||
assert "GEOINTEL_FRONTEND_PORT=1202" in env_example
|
||||
assert "GEOINTEL_BACKEND_PORT=8000" in env_example
|
||||
@@ -137,6 +137,18 @@ def test_compose_exposes_frontend_on_configurable_host_port_with_cors_origin() -
|
||||
assert "http://127.0.0.1:1202" in env_example
|
||||
|
||||
|
||||
def test_packaged_runtime_uses_fail_closed_authentication_defaults() -> None:
|
||||
compose = (ROOT / "docker-compose.unraid.yml").read_text(encoding="utf-8")
|
||||
env_example = (ROOT / "deploy" / "unraid" / "geointel.env.example").read_text(encoding="utf-8")
|
||||
|
||||
assert "GEOINTEL_AUTH_ENABLED: ${GEOINTEL_AUTH_ENABLED:-true}" in compose
|
||||
assert "GEOINTEL_AUTH_REQUIRE_HTTPS: ${GEOINTEL_AUTH_REQUIRE_HTTPS:-true}" in compose
|
||||
assert "GEOINTEL_GUEST_ACCESS_ENABLED: ${GEOINTEL_GUEST_ACCESS_ENABLED:-false}" in compose
|
||||
assert "GEOINTEL_AUTH_ENABLED=true" in env_example
|
||||
assert "GEOINTEL_AUTH_REQUIRE_HTTPS=true" in env_example
|
||||
assert "GEOINTEL_GUEST_ACCESS_ENABLED=false" in env_example
|
||||
|
||||
|
||||
def test_env_example_uses_runtime_env_names_read_by_backend_and_frontend() -> None:
|
||||
env_example = (ROOT / ".env.example").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestUrlShape:
|
||||
"http://127.0.0.1:8000/internal",
|
||||
"http://localhost/internal",
|
||||
"http://10.1.2.3/internal",
|
||||
"http://192.168.10.150/internal",
|
||||
"http://192.168.123.45/internal",
|
||||
"http://172.16.0.9/internal",
|
||||
"http://169.254.169.254/latest/meta-data/",
|
||||
"http://[::1]/internal",
|
||||
@@ -185,7 +185,7 @@ class TestTheGuardIsWiredIntoAcquisition:
|
||||
from app.services.orthophoto_acquisition_service import OrthophotoAcquisitionService
|
||||
|
||||
with pytest.raises(AppError) as exc_info:
|
||||
OrthophotoAcquisitionService._fetch("http://192.168.10.150/wms", self._settings())
|
||||
OrthophotoAcquisitionService._fetch("http://192.168.123.45/wms", self._settings())
|
||||
|
||||
assert exc_info.value.code == "OUTBOUND_URL_NOT_ALLOWED"
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_publication_hygiene_gate_passes_for_tracked_tree() -> None:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(ROOT / "scripts" / "check_repository_hygiene.py")],
|
||||
cwd=ROOT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_checkpoint_inspector_uses_weights_only_deserialization() -> None:
|
||||
inspector = (ROOT / "scripts" / "inspect_torch_checkpoint.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert "weights_only=True" in inspector
|
||||
assert "weights_only=False" not in inspector
|
||||
@@ -21,5 +21,5 @@ def test_evidence_bundle_script_accepts_browser_calibration_summary_export() ->
|
||||
assert "root_project_id = summary.get(\"project_id\")" in script
|
||||
assert "quality_check_ids" in script
|
||||
assert "Browser Detection Lab calibration summary" in readme
|
||||
assert "bash scripts/export_detection_calibration_evidence.sh http://192.168.10.150:1202 ./detection-calibration-summary.json" in readme
|
||||
assert "bash scripts/export_detection_calibration_evidence.sh http://192.168.123.45:1202 ./detection-calibration-summary.json" in readme
|
||||
assert "[x] Allow the evidence bundle script to consume Detection Lab calibration summary exports" in todo
|
||||
|
||||
@@ -279,7 +279,7 @@ def test_stage_and_apply_commands_are_separate_and_local_only(tmp_path: Path) ->
|
||||
assert "--force" not in apply and "--fetch-only" not in apply
|
||||
assert stage[stage.index("--archive-url") + 1] == release.archive_url
|
||||
with pytest.raises(RuntimeError, match="inside GeoIntel"):
|
||||
MANAGER.internal_base_url("http://192.168.10.150:1202/api/v1")
|
||||
MANAGER.internal_base_url("http://192.168.123.45:1202/api/v1")
|
||||
|
||||
|
||||
def test_staged_plan_binds_source_schema_codelist_scope_and_baseline(tmp_path: Path) -> None:
|
||||
|
||||
@@ -388,7 +388,7 @@ def test_approved_upload_uses_canonical_dataset_route_and_complete_provenance(
|
||||
def test_apply_target_must_be_loopback_api() -> None:
|
||||
assert MANAGER.internal_api_url("http://127.0.0.1:8000/api/v1").endswith("/api/v1")
|
||||
with pytest.raises(RuntimeError, match="local /api/v1"):
|
||||
MANAGER.internal_api_url("http://192.168.10.150:1202/api/v1")
|
||||
MANAGER.internal_api_url("http://192.168.123.45:1202/api/v1")
|
||||
with pytest.raises(RuntimeError, match="local /api/v1"):
|
||||
MANAGER.internal_api_url("http://127.0.0.1:8000/not-api")
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_unraid_template_documents_editable_runtime_settings() -> None:
|
||||
assert "Belgium and Belgian North Sea workbench" in template
|
||||
assert "<Repository>geointel-all-in-one:latest</Repository>" in template
|
||||
assert "<WebUI>http://[IP]:[PORT:80]/</WebUI>" in template
|
||||
assert "<Icon>http://192.168.10.150:1202/geointel-icon.png</Icon>" in template
|
||||
assert "gitea.example.com/owner/geointel/raw/branch/main" in template
|
||||
assert "<ExtraParams>--add-host=host.docker.internal:host-gateway</ExtraParams>" in template
|
||||
assert 'Target="80"' in template
|
||||
assert 'Target="/app/storage"' in template
|
||||
@@ -36,6 +36,9 @@ def test_unraid_env_template_matches_single_container_compose_variables() -> Non
|
||||
"GEOINTEL_POSTGRES_PASSWORD",
|
||||
"GEOINTEL_CORS_ORIGINS",
|
||||
"GEOINTEL_MAX_UPLOAD_MB",
|
||||
"GEOINTEL_AUTH_ENABLED",
|
||||
"GEOINTEL_AUTH_REQUIRE_HTTPS",
|
||||
"GEOINTEL_GUEST_ACCESS_ENABLED",
|
||||
):
|
||||
assert key in compose
|
||||
assert f"{key}=" in env_template
|
||||
|
||||
Reference in New Issue
Block a user