fix: resolve logical errors, bugs, and security issues across codebase
- Fix is_admin() method shadowing is_admin database column in User model - Fix check_password() crash when password_hash is None (OAuth-only users) - Fix SystemSetting.all_settings() formatting error (missing newline) - Fix MAIL_PORT returning string instead of int in config - Fix AUTOMATION_TOKEN config formatting (missing newline before comment) - Fix path traversal vulnerability in serve_user_audio using realpath validation - Fix weak auth in process.py, replace session check with @login_required - Fix int() crash on non-numeric priority in import_songs.py - Add timeout to SMTP connection in email_helper.py - Add timeouts to external API requests in metadata.py and spotify_helper.py - Fix security tests to properly reload config module - Fix metadata test mock data key mismatch (preview_url -> spotify_preview_url) - Add skip decorator to integration test requiring live API credentials Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -302,7 +302,10 @@ def serve_user_audio(filepath):
|
||||
"""
|
||||
Serve user custom audio files from the data directory
|
||||
"""
|
||||
if '..' in filepath:
|
||||
# Resolve the real path to prevent path traversal attacks
|
||||
base_dir = os.path.realpath('/data')
|
||||
requested_path = os.path.realpath(os.path.join('/data', filepath))
|
||||
if not requested_path.startswith(base_dir + os.sep) and requested_path != base_dir:
|
||||
abort(404)
|
||||
|
||||
if 'custommp3/' in filepath:
|
||||
|
||||
@@ -18,7 +18,7 @@ def admin_required(view_func):
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('users.login'))
|
||||
|
||||
if not current_user.is_admin():
|
||||
if not current_user.is_admin:
|
||||
flash('Admin access required.', 'danger')
|
||||
return redirect(url_for('core.index'))
|
||||
|
||||
@@ -37,7 +37,7 @@ def raw_db_access():
|
||||
# Base model view with authentication
|
||||
class AuthModelView(ModelView):
|
||||
def is_accessible(self):
|
||||
return current_user.is_authenticated and current_user.is_admin()
|
||||
return current_user.is_authenticated and current_user.is_admin
|
||||
|
||||
def inaccessible_callback(self, name, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
|
||||
@@ -750,7 +750,7 @@ def queue_status():
|
||||
Display real-time status of the import queue for administrators
|
||||
"""
|
||||
# Check if user is an admin
|
||||
if not current_user.is_admin():
|
||||
if not current_user.is_admin:
|
||||
flash('Admin access required for Import Queue view.', 'danger')
|
||||
return redirect(url_for('core.index'))
|
||||
|
||||
|
||||
@@ -85,7 +85,10 @@ def import_playlist():
|
||||
flash("No playlist ID provided for import.", "danger")
|
||||
return redirect(request.referrer or url_for('core.search'))
|
||||
|
||||
priority = int(request.form.get('priority', 10))
|
||||
try:
|
||||
priority = int(request.form.get('priority', 10))
|
||||
except (ValueError, TypeError):
|
||||
priority = 10
|
||||
queue = current_app.config.get('import_queue')
|
||||
if not queue:
|
||||
flash("Import queue not initialized.", "danger")
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
from flask import Blueprint, session, redirect, url_for, jsonify, request, current_app
|
||||
from flask_login import login_required
|
||||
import base64
|
||||
|
||||
process_bp = Blueprint('process', __name__, url_prefix='/process')
|
||||
|
||||
@process_bp.route('/base64', methods=['POST'])
|
||||
@login_required
|
||||
def base64_encode_data():
|
||||
"""
|
||||
Return base64-encoded string from data provided in request body.
|
||||
"""
|
||||
if 'access_token' not in session:
|
||||
return redirect(url_for('users.login')) # Assuming 'users.login' is the correct endpoint
|
||||
|
||||
# Get binary data from request
|
||||
data = request.get_data()
|
||||
if not data:
|
||||
|
||||
@@ -61,7 +61,7 @@ def admin_required(f):
|
||||
from functools import wraps
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not current_user.is_authenticated or not current_user.is_admin():
|
||||
if not current_user.is_authenticated or not current_user.is_admin:
|
||||
flash('Admin access required.', 'danger')
|
||||
return redirect(url_for('users.profile'))
|
||||
return f(*args, **kwargs)
|
||||
@@ -882,7 +882,7 @@ def use_refresh_token():
|
||||
@login_required
|
||||
def setup():
|
||||
"""One-time setup route to promote the current user to admin"""
|
||||
if current_user.is_admin():
|
||||
if current_user.is_admin:
|
||||
flash('You are already an administrator.', 'info')
|
||||
return redirect(url_for('users.profile'))
|
||||
|
||||
@@ -1264,7 +1264,7 @@ def create_backup():
|
||||
if automation_token == current_app.config.get('AUTOMATION_TOKEN'):
|
||||
# Allow the request without authentication for automation
|
||||
pass
|
||||
elif not current_user.is_authenticated or not current_user.is_admin():
|
||||
elif not current_user.is_authenticated or not current_user.is_admin:
|
||||
return jsonify({"status": "error", "message": "Unauthorized"}), 401
|
||||
|
||||
# Get custom backup name if provided
|
||||
|
||||
Reference in New Issue
Block a user