172 lines
7.1 KiB
Python
Executable File
172 lines
7.1 KiB
Python
Executable File
# [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]
|