Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict posting messages on streams having moderators/admins only #1149

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

mounilKshah
Copy link
Collaborator

@mounilKshah mounilKshah commented Jan 29, 2022

What does this PR do?

  • Added is_unauthorised_to_post() function to verify if a user is allowed to message in the given stream or not
  • If the user is not allowed, restrict compose box from opening
  • Print a warning in the footer
  • Test functions (can post and can-not post) for is_unauthorised_to_post() function
    Partly fixes Restrict sending messages to announcement streams #682

Discussion on developer channel:
[#zulip-terminal> Restrict sending messages to streams #T1149 #T682
#api-documentation>stream_post_policy (API docs)

Tested?

  • Manually
  • Existing tests
  • Passed linting & tests (each commit)
  • New tests added

Commit flow
Only one commit which contains all the changes made

  • Creating is_unauthorised_to_post() in model.py
  • Using that function in boxes.py, the compose box is opened only when the user is authorised to send a message to the stream
  • Two new test functions added to test_model.py:
    -test_is_unauthorised_to_post_in_stream_can_post() and
    -test_is_unauthorised_to_post_in_stream_cannot_post()

Notes & Questions
The current changes have covered the following cases:

  • Reply to a message in stream (r/enter)
  • Reply quoting the current message text (>)
  • Reply (in the same stream) mentioning the sender of the current message (@)

Use cases is still pending:

  • New message to a stream
  • Draft whose stream post policies have been changed
  • Full member

Example

Before implementing the changes

before_changes

Here, upon pressing enter/r for replying to the message in the announce stream, the compose box opens even though the user does not have permission to post on that stream

After implementing the changes

after_changes

Upon trying to reply to the message in the stream, no compose box pops up and a warning is displayed in the footer informing the user about the restrictions due to the stream post policy.

@zulipbot zulipbot added size: XL [Automatic label added by zulipbot] enhancement New feature or request labels Jan 29, 2022
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounilKshah Thanks for working on this! 👍

I've left some feedback inline, let me know if you have questions.

Once the code is moved and the logic is isolated, I'd highly recommend adding some tests for the new model function, as that will ensure we cover a lot (or all) of the combinations of stream settings, server levels (if necessary) and user roles.

zulipterminal/helper.py Outdated Show resolved Hide resolved
zulipterminal/ui_tools/boxes.py Outdated Show resolved Hide resolved
@neiljp neiljp added area: UI General user interface update PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback labels Jan 30, 2022
@zulipbot zulipbot added size: L [Automatic label added by zulipbot] and removed size: XL [Automatic label added by zulipbot] labels Feb 2, 2022
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch 3 times, most recently from 84a52d1 to 62c2e77 Compare February 2, 2022 07:22
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounilKshah This is looking cleaner already, including after that black issue!

Once you've extracted some variables this will read much simpler :)

Given the function behaves a little like a boolean, we could consider naming it with an is_ prefix or similar, and of course this doesn't need to refer to the compose box specifically now.

As per my earlier review, I'd recommend investigating tests of the model method now, or perhaps once you've pushed a version with these changes. The user roles should be already independent of server version via the function you're already calling, but a parametrised test will help show confidence in behavior.

zulipterminal/ui_tools/boxes.py Outdated Show resolved Hide resolved
Comment on lines 526 to 528
if self.stream_dict[stream_identifier].get(
"stream_post_policy"
) == 2 or self.stream_dict[stream_identifier].get("is_announcement_only"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with the other file, extracting these two elements above the conditional will make each branch of the conditional much easier to read if you give these good names.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've done this, but note that you've replaced a .get with an index; this may or may not be a problem, but this will be clearer once you have test cases for different server versions.

zulipterminal/model.py Outdated Show resolved Hide resolved
Comment on lines 531 to 533
self.get_user_info(self.user_id).get("role") <= 200
or self.initial_data.get("is_admin", True)
or self.initial_data.get("is_owner", True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing get_user_info could work here, but the type is wrong so mypy is complaining.

Do we need more than the get_user_info?

In principle we could track the role of the user who is running the app separately, but we already have the logic in get_user_info. That method does currently do a lot of work which we won't use here, however.

Copy link
Collaborator Author

@mounilKshah mounilKshah Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I too thought of eliminating the use of the function by extracting value of the role variable alone. However, going through the changelogs, I noticed that role was introduced in Zulip 4.0 (ZFL 59) and the moderator role was added in Zulip 4.0 (ZFL 60), so using role alone can cause problems with respect to backwards compatibility. We would also have to use the is_admin and is_owner attributes to make sure it runs smoothly for earlier versions as well.

One way to do it would be following your suggestion about assigning the user information to a variable using just the first three lines of get_user_info. After assigning, we can verify whether the given user exists or not (as the mypy error is due to the Optional return type of get_user_info). I tried this on my local machine and mypy didn't throw any errors for it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little wary of duplicating the logic in this new method. That said, let's get it working and tested against some known server responses, and we can refactor next.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering your suggestion regarding get_user_info doing a lot more than what we need here, I considered this logic as an alternative to using the get_user_info function within the new method.

return "Only Admins, moderators and owners are allowd to type"
elif self.stream_dict[stream_identifier].get("stream_post_policy") == 3:
# checks whether the user is an admin/owner/moderator/full member
if self.get_user_info(self.user_id).get("role") <= 400:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This includes all users; a "full user" is subtly different and can be time-limited (eg. so new members cannot write to some streams). Check the zulip help documentation.

Copy link
Collaborator Author

@mounilKshah mounilKshah Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is the definition which had been suggested on the #api-documentation>stream_post_policy stream.
However, as suggested by Tim in the #api-documentation>stream_post_policy, I checked with the help center documentation and here they have mentioned that a full member is

Member: Has access to all public streams. Member is the default role for most users. Some organization settings allow an organization to restrict the permissions of new members; Members who do not have those restrictions are called full members.

So the definition is not limited to the new members who are members for the waiting_period_threshold. Hence, I kept a separate conditional statement for this as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, OK, so you know about it but you've left the waiting_period_threshold part unimplemented at this point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't actually thought about its implementation yet. However, I'll look into it.
On a side note, I couldn't find any references to waiting_period_threshold in the terminal repository nor in the (API and Developer) documentation. Can you please guide me on that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you posted about that; you could check the server code in the meantime.

zulipterminal/model.py Outdated Show resolved Hide resolved
@neiljp
Copy link
Collaborator

neiljp commented Feb 3, 2022

@mounilKshah Just a note that generally it's fine (at least in Zulip) to force push a rebased version to a PR branch (where we also avoid merge commits). It's fine to show branch progress as you're coding as extra commits if it's something being explored, but otherwise it's better to show the current status of commits that you'd like to see merged. We can discuss on czo if you'd like to know more.

@mounilKshah
Copy link
Collaborator Author

@mounilKshah Just a note that generally it's fine (at least in Zulip) to force push a rebased version to a PR branch (where we also avoid merge commits). It's fine to show branch progress as you're coding as extra commits if it's something being explored, but otherwise it's better to show the current status of commits that you'd like to see merged. We can discuss on czo if you'd like to know more.

I am sorry for these extra commits. I will make sure to avoid any unnecessary forced pushes.
I'll rebase them before making the next commit.

@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 698deee to 4799355 Compare February 6, 2022 17:24
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounilKshah As per another comment, this looks a lot cleaner 👍

Now seems a good time to write some tests for the function added to model.py for different user/stream combinations, for different server versions. That will ensure that the behavior is as we expect without manually testing every combination.

@zulipbot zulipbot added size: XL [Automatic label added by zulipbot] and removed size: L [Automatic label added by zulipbot] labels Feb 16, 2022
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 166f33f to 594d96d Compare February 21, 2022 13:16
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch 3 times, most recently from 159e039 to a6006b6 Compare March 14, 2022 02:27
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounilKshah This is looking a lot clearer 👍

zulipterminal/model.py Outdated Show resolved Hide resolved
Comment on lines +817 to +877
case(
{"is_owner": False, "is_admin": False, "is_guest": False},
{"is_announcement_only": False, "stream_post_policy": 3},
id="is_member:members_only:Zulip3.0+:ZFL8",
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream_post_policy == 3 appears to only be valid for full members, not all members. The distinction between these depends on how a server is configured, which I don't believe we detect right now.

Comment on lines +887 to +947
case(
{"is_admin": False, "is_owner": False, "is_guest": False, "role": 400},
{"is_announcement_only": False, "stream_post_policy": 3},
id="is_member:members_only:Zulip4.0+:ZFL59",
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here.

Comment on lines 530 to 585
if (
stream_dict.get("is_announcement_only")
or stream_dict.get("stream_post_policy") == 2
):
# For admins and owners only (not using user role)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In future servers there will likely be only stream_post_policy, so maybe we should check for the role here?

Could we simplify the conditionals overall, since == 2 and is_announcement_only are the same?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, role falls under ZFL 59, whereas is_admin, is_guest and is_owner are under ZFL 8/10. Hence, I felt that we could do the checking without using role. However, I'll add role.

is_announcement_only got deprecated after ZFL 1 right? Hence, I decided to keep both so that it can function the same even after we stop support for v2.1.

zulipterminal/model.py Outdated Show resolved Hide resolved
zulipterminal/ui_tools/boxes.py Outdated Show resolved Hide resolved
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch 3 times, most recently from 3af81f6 to 55f992b Compare March 19, 2022 03:59
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 55f992b to 845c283 Compare March 27, 2022 09:28
return None
else:
return STREAM_POST_POLICY[2]
elif stream.get("stream_post_policy") is not None:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neiljp
In my opinion the condition can be changed to – "stream_post_policy" in stream
Apart from this, the rest of the function seems fine to me in the current condition.

@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 845c283 to 224d830 Compare May 4, 2022 04:42
@mounilKshah
Copy link
Collaborator Author

@zulipbot remove "PR awaiting update"
@zulipbot add "PR needs review"

@zulipbot zulipbot removed the PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback label May 4, 2022
@zulipbot zulipbot added the PR needs review PR requires feedback to proceed label May 4, 2022
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounilKshah Thanks for getting back to this 👍

I'd suggest splitting the core function+test into an initial commit and then connecting the UI in one or more later commits - for now the second commit is completely removing some code you added in the first commit, which is not how we'd approach step-by-step commits in a PR branch. It also is easier to see an isolated function and the tests for it separately.

Regarding the new behavior, I tried some of this out and we can clarify in the stream, but broadly I'd suggest:

  • we disallow simple replies and user mention replies (as you had before)
  • we open the compose box for quoted replies (as you have now, to allow content quoting to another stream)

In any case I think we need to extend the time the warnings are shown?

I'm not sure if the filter in the comprehension you added in the second commit is for the reply processing, or a step towards further work? It limits autocomplete, but doesn't handle the case of c while in the stream, and also entering text manually doesn't stop compose either.

Comment on lines 566 to 576
if user_info.get("role") is None:
if user_info.get("is_owner"):
role = 100
elif user_info.get("is_admin"):
role = 200
elif user_info.get("is_guest"):
role = 600
else:
role = 400
else:
role = user_info["role"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cleaner 👍

I think this could be combined with an extraction from get_user_info into a new method, since I believe the code is effectively the same? We discussed this previously, and I think we now have the same behavior?

This is possibly missing is_moderator, though that might be an edge case.

@neiljp neiljp added PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback and removed PR needs review PR requires feedback to proceed labels May 7, 2022
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 224d830 to 8df3905 Compare May 31, 2022 04:20
@mounilKshah mounilKshah added PR needs review PR requires feedback to proceed PR needs buddy review and removed PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback labels Jun 2, 2022
This commit contains is_unauthorised_to_post_in_stream function which
restricts unauthorised users from writing/replying on unauthorised streams.
Two test functions have been defined under test_model to test
is_unauthorised_to_post_in_stream function. Fixes zulip#682.
This commit alters _stream_box_autocomplete() to avoid showing any
restricted stream(s) in autocomplete suggestions.
This commit implements is_unauthorised_to_post() which was
added in the previous commit. Fixes zulip#682.
@mounilKshah mounilKshah force-pushed the restrict-messages-to-announcement-streams branch from 8df3905 to afb5625 Compare June 7, 2022 10:47
Copy link
Collaborator

@srdeotarse srdeotarse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR looks good with proper commits 👍. I will have a look at failing test cases more closely, but here are some suggestions.

)
if is_unauthorised_warning is not None:
self.model.controller.view.set_footer_text(is_unauthorised_warning, "task:warning", 8)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you can use report_warning function in core.py

self.message["stream_id"]
)
if is_unauthorised_warning is not None:
self.model.controller.view.set_footer_text(is_unauthorised_warning, "task:warning", 8)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also you can use report_warning function in core.py

self.message["stream_id"]
)
if is_unauthorised_warning is not None:
self.model.controller.view.set_footer_text(is_unauthorised_warning, "task:warning", 8)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, when we open the compose box (current narrow is unrestricted_stream) by using c shortcut for new message and press ctrl+d to send a message, it shows error footer_text.
To keep the styling uniform, maybe we can use report_error instead of report_warning

@neiljp
Copy link
Collaborator

neiljp commented Jun 21, 2022

@mounilKshah I think you were planning to get this worked on more shortly, so I went through the code and conversation in the stream and left what I hope is a good summary of the current status in the stream topic, with some feedback and other ideas.

@neiljp neiljp added PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback and removed PR needs review PR requires feedback to proceed PR needs buddy review labels Jun 21, 2022
@zulipbot
Copy link
Member

Heads up @mounilKshah, we just merged some commits that conflict with the changes you made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the upstream/main branch and resolve your pull request's merge conflicts accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: UI General user interface update enhancement New feature or request has conflicts PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback size: XL [Automatic label added by zulipbot]
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Restrict sending messages to announcement streams
4 participants