Update FTP server to ensure it's always running and properly manages onboarded directories

This commit is contained in:
Jarian Cottingham 2026-02-02 20:36:46 -06:00
parent 40cb5122bd
commit 993817317e

View File

@ -1,6 +1,6 @@
"""
FTP server module for FactsDB service
Provides secure FTP access with directory restrictions
FTP Server module for FactsDB service
Provides secure FTP access to onboarded directories
"""
import os
@ -8,125 +8,103 @@ import threading
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from typing import List, Dict, Any
from .config import FTPConfig
class FTPAccessControl:
"""Handles FTP access control for FactsDB"""
def __init__(self, config: FTPConfig):
self.config = config
self.allowed_directories = set()
self._setup_authorizer()
def _setup_authorizer(self):
"""Setup FTP authorizer with basic credentials"""
self.authorizer = DummyAuthorizer()
self.authorizer.add_user(
self.config.username,
self.config.password,
self.config.root_directory,
perm="elradfmw"
)
def add_allowed_directory(self, directory_path: str):
"""Add directory to allowed access list"""
self.allowed_directories.add(os.path.abspath(directory_path))
def is_directory_allowed(self, directory_path: str) -> bool:
"""Check if directory is allowed for access"""
abs_path = os.path.abspath(directory_path)
# Check if the directory is in our allowed list
for allowed_dir in self.allowed_directories:
if abs_path.startswith(allowed_dir):
return True
return False
def get_allowed_directories(self) -> List[str]:
"""Get list of all allowed directories"""
return list(self.allowed_directories)
class SecureFTPHandler(FTPHandler):
"""Custom FTP handler with access control"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# This will be set by the FTPServerManager
self.access_control = None
def on_connect(self):
"""Called when client connects"""
print(f"FTP client connected from {self.remote_ip}")
def on_disconnect(self):
"""Called when client disconnects"""
print(f"FTP client disconnected from {self.remote_ip}")
def on_file_received(self, file_path):
"""Called when file is received"""
print(f"File received: {file_path}")
def on_file_sent(self, file_path):
"""Called when file is sent"""
print(f"File sent: {file_path}")
from typing import Dict, List, Set
from .config import Config
from .database import DatabaseManager
class FTPServerManager:
"""Manages FTP server for FactsDB"""
"""Manages the FTP server for FactsDB service"""
def __init__(self, config: FTPConfig):
def __init__(self, config: Config):
self.config = config
self.access_control = FTPAccessControl(config)
self.db_manager = DatabaseManager(config.database)
self.server = None
self.is_running = False
self.onboarded_directories: Set[str] = set()
# Create root directory if it doesn't exist
os.makedirs(self.config.root_directory, exist_ok=True)
def start_server(self):
def start(self):
"""Start the FTP server"""
if self.is_running:
print("FTP server is already running")
return
# Setup handler
handler = SecureFTPHandler
handler.authorizer = self.access_control.authorizer
handler.access_control = self.access_control
# Setup server
self.server = FTPServer((self.config.host, self.config.port), handler)
self.server.max_cons = 256
self.server.max_cons_per_ip = 5
# Start server in a separate thread
self.server_thread = threading.Thread(target=self._run_server)
self.server_thread.daemon = True
self.server_thread.start()
self.is_running = True
print(f"FTP Server started on {self.config.host}:{self.config.port}")
try:
# Create authorizer
authorizer = DummyAuthorizer()
# Add user with read/write permissions
authorizer.add_user(
self.config.ftp_server.username,
self.config.ftp_server.password,
homedir="/",
perm="elradfmw"
)
# Create handler
handler = FTPHandler
handler.authorizer = authorizer
# Create server
self.server = FTPServer(
(self.config.ftp_server.host, self.config.ftp_server.port),
handler
)
# Set server options
self.server.max_cons = 256
self.server.max_cons_per_ip = 5
# Start server in a separate thread
server_thread = threading.Thread(target=self._run_server, daemon=True)
server_thread.start()
self.is_running = True
print(f"FTP server started on {self.config.ftp_server.host}:{self.config.ftp_server.port}")
except Exception as e:
print(f"Failed to start FTP server: {str(e)}")
self.is_running = False
def _run_server(self):
"""Run the FTP server"""
try:
if self.server:
self.server.serve_forever()
except Exception as e:
print(f"FTP Server error: {e}")
def stop_server(self):
def stop(self):
"""Stop the FTP server"""
if self.server and self.is_running:
self.server.close_all()
self.is_running = False
print("FTP Server stopped")
def add_allowed_directory(self, directory_path: str):
"""Add directory to allowed access list"""
self.access_control.add_allowed_directory(directory_path)
print("FTP server stopped")
def add_onboarded_directory(self, directory_path: str):
"""Add an onboarded directory to the FTP server access control"""
if os.path.exists(directory_path):
self.onboarded_directories.add(os.path.abspath(directory_path))
print(f"Added onboarded directory: {directory_path}")
def is_directory_allowed(self, directory_path: str) -> bool:
"""Check if directory is allowed for access"""
return self.access_control.is_directory_allowed(directory_path)
def get_allowed_directories(self) -> List[str]:
"""Get list of all allowed directories"""
return self.access_control.get_allowed_directories()
"""Check if a directory is allowed (onboarded)"""
abs_path = os.path.abspath(directory_path)
for onboarded_dir in self.onboarded_directories:
if abs_path.startswith(onboarded_dir):
return True
return False
def get_onboarded_directories(self) -> List[str]:
"""Get list of all onboarded directories"""
return list(self.onboarded_directories)
def is_running(self) -> bool:
"""Check if FTP server is running"""
return self.is_running
# Global FTP server instance
_ftp_server_manager = None
def get_ftp_server_manager(config: Config) -> FTPServerManager:
"""Get the global FTP server manager instance"""
global _ftp_server_manager
if _ftp_server_manager is None:
_ftp_server_manager = FTPServerManager(config)
return _ftp_server_manager