From 56ade7fb089ea60d90643bf79ee91c0ec75fe4cc Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 14 Apr 2026 09:51:23 +0200 Subject: [PATCH] Task 13: wire geo_batch_lookup through dependency injection and mark task completed --- Docs/Tasks.md | 2 ++ backend/app/dependencies.py | 9 ++++++ backend/app/routers/bans.py | 13 +++----- backend/app/routers/blocklist.py | 4 ++- backend/app/routers/dashboard.py | 7 +++-- backend/app/routers/jails.py | 54 +++++++++++++++++--------------- 6 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index ce743ff..518b6e8 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -371,6 +371,8 @@ None. ### Task 13 — Wire geo_batch_lookup through dependency injection +**Status:** Completed + **Severity:** Medium **Where:** diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index eb32ddb..22e591b 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -20,6 +20,7 @@ from fastapi import Depends, FastAPI, HTTPException, Request, status from app.config import Settings from app.models.auth import Session from app.models.config import PendingRecovery +from app.models.geo import GeoBatchLookup from app.models.server import ServerStatus from app.repositories.protocols import SessionRepository from app.services.protocols import AuthService, JailService @@ -195,6 +196,13 @@ async def get_fail2ban_start_command(settings: Settings = Depends(get_settings)) return settings.fail2ban_start_command +async def get_geo_batch_lookup() -> GeoBatchLookup: + """Provide the concrete geo batch lookup callable used by routers.""" + from app.services import geo_service # noqa: PLC0415 + + return geo_service.lookup_batch + + async def get_session_cache(app_context: Annotated[ApplicationContext, Depends(get_app_context)]) -> SessionCache: """Provide the configured session cache backend from application context.""" if app_context.session_cache is None: @@ -326,6 +334,7 @@ SchedulerDep = Annotated[AsyncIOScheduler, Depends(get_scheduler)] Fail2BanSocketDep = Annotated[str, Depends(get_fail2ban_socket)] Fail2BanConfigDirDep = Annotated[str, Depends(get_fail2ban_config_dir)] Fail2BanStartCommandDep = Annotated[str, Depends(get_fail2ban_start_command)] +GeoBatchLookupDep = Annotated[GeoBatchLookup, Depends(get_geo_batch_lookup)] ServerStatusDep = Annotated[ServerStatus, Depends(get_server_status)] PendingRecoveryDep = Annotated[PendingRecovery | None, Depends(get_pending_recovery)] SessionCacheDep = Annotated[SessionCache, Depends(get_session_cache)] diff --git a/backend/app/routers/bans.py b/backend/app/routers/bans.py index 3848e16..baba536 100644 --- a/backend/app/routers/bans.py +++ b/backend/app/routers/bans.py @@ -10,23 +10,19 @@ Manual ban and unban operations and the active-bans overview: from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - import aiohttp - from fastapi import APIRouter, HTTPException, Request, status from app.dependencies import ( AuthDep, DbDep, Fail2BanSocketDep, + GeoBatchLookupDep, HttpSessionDep, ) +from app.exceptions import JailNotFoundError, JailOperationError from app.models.ban import ActiveBanListResponse, BanRequest, UnbanAllResponse, UnbanRequest from app.models.jail import JailCommandResponse -from app.services import geo_service, jail_service -from app.exceptions import JailNotFoundError, JailOperationError +from app.services import jail_service from app.utils.fail2ban_client import Fail2BanConnectionError router: APIRouter = APIRouter(prefix="/api/bans", tags=["Bans"]) @@ -58,6 +54,7 @@ async def get_active_bans( db: DbDep, socket_path: Fail2BanSocketDep, http_session: HttpSessionDep, + geo_batch_lookup: GeoBatchLookupDep, ) -> ActiveBanListResponse: """Return every IP that is currently banned across all fail2ban jails. @@ -77,7 +74,7 @@ async def get_active_bans( try: return await jail_service.get_active_bans( socket_path, - geo_batch_lookup=geo_service.lookup_batch, + geo_batch_lookup=geo_batch_lookup, http_session=http_session, app_db=db, ) diff --git a/backend/app/routers/blocklist.py b/backend/app/routers/blocklist.py index 8fb53a2..04c4d4b 100644 --- a/backend/app/routers/blocklist.py +++ b/backend/app/routers/blocklist.py @@ -31,6 +31,7 @@ from app.dependencies import ( AppDep, AuthDep, Fail2BanSocketDep, + GeoBatchLookupDep, HttpSessionDep, SchedulerDep, get_db, @@ -122,6 +123,7 @@ async def run_import_now( db: DbDep, _auth: AuthDep, socket_path: Fail2BanSocketDep, + geo_batch_lookup: GeoBatchLookupDep, ) -> ImportRunResult: """Download and apply all enabled blocklist sources immediately. @@ -140,7 +142,7 @@ async def run_import_now( http_session, socket_path, geo_is_cached=geo_service.is_cached, - geo_batch_lookup=geo_service.lookup_batch, + geo_batch_lookup=geo_batch_lookup, ban_ip=jail_service.ban_ip, ) diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index 38f2802..0fa2be6 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -21,6 +21,7 @@ from app.dependencies import ( AuthDep, DbDep, Fail2BanSocketDep, + GeoBatchLookupDep, HttpSessionDep, ServerStatusDep, ) @@ -83,6 +84,7 @@ async def get_dashboard_bans( db: DbDep, socket_path: Fail2BanSocketDep, http_session: HttpSessionDep, + geo_batch_lookup: GeoBatchLookupDep, range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."), source: Literal["fail2ban", "archive"] = Query( default="fail2ban", @@ -123,7 +125,7 @@ async def get_dashboard_bans( page_size=page_size, http_session=http_session, app_db=db, - geo_batch_lookup=geo_service.lookup_batch, + geo_batch_lookup=geo_batch_lookup, origin=origin, ) @@ -138,6 +140,7 @@ async def get_bans_by_country( db: DbDep, socket_path: Fail2BanSocketDep, http_session: HttpSessionDep, + geo_batch_lookup: GeoBatchLookupDep, range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."), source: Literal["fail2ban", "archive"] = Query( default="fail2ban", @@ -175,7 +178,7 @@ async def get_bans_by_country( source=source, http_session=http_session, geo_cache_lookup=geo_service.lookup_cached_only, - geo_batch_lookup=geo_service.lookup_batch, + geo_batch_lookup=geo_batch_lookup, app_db=db, origin=origin, country_code=country_code, diff --git a/backend/app/routers/jails.py b/backend/app/routers/jails.py index 99effde..51ee3a0 100644 --- a/backend/app/routers/jails.py +++ b/backend/app/routers/jails.py @@ -27,6 +27,7 @@ from app.dependencies import ( AuthDep, DbDep, Fail2BanSocketDep, + GeoBatchLookupDep, HttpSessionDep, JailServiceDep, ) @@ -38,7 +39,7 @@ from app.models.jail import ( JailDetailResponse, JailListResponse, ) -from app.services import geo_service, jail_service +from app.services import jail_service from app.utils.fail2ban_client import Fail2BanConnectionError router: APIRouter = APIRouter(prefix="/api/jails", tags=["Jails"]) @@ -108,7 +109,7 @@ def _conflict(message: str) -> HTTPException: async def get_jails( _auth: AuthDep, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailListResponse: """Return a summary of every active fail2ban jail. @@ -123,7 +124,7 @@ async def get_jails( :class:`~app.models.jail.JailListResponse` with all active jails. """ try: - return await jail_service.list_jails(socket_path) + return await jail_service_dep.list_jails(socket_path) except Fail2BanConnectionError as exc: raise _bad_gateway(exc) from exc @@ -137,7 +138,7 @@ async def get_jail( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailDetailResponse: """Return the complete configuration and runtime state for one jail. @@ -157,7 +158,7 @@ async def get_jail( HTTPException: 502 when fail2ban is unreachable. """ try: - return await jail_service.get_jail(socket_path, name) + return await jail_service_dep.get_jail(socket_path, name) except JailNotFoundError: raise _not_found(name) from None except Fail2BanConnectionError as exc: @@ -177,7 +178,7 @@ async def get_jail( async def reload_all_jails( _auth: AuthDep, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Reload every fail2ban jail to apply configuration changes. @@ -195,7 +196,7 @@ async def reload_all_jails( HTTPException: 409 when fail2ban reports the operation failed. """ try: - await jail_service.reload_all(socket_path) + await jail_service_dep.reload_all(socket_path) return JailCommandResponse(message="All jails reloaded successfully.", jail="*") except JailOperationError as exc: raise _conflict(str(exc)) from exc @@ -212,7 +213,7 @@ async def start_jail( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Start a fail2ban jail that is currently stopped. @@ -229,7 +230,7 @@ async def start_jail( HTTPException: 502 when fail2ban is unreachable. """ try: - await jail_service.start_jail(socket_path, name) + await jail_service_dep.start_jail(socket_path, name) return JailCommandResponse(message=f"Jail {name!r} started.", jail=name) except JailNotFoundError: raise _not_found(name) from None @@ -248,7 +249,7 @@ async def stop_jail( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Stop a running fail2ban jail. @@ -268,7 +269,7 @@ async def stop_jail( HTTPException: 502 when fail2ban is unreachable. """ try: - await jail_service.stop_jail(socket_path, name) + await jail_service_dep.stop_jail(socket_path, name) return JailCommandResponse(message=f"Jail {name!r} stopped.", jail=name) except JailOperationError as exc: raise _conflict(str(exc)) from exc @@ -285,7 +286,7 @@ async def toggle_idle( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, on: bool = Body(..., description="``true`` to enable idle, ``false`` to disable."), ) -> JailCommandResponse: """Enable or disable idle mode for a fail2ban jail. @@ -308,7 +309,7 @@ async def toggle_idle( """ state_str = "on" if on else "off" try: - await jail_service.set_idle(socket_path, name, on=on) + await jail_service_dep.set_idle(socket_path, name, on=on) return JailCommandResponse( message=f"Jail {name!r} idle mode turned {state_str}.", jail=name, @@ -330,7 +331,7 @@ async def reload_jail( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Reload a single fail2ban jail to pick up configuration changes. @@ -347,7 +348,7 @@ async def reload_jail( HTTPException: 502 when fail2ban is unreachable. """ try: - await jail_service.reload_jail(socket_path, name) + await jail_service_dep.reload_jail(socket_path, name) return JailCommandResponse(message=f"Jail {name!r} reloaded.", jail=name) except JailNotFoundError: raise _not_found(name) from None @@ -379,7 +380,7 @@ async def get_ignore_list( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> list[str]: """Return the current ignore list (IP whitelist) for a fail2ban jail. @@ -395,7 +396,7 @@ async def get_ignore_list( HTTPException: 502 when fail2ban is unreachable. """ try: - return await jail_service.get_ignore_list(socket_path, name) + return await jail_service_dep.get_ignore_list(socket_path, name) except JailNotFoundError: raise _not_found(name) from None except Fail2BanConnectionError as exc: @@ -413,7 +414,7 @@ async def add_ignore_ip( name: _NamePath, body: IgnoreIpRequest, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Add an IP address or CIDR network to a jail's ignore list. @@ -435,7 +436,7 @@ async def add_ignore_ip( HTTPException: 502 when fail2ban is unreachable. """ try: - await jail_service.add_ignore_ip(socket_path, name, body.ip) + await jail_service_dep.add_ignore_ip(socket_path, name, body.ip) return JailCommandResponse( message=f"IP {body.ip!r} added to ignore list of jail {name!r}.", jail=name, @@ -463,7 +464,7 @@ async def del_ignore_ip( name: _NamePath, body: IgnoreIpRequest, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, ) -> JailCommandResponse: """Remove an IP address or CIDR network from a jail's ignore list. @@ -481,7 +482,7 @@ async def del_ignore_ip( HTTPException: 502 when fail2ban is unreachable. """ try: - await jail_service.del_ignore_ip(socket_path, name, body.ip) + await jail_service_dep.del_ignore_ip(socket_path, name, body.ip) return JailCommandResponse( message=f"IP {body.ip!r} removed from ignore list of jail {name!r}.", jail=name, @@ -503,7 +504,7 @@ async def toggle_ignore_self( _auth: AuthDep, name: _NamePath, socket_path: Fail2BanSocketDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, on: bool = Body(..., description="``true`` to enable ignoreself, ``false`` to disable."), ) -> JailCommandResponse: """Toggle the ``ignoreself`` flag for a fail2ban jail. @@ -526,7 +527,7 @@ async def toggle_ignore_self( """ state_str = "enabled" if on else "disabled" try: - await jail_service.set_ignore_self(socket_path, name, on=on) + await jail_service_dep.set_ignore_self(socket_path, name, on=on) return JailCommandResponse( message=f"ignoreself {state_str} for jail {name!r}.", jail=name, @@ -555,7 +556,8 @@ async def get_jail_banned_ips( name: _NamePath, socket_path: Fail2BanSocketDep, http_session: HttpSessionDep, - jail_service: JailServiceDep, + jail_service_dep: JailServiceDep, + geo_batch_lookup: GeoBatchLookupDep, page: int = 1, page_size: int = 25, search: str | None = None, @@ -593,13 +595,13 @@ async def get_jail_banned_ips( ) try: - return await jail_service.get_jail_banned_ips( + return await jail_service_dep.get_jail_banned_ips( socket_path=socket_path, jail_name=name, page=page, page_size=page_size, search=search, - geo_batch_lookup=geo_service.lookup_batch, + geo_batch_lookup=geo_batch_lookup, http_session=http_session, app_db=db, )