from flask import Blueprint, request, Response, jsonify from flask_jwt_extended import get_jwt_identity import csv from models import SystemLog, User from app import db from auth import jwt_required_with_logging, get_current_user_safe from api.badges import is_admin_user from logging_utils import log_event, parse_rfc5424_log syslog_bp = Blueprint('syslog', __name__) @syslog_bp.route('/syslog', methods=['POST']) @jwt_required_with_logging def receive_syslog(): """Receive RFC 5424 syslog messages - requires JWT authentication per OpenAPI spec""" try: content_type = request.content_type or 'text/plain' if content_type.startswith('application/json'): # Handle JSON format data = request.get_json() if not data: return jsonify({'error': 'JSON data required'}), 400 # Extract log messages from JSON message = data.get('syslog_message', []) if not message: return jsonify({'error': 'syslog_message field required'}), 400 processed_count = 0 try: # Parse RFC 5424 message parsed = parse_rfc5424_log(message) # Create log entry log_entry = SystemLog( facility=parsed['facility'], severity=parsed['severity'], version=parsed['version'], timestamp=parsed['timestamp'], hostname=parsed['hostname'], app_name=parsed['app_name'], proc_id=parsed['proc_id'], msg_id=parsed['msg_id'], structured_data=parsed['structured_data'], message=parsed['message'], user_id=int(get_jwt_identity()), source_ip=request.remote_addr, event_type='syslog_received' ) db.session.add(log_entry) processed_count += 1 except Exception as e: # Log parsing error but continue processing log_event( severity=4, message=f"Failed to parse syslog message: {str(e)}", event_type="syslog_parse_error", user_id=int(get_jwt_identity()) ) db.session.commit() return jsonify({ 'message': f'Processed {processed_count} log messages', 'processed_count': processed_count }) else: # Handle raw text format (single RFC 5424 message) message = request.get_data(as_text=True).strip() if not message: return jsonify({'error': 'Log message required'}), 400 try: # Parse RFC 5424 message parsed = parse_rfc5424_log(message) # Create log entry log_entry = SystemLog( facility=parsed['facility'], severity=parsed['severity'], version=parsed['version'], timestamp=parsed['timestamp'], hostname=parsed['hostname'], app_name=parsed['app_name'], proc_id=parsed['proc_id'], msg_id=parsed['msg_id'], structured_data=parsed['structured_data'], message=parsed['message'], user_id=int(get_jwt_identity()), source_ip=request.remote_addr, event_type='syslog_received' ) db.session.add(log_entry) db.session.commit() return jsonify({ 'message': 'Log message processed successfully' }) except Exception as e: log_event( severity=4, message=f"Failed to process syslog message: {str(e)}", event_type="syslog_error", user_id=int(get_jwt_identity()) ) return jsonify({'error': 'Failed to process log message'}), 500 except Exception as e: try: user_id = int(get_jwt_identity()) except: user_id = None log_event( severity=3, message=f"Syslog endpoint error: {str(e)}", event_type="syslog_endpoint_error", user_id=user_id ) return jsonify({'error': 'Internal server error'}), 500 @syslog_bp.route('/logs', methods=['GET']) @jwt_required_with_logging def get_logs(): """Get system logs with filtering""" try: # Get query parameters page = int(request.args.get('page', 1)) per_page = min(int(request.args.get('per_page', 50)), 100) severity = request.args.get('severity') app_name = request.args.get('app_name') event_type = request.args.get('event_type') hostname = request.args.get('hostname') search = request.args.get('search') # Build query query = SystemLog.query # Get current user current_user = get_current_user_safe() # Filter by user_id unless user is admin if current_user: # Check if user is admin using consistent helper function is_admin = is_admin_user(current_user) if not is_admin: # Only show logs for this user query = query.filter(SystemLog.user_id == current_user.id) # Apply filters if severity is not None: query = query.filter(SystemLog.severity == int(severity)) if app_name: query = query.filter(SystemLog.app_name == app_name) if event_type: query = query.filter(SystemLog.event_type == event_type) if hostname: query = query.filter(SystemLog.hostname == hostname) if search: query = query.filter(SystemLog.message.contains(search)) # Order by timestamp descending query = query.order_by(SystemLog.timestamp.desc()) # Get paginated results pagination = query.paginate( page=page, per_page=per_page, error_out=False ) logs = [] for log in pagination.items: log_dict = log.to_dict() # Add username if user_id exists if log.user_id: from models import User user = User.query.get(log.user_id) log_dict["username"] = user.username if user else None else: log_dict["username"] = None logs.append(log_dict) return jsonify({ 'logs': logs, 'total': pagination.total, 'pages': pagination.pages, 'current_page': page, 'per_page': per_page }) except Exception as e: log_event( severity=3, message=f"Failed to get logs: {str(e)}", event_type="get_logs_error", user_id=get_current_user_safe().id if get_current_user_safe() else None ) return jsonify({'error': str(e)}), 500 @syslog_bp.route('/logs/stats', methods=['GET']) @jwt_required_with_logging def get_log_stats(): """Get log statistics""" try: # Get current user current_user = get_current_user_safe() # Base query base_query = SystemLog.query # Filter by user_id unless user is admin if current_user: # Check if user is admin using consistent helper function is_admin = is_admin_user(current_user) if not is_admin: # Only show logs for this user base_query = base_query.filter(SystemLog.user_id == current_user.id) # Get log counts by severity severity_stats = db.session.query( SystemLog.severity, db.func.count(SystemLog.id).label('count') ) # Apply user filter if not admin if current_user and not is_admin_user(current_user): severity_stats = severity_stats.filter(SystemLog.user_id == current_user.id) severity_stats = severity_stats.group_by(SystemLog.severity).all() # Get log counts by event type event_stats = db.session.query( SystemLog.event_type, db.func.count(SystemLog.id).label('count') ) # Apply user filter if not admin if current_user and not is_admin_user(current_user): event_stats = event_stats.filter(SystemLog.user_id == current_user.id) event_stats = event_stats.group_by(SystemLog.event_type).limit(10).all() # Get log counts by app name app_stats = db.session.query( SystemLog.app_name, db.func.count(SystemLog.id).label('count') ) # Apply user filter if not admin if current_user and not is_admin_user(current_user): app_stats = app_stats.filter(SystemLog.user_id == current_user.id) app_stats = app_stats.group_by(SystemLog.app_name).limit(10).all() # Total logs total_logs = base_query.count() return jsonify({ 'total_logs': total_logs, 'severity_stats': [{'severity': s[0], 'count': s[1]} for s in severity_stats], 'event_stats': [{'event_type': e[0], 'count': e[1]} for e in event_stats], 'app_stats': [{'app_name': a[0], 'count': a[1]} for a in app_stats] }) except Exception as e: log_event( severity=3, message=f"Failed to get log stats: {str(e)}", event_type="get_stats_error", user_id=get_current_user_safe().id if get_current_user_safe() else None ) return jsonify({'error': str(e)}), 500 @syslog_bp.route('/logs/csv', methods=['GET']) @jwt_required_with_logging def download_logs_csv(): """Download all filtered logs as CSV (ignores paging)""" try: severity = request.args.get('severity') app_name = request.args.get('app_name') event_type = request.args.get('event_type') hostname = request.args.get('hostname') search = request.args.get('search') query = SystemLog.query current_user = get_current_user_safe() if current_user: is_admin = is_admin_user(current_user) if not is_admin: query = query.filter(SystemLog.user_id == current_user.id) if severity is not None: query = query.filter(SystemLog.severity == int(severity)) if app_name: query = query.filter(SystemLog.app_name == app_name) if event_type: query = query.filter(SystemLog.event_type == event_type) if hostname: query = query.filter(SystemLog.hostname == hostname) if search: query = query.filter(SystemLog.message.contains(search)) logs = query.order_by(SystemLog.timestamp.desc()).all() # Prepare CSV output = csv.StringIO() if hasattr(csv, 'StringIO') else __import__('io').StringIO() writer = csv.writer(output) headers = [ 'Timestamp', 'Severity', 'Event Type', 'App Name', 'Hostname', 'Message', 'User', 'Source IP' ] writer.writerow(headers) for log in logs: username = None if log.user_id: user = User.query.get(log.user_id) username = user.username if user else None writer.writerow([ log.timestamp.isoformat() if log.timestamp else '', log.severity, log.event_type, log.app_name, log.hostname, log.message, username or '', log.source_ip or '' ]) output.seek(0) return Response( output.getvalue(), mimetype='text/csv', headers={ 'Content-Disposition': 'attachment; filename="logs.csv"' } ) except Exception as e: log_event( severity=3, message=f"Failed to download logs CSV: {str(e)}", event_type="download_logs_csv_error", user_id=get_current_user_safe().id if get_current_user_safe() else None ) return jsonify({'error': str(e)}), 500