Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Allow marking users as shadow banned #8028

Closed
wants to merge 14 commits into from
Closed

Conversation

clokep
Copy link
Member

@clokep clokep commented Aug 4, 2020

Shadow banned users are able to interact with the CS API (and receive "200 OK" responses), but the state changes aren't stored in the room.

It is worth noting that this PR doesn't give a way to mark users as shadow banned, it would need to be done manually in the database. There are follow-ups that will help here.

The majority of this PR is by @erikjohnston, I've fixed up some formatting and added tests.

self.other_user_id = self.register_user("otheruser", "pass")
self.other_access_token = self.login("otheruser", "pass")

def test_invite(self):
Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like these tests have failed once or twice intermittently? I have no idea why though. They're timing out.

(Originally mentioned this on #8034 (comment))

Copy link
Member Author

Choose a reason for hiding this comment

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

I still need to look deeper into this.

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

hrm, a few bits and bobs here.

I have a concern though. We are making up REST responses deep in the handler layer, where it is impossible to know which REST endpoint led to that path, and even where it is possible to figure it out, we'll forget about updating these codepaths when we decide to update the shape of the REST response.

We should be faking up the REST bodies close to where we are building the regular REST bodies. it may be appropriate to do so by catching ShadowBanErrors, in which case we should document each function that can raise a ShadowBanError and trace them up the call stack.

changelog.d/8028.feature Outdated Show resolved Hide resolved
synapse/api/errors.py Outdated Show resolved Hide resolved
synapse/api/errors.py Outdated Show resolved Hide resolved
synapse/api/errors.py Outdated Show resolved Hide resolved
synapse/handlers/register.py Outdated Show resolved Hide resolved
synapse/handlers/register.py Outdated Show resolved Hide resolved
synapse/handlers/room.py Show resolved Hide resolved
synapse/replication/http/register.py Outdated Show resolved Hide resolved
synapse/storage/databases/main/registration.py Outdated Show resolved Hide resolved
@clokep
Copy link
Member Author

clokep commented Aug 10, 2020

I have a concern though. We are making up REST responses deep in the handler layer, where it is impossible to know which REST endpoint led to that path, and even where it is possible to figure it out, we'll forget about updating these codepaths when we decide to update the shape of the REST response.

We should be faking up the REST bodies close to where we are building the regular REST bodies. it may be appropriate to do so by catching ShadowBanErrors, in which case we should document each function that can raise a ShadowBanError and trace them up the call stack.

I agree that this will probably be clearer! I'll re-worker this. I think the current approach was chosen to avoid a bunch of try-excepts where the except essentially converts the ShadowBanError into a SynapeError, but having the returned body close to the REST layer makes sense.

@clokep
Copy link
Member Author

clokep commented Aug 10, 2020

I agree that this will probably be clearer! I'll re-worker this. I think the current approach was chosen to avoid a bunch of try-excepts where the except essentially converts the ShadowBanError into a SynapeError, but having the returned body close to the REST layer makes sense.

Looking more deeply at this, I think it is still the proper thing to do, but it does bubble up through a lot of methods.

@richvdh
Copy link
Member

richvdh commented Aug 11, 2020

I believe this fixes matrix-org/matrix-spec-proposals#789 (or at least makes most of it irrelevant)

@clokep clokep requested a review from richvdh August 11, 2020 18:22
@clokep
Copy link
Member Author

clokep commented Aug 11, 2020

I believe this is ready for another review! Sorry that this grew quite a bit.

Note that I wonder if we should add similar guards to send_nonmember_event. It doesn't seem like any of the places it is called right now would be useful to have it, but it seems like it should make the same checks as create_and_send_nonmember_event.

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

I suspect we need some logging when we do a "fake" response, partly so that we can see if we're seeing requests from shadow-banned users, but also because otherwise we're going to be confused when a user's requests unexpectedly do nothing.

@@ -1171,8 +1190,25 @@ async def shutdown_room(
if not await self.store.get_room(room_id):
raise NotFoundError("Unknown room id %s" % (room_id,))

# Check if the user is shadow-banned before any mutations occur.
Copy link
Member

Choose a reason for hiding this comment

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

given this can only be called by an admin user, I think it might be a bit redundant to check the shadow-ban.

Comment on lines +177 to +178

ShadowBanError: if the requester is shadow-banned.
Copy link
Member

Choose a reason for hiding this comment

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

is this coming from _update_canonical_alias? should maybe just ignore the exception if so, as we do with AuthError.

Copy link
Member

Choose a reason for hiding this comment

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

also: would be nice to stick the Raises docstring on _update_canonical_alias, even though it's internal.

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

I'm afraid I'm struggling to follow all of this, and I'm not convinced that all of the code flows have been considered. What happens, for example, if a shadow-banned account is deactivated?

It might be easier to break this up: first land the changes to get the shadow_banned flag into Requester in one PR. Then add a raise ShadowBanError in one place and chase it up through the code, considering the correct behaviour on each path.

Sorry I know that's a load of work but I feel like this is currently a feature which will only work with one very specific set of configuration settings.

Comment on lines +133 to +135

Raises:
ShadowBanError if the requester is shadow-banned.
Copy link
Member

Choose a reason for hiding this comment

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

Again: what is the code path by which this happens? Is it just the final PL send? we should maybe ignore the exception there rather than allow it to propagate? or bail out sooner?

@@ -281,6 +288,31 @@ async def update_membership(
content: Optional[dict] = None,
require_consent: bool = True,
) -> Tuple[str, int]:
"""Update a user's membership in a room.
Copy link
Member

Choose a reason for hiding this comment

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

I note that this is used by auto-join rooms when registering. Does that mean that any server that has auto-join rooms will 500 on registration for a shadow-banned user?

Copy link
Member Author

Choose a reason for hiding this comment

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

This change was already merged in #8095, which I think makes it clearer that this only affects invites, so it shouldn't cause an issue for auto-join rooms. (Unless I'm missing what the concern was here.)

Comment on lines +176 to +182
# Since the requester can get overwritten below, check if the real
# requester is shadow-banned.
if requester.shadow_banned:
# We randomly sleep a bit just to annoy the requester a bit.
await self.clock.sleep(random.randint(1, 10))
raise ShadowBanError()

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if this is necessary. Shouldn't we let shadow-banned users change their displaynames if they want? It won't make it into any rooms thanks to the check in update_membership.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm....I was under the impression it would still end up in the room, but I think you're right. I'll need to test this.

Copy link
Member Author

Choose a reason for hiding this comment

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

So the changes to update_membership won't catch this because those only apply to invite requests.

@clokep
Copy link
Member Author

clokep commented Aug 14, 2020

It might be easier to break this up: first land the changes to get the shadow_banned flag into Requester in one PR. Then add a raise ShadowBanError in one place and chase it up through the code, considering the correct behaviour on each path.

This sounds like a tractable way to move forward with this. I suspect it would cleaner to start with a new pull request, but would be curious of your thoughts here!

@richvdh
Copy link
Member

richvdh commented Aug 14, 2020

I suspect it would cleaner to start with a new pull request

so do I...

@clokep
Copy link
Member Author

clokep commented Aug 24, 2020

This has been split out into individual parts, so I'm going to close this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants