# [DEF:Dependencies:Module] # @SEMANTICS: dependency, injection, singleton, factory, auth, jwt # @PURPOSE: Manages creation and provision of shared application dependencies, such as PluginLoader and TaskManager, to avoid circular imports. # @LAYER: Core # @RELATION: Used by main app and API routers to get access to shared instances. from pathlib import Path from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError from .core.plugin_loader import PluginLoader from .core.task_manager import TaskManager from .core.config_manager import ConfigManager from .core.scheduler import SchedulerService from .services.resource_service import ResourceService from .services.mapping_service import MappingService from .core.database import init_db, get_auth_db from .core.logger import logger from .core.auth.jwt import decode_token from .core.auth.repository import AuthRepository from .models.auth import User # Initialize singletons # Use absolute path relative to this file to ensure plugins are found regardless of CWD project_root = Path(__file__).parent.parent.parent config_path = project_root / "config.json" # Initialize database before services that use persisted configuration. init_db() config_manager = ConfigManager(config_path=str(config_path)) # [DEF:get_config_manager:Function] # @PURPOSE: Dependency injector for ConfigManager. # @PRE: Global config_manager must be initialized. # @POST: Returns shared ConfigManager instance. # @RETURN: ConfigManager - The shared config manager instance. def get_config_manager() -> ConfigManager: """Dependency injector for ConfigManager.""" return config_manager # [/DEF:get_config_manager:Function] plugin_dir = Path(__file__).parent / "plugins" plugin_loader = PluginLoader(plugin_dir=str(plugin_dir)) logger.info(f"PluginLoader initialized with directory: {plugin_dir}") logger.info(f"Available plugins: {[config.name for config in plugin_loader.get_all_plugin_configs()]}") task_manager = TaskManager(plugin_loader) logger.info("TaskManager initialized") scheduler_service = SchedulerService(task_manager, config_manager) logger.info("SchedulerService initialized") resource_service = ResourceService() logger.info("ResourceService initialized") # [DEF:get_plugin_loader:Function] # @PURPOSE: Dependency injector for PluginLoader. # @PRE: Global plugin_loader must be initialized. # @POST: Returns shared PluginLoader instance. # @RETURN: PluginLoader - The shared plugin loader instance. def get_plugin_loader() -> PluginLoader: """Dependency injector for PluginLoader.""" return plugin_loader # [/DEF:get_plugin_loader:Function] # [DEF:get_task_manager:Function] # @PURPOSE: Dependency injector for TaskManager. # @PRE: Global task_manager must be initialized. # @POST: Returns shared TaskManager instance. # @RETURN: TaskManager - The shared task manager instance. def get_task_manager() -> TaskManager: """Dependency injector for TaskManager.""" return task_manager # [/DEF:get_task_manager:Function] # [DEF:get_scheduler_service:Function] # @PURPOSE: Dependency injector for SchedulerService. # @PRE: Global scheduler_service must be initialized. # @POST: Returns shared SchedulerService instance. # @RETURN: SchedulerService - The shared scheduler service instance. def get_scheduler_service() -> SchedulerService: """Dependency injector for SchedulerService.""" return scheduler_service # [/DEF:get_scheduler_service:Function] # [DEF:get_resource_service:Function] # @PURPOSE: Dependency injector for ResourceService. # @PRE: Global resource_service must be initialized. # @POST: Returns shared ResourceService instance. # @RETURN: ResourceService - The shared resource service instance. def get_resource_service() -> ResourceService: """Dependency injector for ResourceService.""" return resource_service # [/DEF:get_resource_service:Function] # [DEF:get_mapping_service:Function] # @PURPOSE: Dependency injector for MappingService. # @PRE: Global config_manager must be initialized. # @POST: Returns new MappingService instance. # @RETURN: MappingService - A new mapping service instance. def get_mapping_service() -> MappingService: """Dependency injector for MappingService.""" return MappingService(config_manager) # [/DEF:get_mapping_service:Function] # [DEF:oauth2_scheme:Variable] # @PURPOSE: OAuth2 password bearer scheme for token extraction. oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") # [/DEF:oauth2_scheme:Variable] # [DEF:get_current_user:Function] # @PURPOSE: Dependency for retrieving currently authenticated user from a JWT. # @PRE: JWT token provided in Authorization header. # @POST: Returns User object if token is valid. # @THROW: HTTPException 401 if token is invalid or user not found. # @PARAM: token (str) - Extracted JWT token. # @PARAM: db (Session) - Auth database session. # @RETURN: User - The authenticated user. def get_current_user(token: str = Depends(oauth2_scheme), db = Depends(get_auth_db)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = decode_token(token) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception repo = AuthRepository(db) user = repo.get_user_by_username(username) if user is None: raise credentials_exception return user # [/DEF:get_current_user:Function] # [DEF:has_permission:Function] # @PURPOSE: Dependency for checking if the current user has a specific permission. # @PRE: User is authenticated. # @POST: Returns True if user has permission. # @THROW: HTTPException 403 if permission is denied. # @PARAM: resource (str) - The resource identifier. # @PARAM: action (str) - The action identifier (READ, EXECUTE, WRITE). # @RETURN: User - The authenticated user if permission granted. def has_permission(resource: str, action: str): def permission_checker(current_user: User = Depends(get_current_user)): # Union of all permissions across all roles for role in current_user.roles: for perm in role.permissions: if perm.resource == resource and perm.action == action: return current_user # Special case for Admin role (full access) if any(role.name == "Admin" for role in current_user.roles): return current_user from .core.auth.logger import log_security_event log_security_event("PERMISSION_DENIED", current_user.username, {"resource": resource, "action": action}) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Permission denied for {resource}:{action}" ) return permission_checker # [/DEF:has_permission:Function] # [/DEF:Dependencies:Module]