This page contains code snippets (lines starting with >>>), which are
being tested during our development workflow. The following
snippet initializes the demo project used throughout this page.
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 aren’t private by default in Noi:
>>> dd.plugins.users.private_defaultFalse
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 Sales team Jean `#116 (Why <p> tags are so bar) <…>`__ 338 Sales team Luc `#116 (Why <p> tags are so bar) <…>`__ 339 Sales team Marc `#116 (Why <p> tags are so bar) <…>`__ 340 Sales team Mathieu `#116 (Why <p> tags are so bar) <…>`__ 341 Sales team Romain Raffault `#116 (Why <p> tags are so bar) <…>`__ 342 Sales team Rolf Rompen `#116 (Why <p> tags are so bar) <…>`__ 343 Sales team Robin Rood `#116 (Why <p> tags are so bar) <…>`__ 344 Managers Jean `#115 (Cannot delete foo) <…>`__ 345 Managers Luc `#115 (Cannot delete foo) <…>`__ 346 Managers Marc `#115 (Cannot delete foo) <…>`__ 347 Managers Mathieu `#115 (Cannot delete foo) <…>`__ 348 Managers Romain Raffault `#115 (Cannot delete foo) <…>`__ 349 Managers Rolf Rompen `#115 (Cannot delete foo) <…>`__ 350 Managers Robin Rood `#115 (Cannot delete foo) <…>`__ 351 Developers Jean `#114 (No more foo when bar is gone) <…>`__ 352 Developers Luc `#114 (No more foo when bar is gone) <…>`__ 353 Developers Marc `#114 (No more foo when bar is gone) <…>`__ 354 Developers Mathieu `#114 (No more foo when bar is gone) <…>`__ 355 Developers Romain Raffault `#114 (No more foo when bar is gone) <…>`__ 356 Developers 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)
>>> rt.login('marc').user.user_type<users.UserTypes.customer:100>>>> qs=rt.login('marc').spawn(comments.RecentComments).data_iterator>>> printsql(qs)...SELECT DISTINCT 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.private, comments_comment.group_id, comments_comment.owner_type_id, comments_comment.owner_id, comments_comment.reply_to_id, comments_comment.comment_type_id, COUNT(T6.id) AS num_replies, COUNT(comments_reaction.id) AS num_reactionsFROM comments_commentLEFT OUTER JOIN groups_group ON (comments_comment.group_id = groups_group.id)LEFT OUTER JOIN groups_membership ON (groups_group.id = groups_membership.group_id)LEFT OUTER JOIN comments_comment T6 ON (comments_comment.id = T6.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 OR NOT groups_group.private OR groups_membership.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.private, comments_comment.group_id, comments_comment.owner_type_id, comments_comment.owner_id, comments_comment.reply_to_id, 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 Developers Robin Rood `#105 (Irritating message when bar) <…>`__ 419 Developers Rolf Rompen `#105 (Irritating message when bar) <…>`__ 418 Developers Romain Raffault `#105 (Irritating message when bar) <…>`__ 417 Developers Mathieu `#105 (Irritating message when bar) <…>`__ 416 Developers Marc `#105 (Irritating message when bar) <…>`__ 415 Developers Luc `#105 (Irritating message when bar) <…>`__ 414 Developers Jean `#105 (Irritating message when bar) <…>`__ 413 Managers Robin Rood `#106 (How can I see where bar?) <…>`__ 412 Managers Rolf Rompen `#106 (How can I see where bar?) <…>`__ 411 Managers Romain Raffault `#106 (How can I see where bar?) <…>`__===== ============ ================= ============================================
>>> rt.login("marc").show(comments.RecentComments,... column_names="id user group ticket__group owner",... filter=Q(ticket__isnull=False),... limit=10,display_mode=DISPLAY_MODE_GRID)...===== ================= ============ ============ ============================================ ID Author Team Team Topic----- ----------------- ------------ ------------ -------------------------------------------- 420 Robin Rood Developers Developers `#105 (Irritating message when bar) <…>`__ 419 Rolf Rompen Developers Developers `#105 (Irritating message when bar) <…>`__ 418 Romain Raffault Developers Developers `#105 (Irritating message when bar) <…>`__ 417 Mathieu Developers Developers `#105 (Irritating message when bar) <…>`__ 416 Marc Developers Developers `#105 (Irritating message when bar) <…>`__ 415 Luc Developers Developers `#105 (Irritating message when bar) <…>`__ 414 Jean Developers Developers `#105 (Irritating message when bar) <…>`__ 409 Marc Managers Managers `#106 (How can I see where bar?) <…>`__ 406 Robin Rood Sales team Sales team `#107 (Misc optimizations in Baz) <…>`__ 405 Rolf Rompen Sales team Sales team `#107 (Misc optimizations in Baz) <…>`__===== ================= ============ ============ ============================================
Anonymous users can see only public comments.
>>> rt.show(comments.RecentComments,... column_names="id group user owner",... filter=Q(ticket__isnull=False),... limit=10,display_mode=DISPLAY_MODE_GRID)...===== ============ ================= ============================================ ID Team Author Topic----- ------------ ----------------- -------------------------------------------- 420 Developers Robin Rood `#105 (Irritating message when bar) <…>`__ 419 Developers Rolf Rompen `#105 (Irritating message when bar) <…>`__ 418 Developers Romain Raffault `#105 (Irritating message when bar) <…>`__ 417 Developers Mathieu `#105 (Irritating message when bar) <…>`__ 416 Developers Marc `#105 (Irritating message when bar) <…>`__ 415 Developers Luc `#105 (Irritating message when bar) <…>`__ 414 Developers Jean `#105 (Irritating message when bar) <…>`__ 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) <…>`__===== ============ ================= ============================================
Just to make sure: is comment 406 really public? Yes, because neither the group,
nor the owner, nor the comment itself have been marked as private
(“confidential”).
For example the following request failed to cause an exception.
An anonymous request may of course not see it:
>>> test_client.cookies# nobody is signed in<SimpleCookie: >
When processing the incoming request, Lino logs a warning in the logger and then
returns a 404 error:
>>> res=test_client.get("/api/comments/RecentComments/413")Error during ApiElement.get(): Invalid request for '413' on comments.RecentComments (Row 413 does not exist on comments.RecentComments)Row 413 does not exist on comments.RecentCommentsTraceback (most recent call last):...django.http.response.Http404: Row 413 does not exist on comments.RecentCommentsNot Found: /api/comments/RecentComments/413
>>> res.status_code404
It would be more correct to return 403 (Forbidden) instead of 404 (Not found),
at least in above case, because the requested comment 413 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:
>>> res=test_client.get("/api/comments/RecentComments/123456789")...Error during ApiElement.get(): Invalid request for '123456789' on comments.RecentComments (Row 123456789 does not exist on comments.RecentComments)Row 123456789 does not exist on comments.RecentCommentsTraceback (most recent call last):...django.http.response.Http404: Row 123456789 does not exist on comments.RecentCommentsNot Found: /api/comments/RecentComments/123456789
>>> res.status_code404
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.413_000_en.json</title> <meta
name=”robots” content=”NONE,NOARCHIVE”> <style> html * { padding:0; margin:0;
…
comments
in Noi¶The
lino.modlib.comments
plugin in Lino Noi is configured and used to satisfy the application requirements.This page contains code snippets (lines starting with
>>>
), which are being tested during our development workflow. The following snippet initializes the demo project used throughout this page.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 aren’t private by default in Noi:
Comments on a team are public when the team is not private.
Visibility of comments¶
The demo database contains 504 comments, of which 168 have no group and 462 are marked as confidential (private).
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 can see only public comments.
Just to make sure: is comment 406 really public? Yes, because neither the group, nor the owner, nor the comment itself have been marked as private (“confidential”).
Overview how many database rows each user sees:
#5759 (Anonymous can GET private comments)¶
Ticket #5759 (Anonymous can GET private comments) was a security issue between 20240920 and 20241001.
Let’s pick a confidential comment for the following tests:
For example the following request failed to cause an exception.
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 413 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