04/06 remediate some issues
This commit is contained in:
+16
-4
@@ -9,7 +9,7 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request,
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.utils import secure_filename
|
||||
import bleach
|
||||
from app import db
|
||||
from app import db, limiter
|
||||
from app.models import (User, Ticket, Comment, ActivityLog, KnowledgeBase,
|
||||
KBAttachment, UserRole, TicketStatus)
|
||||
from app.services.log_service import log_action
|
||||
@@ -404,8 +404,10 @@ def all_tickets():
|
||||
|
||||
if search:
|
||||
from app.models import Comment
|
||||
from sqlalchemy import func
|
||||
submitter_alias = db.aliased(User)
|
||||
assignee_alias = db.aliased(User)
|
||||
stripped_body = func.regexp_replace(Comment.body, r'<[^>]+>', '', 'g')
|
||||
q = (
|
||||
q
|
||||
.outerjoin(submitter_alias, submitter_alias.id == Ticket.created_by_id)
|
||||
@@ -417,7 +419,7 @@ def all_tickets():
|
||||
Ticket.description.ilike(f'%{search}%') |
|
||||
submitter_alias.full_name.ilike(f'%{search}%') |
|
||||
assignee_alias.full_name.ilike(f'%{search}%') |
|
||||
Comment.body.ilike(f'%{search}%')
|
||||
stripped_body.ilike(f'%{search}%')
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
@@ -540,6 +542,7 @@ def kb_upload_image():
|
||||
# ── File-serve route (images embedded in articles + attachment downloads) ─────
|
||||
|
||||
@admin_bp.route('/kb/files/<string:stored_name>')
|
||||
@login_required
|
||||
def kb_serve_file(stored_name):
|
||||
"""Serve a KB attachment file. Login required — no public access.
|
||||
|
||||
@@ -551,6 +554,12 @@ def kb_serve_file(stored_name):
|
||||
traverse outside the upload directory. <string:> disallows slashes,
|
||||
restricting the value to a flat filename — matching the UUID-based
|
||||
stored_name format (e.g. 'a1b2c3d4e5f6....png') used by all upload helpers.
|
||||
|
||||
@login_required is applied here because the upload directory is shared
|
||||
across KB files, ticket attachments, comment images, and user avatars.
|
||||
Without authentication, an unauthenticated caller who knows or guesses
|
||||
any stored_name (UUID-based) could retrieve arbitrary files from the
|
||||
shared uploads folder — including confidential ticket attachments.
|
||||
"""
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
return send_from_directory(upload_dir, stored_name)
|
||||
@@ -572,7 +581,6 @@ def kb_delete_attachment(article_id, att_id):
|
||||
logger.info(f'[KB ATTACHMENT DELETE] att_id={att.id} article_id={article_id} by user_id={current_user.id}')
|
||||
db.session.delete(att)
|
||||
db.session.commit()
|
||||
logger.info(f'[KB ATTACHMENT DELETE] att_id={att.id} article_id={article_id} completed')
|
||||
# Return JSON so the edit page can remove the row without a full reload
|
||||
return jsonify({'ok': True, 'att_id': att.id})
|
||||
|
||||
@@ -766,6 +774,7 @@ def activity_logs():
|
||||
@admin_bp.route('/tickets/export')
|
||||
@login_required
|
||||
@admin_required
|
||||
@limiter.limit('10 per hour')
|
||||
def export_tickets():
|
||||
"""Stream a CSV of tickets matching the current filter params."""
|
||||
status = request.args.get('status', '')
|
||||
@@ -780,8 +789,10 @@ def export_tickets():
|
||||
elif assigned == 'unassigned': q = q.filter_by(assigned_to_id=None)
|
||||
|
||||
if search:
|
||||
from sqlalchemy import func
|
||||
submitter_alias = db.aliased(User)
|
||||
assignee_alias = db.aliased(User)
|
||||
stripped_body = func.regexp_replace(Comment.body, r'<[^>]+>', '', 'g')
|
||||
q = (
|
||||
q
|
||||
.outerjoin(submitter_alias, submitter_alias.id == Ticket.created_by_id)
|
||||
@@ -793,7 +804,7 @@ def export_tickets():
|
||||
Ticket.description.ilike(f'%{search}%') |
|
||||
submitter_alias.full_name.ilike(f'%{search}%') |
|
||||
assignee_alias.full_name.ilike(f'%{search}%') |
|
||||
Comment.body.ilike(f'%{search}%')
|
||||
stripped_body.ilike(f'%{search}%')
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
@@ -845,6 +856,7 @@ def _roles():
|
||||
|
||||
|
||||
@admin_bp.route('/settings', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def settings():
|
||||
from app.models import SystemSetting
|
||||
|
||||
Reference in New Issue
Block a user