Whether a comment is private or not depends on its discussion topic:
Comments on a ticket are public when neither the ticket nor its site are marked
private.
Comments are private by default:
>>> dd.plugins.comments.private_defaultTrue
Comments on a team are public when the team is not private.
>>> fromdjango.db.modelsimportQ>>> rt.login("robin").show(comments.Comments,... column_names="id ticket__group user owner",... filter=Q(ticket__isnull=False),... limit=20,display_mode=DISPLAY_MODE_GRID)... ===== ============ ================= ============================================= ID Team Author Topic----- ------------ ----------------- --------------------------------------------- 337 Jean `#116 (Why is foo so bar) <…>`__ 338 Luc `#116 (Why is foo so bar) <…>`__ 339 Marc `#116 (Why is foo so bar) <…>`__ 340 Mathieu `#116 (Why is foo so bar) <…>`__ 341 Romain Raffault `#116 (Why is foo so bar) <…>`__ 342 Rolf Rompen `#116 (Why is foo so bar) <…>`__ 343 Robin Rood `#116 (Why is foo so bar) <…>`__ 344 Developers Jean `#115 (Cannot delete foo) <…>`__ 345 Developers Luc `#115 (Cannot delete foo) <…>`__ 346 Developers Marc `#115 (Cannot delete foo) <…>`__ 347 Developers Mathieu `#115 (Cannot delete foo) <…>`__ 348 Developers Romain Raffault `#115 (Cannot delete foo) <…>`__ 349 Developers Rolf Rompen `#115 (Cannot delete foo) <…>`__ 350 Developers Robin Rood `#115 (Cannot delete foo) <…>`__ 351 Jean `#114 (No more foo when bar is gone) <…>`__ 352 Luc `#114 (No more foo when bar is gone) <…>`__ 353 Marc `#114 (No more foo when bar is gone) <…>`__ 354 Mathieu `#114 (No more foo when bar is gone) <…>`__ 355 Romain Raffault `#114 (No more foo when bar is gone) <…>`__ 356 Rolf Rompen `#114 (No more foo when bar is gone) <…>`__===== ============ ================= =============================================
Marc is a customer, so he can see only comments that are (1) public OR (2) his
own OR (3) about a group (“team”) that he can see OR (4) about something that he
can see.
Comments in Noi can be about tickets or about groups.
marc can see tickets that are (public OR his own) AND in a group that he can see
marc can see groups that are (public OR of which he is a member)
>>> qs=rt.login('marc').spawn(comments.RecentComments).data_iterator>>> printsql(qs)... SELECT comments_comment.id, comments_comment.modified, comments_comment.created, comments_comment.body, comments_comment.body_short_preview, comments_comment.body_full_preview, comments_comment.user_id, comments_comment.owner_type_id, comments_comment.owner_id, comments_comment.reply_to_id, comments_comment.private, comments_comment.comment_type_id, COUNT(T5.id) AS num_replies, COUNT(comments_reaction.id) AS num_reactionsFROM comments_commentLEFT OUTER JOIN groups_group ON (comments_comment.owner_id = groups_group.id AND (comments_comment.owner_type_id = 65))LEFT OUTER JOIN tickets_ticket ON (comments_comment.owner_id = tickets_ticket.id AND (comments_comment.owner_type_id = 43))LEFT OUTER JOIN comments_comment T5 ON (comments_comment.id = T5.reply_to_id)LEFT OUTER JOIN comments_reaction ON (comments_comment.id = comments_reaction.comment_id)WHERE ((NOT comments_comment.private OR comments_comment.user_id = 4) AND (groups_group.id IS NULL OR groups_group.id IN (SELECT DISTINCT U0.id FROM groups_group U0 LEFT OUTER JOIN groups_membership U1 ON (U0.id = U1.group_id) WHERE (NOT U0.private OR U1.user_id = 4))) AND (tickets_ticket.id IS NULL OR tickets_ticket.id IN (SELECT DISTINCT U0.id FROM tickets_ticket U0 LEFT OUTER JOIN groups_group U1 ON (U0.group_id = U1.id) LEFT OUTER JOIN groups_membership U2 ON (U1.id = U2.group_id) WHERE ((NOT U1.private AND NOT U0.private) OR U2.user_id = 4 OR U0.user_id = 4))))GROUP BY comments_comment.id, comments_comment.modified, comments_comment.created, comments_comment.body, comments_comment.body_short_preview, comments_comment.body_full_preview, comments_comment.user_id, comments_comment.owner_type_id, comments_comment.owner_id, comments_comment.reply_to_id, comments_comment.private, comments_comment.comment_type_idORDER BY comments_comment.created DESC
>>> rt.login("robin").show(comments.RecentComments,... column_names="id ticket__group user owner",... filter=Q(ticket__isnull=False),... limit=10,display_mode=DISPLAY_MODE_GRID)... ===== ========== ================= ============================================ ID Team Author Topic----- ---------- ----------------- -------------------------------------------- 420 Managers Robin Rood `#105 (Irritating message when bar) <…>`__ 419 Managers Rolf Rompen `#105 (Irritating message when bar) <…>`__ 418 Managers Romain Raffault `#105 (Irritating message when bar) <…>`__ 417 Managers Mathieu `#105 (Irritating message when bar) <…>`__ 416 Managers Marc `#105 (Irritating message when bar) <…>`__ 415 Managers Luc `#105 (Irritating message when bar) <…>`__ 414 Managers Jean `#105 (Irritating message when bar) <…>`__ 413 Robin Rood `#106 (How can I see where bar?) <…>`__ 412 Rolf Rompen `#106 (How can I see where bar?) <…>`__ 411 Romain Raffault `#106 (How can I see where bar?) <…>`__===== ========== ================= ============================================
>>> rt.login("marc").show(comments.RecentComments,... column_names="id ticket__group user owner",... filter=Q(ticket__isnull=False),... limit=10,display_mode=DISPLAY_MODE_GRID)... ===== ============ ================= ============================================================= ID Team Author Topic----- ------------ ----------------- ------------------------------------------------------------- 406 Sales team Robin Rood `#107 (Misc optimizations in Baz) <…>`__ 405 Sales team Rolf Rompen `#107 (Misc optimizations in Baz) <…>`__ 404 Sales team Romain Raffault `#107 (Misc optimizations in Baz) <…>`__ 403 Sales team Mathieu `#107 (Misc optimizations in Baz) <…>`__ 402 Sales team Marc `#107 (Misc optimizations in Baz) <…>`__ 401 Sales team Luc `#107 (Misc optimizations in Baz) <…>`__ 400 Sales team Jean `#107 (Misc optimizations in Baz) <…>`__ 395 Marc `#108 (Default account in invoices per partner) <…>`__ 392 Developers Robin Rood `#109 ('NoneType' object has no attribute 'isocode') <…>`__ 391 Developers Rolf Rompen `#109 ('NoneType' object has no attribute 'isocode') <…>`__===== ============ ================= =============================================================
Anonymous users they see only public comments.
>>> rt.show(comments.RecentComments,... column_names="id ticket__group user owner",... filter=Q(ticket__isnull=False),... limit=10,display_mode=DISPLAY_MODE_GRID)... ===== ============ ================= ============================================================= ID Team Author Topic----- ------------ ----------------- ------------------------------------------------------------- 406 Sales team Robin Rood `#107 (Misc optimizations in Baz) <…>`__ 405 Sales team Rolf Rompen `#107 (Misc optimizations in Baz) <…>`__ 404 Sales team Romain Raffault `#107 (Misc optimizations in Baz) <…>`__ 403 Sales team Mathieu `#107 (Misc optimizations in Baz) <…>`__ 402 Sales team Marc `#107 (Misc optimizations in Baz) <…>`__ 401 Sales team Luc `#107 (Misc optimizations in Baz) <…>`__ 400 Sales team Jean `#107 (Misc optimizations in Baz) <…>`__ 392 Developers Robin Rood `#109 ('NoneType' object has no attribute 'isocode') <…>`__ 391 Developers Rolf Rompen `#109 ('NoneType' object has no attribute 'isocode') <…>`__ 390 Developers Romain Raffault `#109 ('NoneType' object has no attribute 'isocode') <…>`__===== ============ ================= =============================================================
Ticket #5759 (Anonymous can GET private comments) was a security issue
between 20240920 and 20241001.
For example the following request failed to cause an exception.
It would be more correct to return 403 (Forbidden) instead of 404 (Not found),
at least in above case, because the requested comment 420 does exist, only the
user lacks permission to see it. The case below is quite similar, except that a
comment with id 123456789 does not exist:
In both cases we return the same error code until further notice because
differentiating them would need an additional database query.
Ticket #5759 had been introduced by #5751 (ObjectDoesNotExist:
Invalid primary key 4968 for avanti.Clients), which came because avanti.Clients
has default table parameters that show only registered clients. When the user
manually removed that filter and then double-clicked on a client that was
usually filtered out, Lino gave this (false) error.
#5763 (A permalink with Bad request shows a big unreadable warning with
HTML tags)
TODO: The React front end does above call as an AJAX call and expects a
JSON-encoded response. But Lino returns Django’s default HttpResponseNotFound
response, which is in HTML. That’s why the user sees a big red warning saying
Bad request
<!DOCTYPE html> <html lang=”en”> <head> <meta http-equiv=”content-type”
content=”text/html; charset=utf-8”> <title>Page not found at
/media/cache/json/Lino_comments.Comments.420_000_en.json</title> <meta
name=”robots” content=”NONE,NOARCHIVE”> <style> html * { padding:0; margin:0;
…
comments
(comments in Noi)¶The
lino.modlib.comments
plugin in Lino Noi is configured and used to satisfy the application requirements.Side note: Code snippets (lines starting with
>>>
) in this document get tested as part of our development workflow. The following initialization snippet tells you which demo project is being used in this document.Overview¶
Public comments in Lino Noi are visible even to anonymous users.
There are seven
Commentable
models in Lino Noi, but only tickets have a CommentsByRFC panel in their detail.Whether a comment is private or not depends on its discussion topic: Comments on a ticket are public when neither the ticket nor its site are marked private.
Comments are private by default:
Comments on a team are public when the team is not private.
Visibility of comments¶
The demo database contains 420 comments, 84 of which have no team. 49 comments are public and 371 are confidential.
Marc is a customer, so he can see only comments that are (1) public OR (2) his own OR (3) about a group (“team”) that he can see OR (4) about something that he can see.
Comments in Noi can be about tickets or about groups.
marc can see tickets that are (public OR his own) AND in a group that he can see
marc can see groups that are (public OR of which he is a member)
Anonymous users they see only public comments.
#5759 (Anonymous can GET private comments)¶
Ticket #5759 (Anonymous can GET private comments) was a security issue between 20240920 and 20241001. For example the following request failed to cause an exception.
Comment 420 is confidential:
An anonymous request may of course not see it:
When processing the incoming request, Lino logs a warning in the logger and then returns a 404 error:
It would be more correct to return 403 (Forbidden) instead of 404 (Not found), at least in above case, because the requested comment 420 does exist, only the user lacks permission to see it. The case below is quite similar, except that a comment with id 123456789 does not exist:
In both cases we return the same error code until further notice because differentiating them would need an additional database query.
Ticket #5759 had been introduced by #5751 (ObjectDoesNotExist: Invalid primary key 4968 for avanti.Clients), which came because avanti.Clients has default table parameters that show only registered clients. When the user manually removed that filter and then double-clicked on a client that was usually filtered out, Lino gave this (false) error.
#5763 (Big unreadable warning with HTML tags)¶
#5763 (A permalink with Bad request shows a big unreadable warning with HTML tags)
TODO: The React front end does above call as an AJAX call and expects a JSON-encoded response. But Lino returns Django’s default HttpResponseNotFound response, which is in HTML. That’s why the user sees a big red warning saying