04/06 remediate some issues
This commit is contained in:
+47
-5
@@ -25,6 +25,23 @@ logger = logging.getLogger(__name__)
|
||||
ALLOWED_EXT = {'png', 'jpg', 'jpeg', 'gif', 'pdf', 'doc', 'docx', 'txt', 'zip', 'log'}
|
||||
|
||||
|
||||
def _strip_html(text: str) -> str:
|
||||
"""Remove HTML tags from *text* for plain-text search matching.
|
||||
|
||||
Comment bodies are stored as sanitized HTML (rendered at write time via
|
||||
render_comment_body). Searching with ilike('%term%') against raw HTML
|
||||
produces two problems:
|
||||
1. A search for 'bold' misses '<strong>bold</strong>' in the stored body.
|
||||
2. HTML tag names ('strong', 'pre') can accidentally match search terms.
|
||||
Stripping tags before comparison gives consistent, tag-agnostic results.
|
||||
|
||||
Uses a simple regex rather than a full HTML parser — sufficient for the
|
||||
sanitized subset of HTML that bleach allows in comment bodies.
|
||||
"""
|
||||
import re
|
||||
return re.sub(r'<[^>]+>', '', text)
|
||||
|
||||
|
||||
def _resolve_mime_type(att):
|
||||
"""Return a reliable MIME type for an attachment.
|
||||
|
||||
@@ -195,8 +212,13 @@ def ticket_list():
|
||||
if search:
|
||||
# Extend search to cover comments and assignee name via outer joins.
|
||||
# distinct() prevents duplicate ticket rows when multiple comments match.
|
||||
# Comment bodies are stored as sanitized HTML — use REGEXP_REPLACE to
|
||||
# strip tags at the SQL level before matching so 'bold' finds
|
||||
# '<strong>bold</strong>' and HTML tag names don't pollute results.
|
||||
from app.models import Comment
|
||||
from sqlalchemy import func
|
||||
assignee_alias = db.aliased(User)
|
||||
stripped_body = func.regexp_replace(Comment.body, r'<[^>]+>', '', 'g')
|
||||
query = (
|
||||
query
|
||||
.outerjoin(Comment, Comment.ticket_id == Ticket.id)
|
||||
@@ -205,7 +227,7 @@ def ticket_list():
|
||||
Ticket.title.ilike(f'%{search}%') |
|
||||
Ticket.ticket_number.ilike(f'%{search}%') |
|
||||
Ticket.description.ilike(f'%{search}%') |
|
||||
Comment.body.ilike(f'%{search}%') |
|
||||
stripped_body.ilike(f'%{search}%') |
|
||||
assignee_alias.full_name.ilike(f'%{search}%')
|
||||
)
|
||||
.distinct()
|
||||
@@ -389,12 +411,32 @@ def update_ticket(ticket_id):
|
||||
@tickets_bp.route('/comments/<int:comment_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_comment(comment_id):
|
||||
comment = db.session.get(Comment, comment_id) or abort(404)
|
||||
if not current_user.is_it_staff and comment.author_id != current_user.id:
|
||||
abort(403)
|
||||
comment = db.session.get(Comment, comment_id) or abort(404)
|
||||
ticket_id = comment.ticket_id
|
||||
|
||||
# Authorization: IT staff may always delete any comment.
|
||||
# Employees may only delete their own comments, and only while the
|
||||
# ticket is still open or in-progress. Allowing deletion on resolved
|
||||
# or closed tickets would silently alter the historical record of a
|
||||
# completed support interaction.
|
||||
if not current_user.is_it_staff:
|
||||
if comment.author_id != current_user.id:
|
||||
abort(403)
|
||||
ticket = db.session.get(Ticket, ticket_id) or abort(404)
|
||||
if ticket.status in (TicketStatus.RESOLVED, TicketStatus.CLOSED):
|
||||
logger.warning(
|
||||
f'[COMMENT DELETE BLOCKED] comment_id={comment.id} '
|
||||
f'ticket_id={ticket_id} status={ticket.status} '
|
||||
f'user_id={current_user.id} — ticket is {ticket.status}'
|
||||
)
|
||||
flash('Comments cannot be deleted on resolved or closed tickets.', 'warning')
|
||||
return redirect(url_for('tickets.ticket_detail', ticket_id=ticket_id))
|
||||
|
||||
# Include a truncated snapshot of the body in the audit log so the
|
||||
# content is recoverable from logs even after the DB row is gone.
|
||||
body_snapshot = comment.body[:200].replace('\n', ' ')
|
||||
log_action(current_user.id, 'comment_delete', 'comment', comment.id,
|
||||
f'ticket_id={ticket_id}')
|
||||
f'ticket_id={ticket_id} body_snapshot="{body_snapshot}"')
|
||||
logger.info(f'[COMMENT DELETE] comment_id={comment.id} ticket_id={ticket_id} by user_id={current_user.id}')
|
||||
db.session.delete(comment)
|
||||
db.session.commit()
|
||||
|
||||
Reference in New Issue
Block a user