Fix security issues.

This commit is contained in:
2025-08-04 13:45:13 +02:00
committed by Jens Luedicke
parent f98e8f3e71
commit 64b8c3fccb
7 changed files with 1100 additions and 174 deletions

View File

@@ -8,8 +8,9 @@ from sqlalchemy import and_, or_
# Local application imports
from models import (Note, NoteFolder, NoteLink, NoteVisibility, Project,
Task, db)
Task, UserPreferences, db)
from routes.auth import company_required, login_required
from security_utils import sanitize_folder_path, validate_folder_access
# Create blueprint
notes_bp = Blueprint('notes', __name__, url_prefix='/notes')
@@ -30,6 +31,16 @@ def notes_list():
visibility_filter = request.args.get('visibility', '')
search_query = request.args.get('search', '')
# Sanitize folder filter if provided
if folder_filter:
try:
folder_filter = sanitize_folder_path(folder_filter)
# Validate folder exists
if not validate_folder_access(folder_filter, g.user.company_id, db.session):
folder_filter = '' # Reset to root if invalid
except ValueError:
folder_filter = '' # Reset to root if invalid
# Base query - only non-archived notes for the user's company
query = Note.query.filter_by(
company_id=g.user.company_id,
@@ -197,6 +208,41 @@ def create_note():
task_id = request.form.get('task_id')
is_pinned = request.form.get('is_pinned') == '1'
# Sanitize and validate folder if provided
if folder:
try:
folder = sanitize_folder_path(folder)
# Ensure folder exists or create it
if not validate_folder_access(folder, g.user.company_id, db.session):
# Create folder hierarchy if it doesn't exist
folder_parts = folder.split('/')
current_path = ''
for i, part in enumerate(folder_parts):
if i == 0:
current_path = part
else:
current_path = current_path + '/' + part
existing = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=current_path
).first()
if not existing:
parent_path = '/'.join(folder_parts[:i]) if i > 0 else None
new_folder = NoteFolder(
name=part,
path=current_path,
parent_path=parent_path,
company_id=g.user.company_id,
created_by_id=g.user.id
)
db.session.add(new_folder)
db.session.flush() # Ensure folder is created before continuing
except ValueError as e:
flash(f'Invalid folder path: {str(e)}', 'error')
return redirect(url_for('notes.create_note'))
# Validate
if not title:
flash('Title is required', 'error')
@@ -366,6 +412,18 @@ def edit_note(slug):
task_id = request.form.get('task_id')
is_pinned = request.form.get('is_pinned') == '1'
# Sanitize and validate folder if provided
if folder:
try:
folder = sanitize_folder_path(folder)
# Validate folder exists
if not validate_folder_access(folder, g.user.company_id, db.session):
flash('Invalid folder selected', 'error')
return redirect(url_for('notes.edit_note', slug=slug))
except ValueError as e:
flash(f'Invalid folder path: {str(e)}', 'error')
return redirect(url_for('notes.edit_note', slug=slug))
# Validate
if not title:
flash('Title is required', 'error')
@@ -495,4 +553,30 @@ def notes_folders():
folders=folders,
folder_tree=folder_tree,
folder_counts=folder_counts,
title='Manage Folders')
title='Manage Folders')
@notes_bp.route('/preferences', methods=['POST'])
@login_required
@company_required
def update_note_preferences():
"""Update note-related user preferences"""
note_preview_font = request.form.get('note_preview_font', 'system')
# Get or create user preferences
preferences = UserPreferences.query.filter_by(user_id=g.user.id).first()
if not preferences:
preferences = UserPreferences(user_id=g.user.id)
db.session.add(preferences)
# Update preferences
preferences.note_preview_font = note_preview_font
db.session.commit()
# Return JSON response for AJAX calls
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({'success': True, 'font': note_preview_font})
# Otherwise redirect back
return redirect(request.referrer or url_for('notes.notes_list'))