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

Commit

Permalink
Implements admin API to lock an user
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathieu Velten committed Jul 3, 2023
1 parent cd8b73a commit 4d4054e
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog.d/15870.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implements admin API to lock an user.
7 changes: 7 additions & 0 deletions synapse/api/auth/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ async def _wrapped_get_user_by_req(
access_token, allow_expired=allow_expired
)

if self.store.get_user_locked_status(requester.user.to_string()):
raise AuthError(
403,
"User account has been locked",
errcode=Codes.USER_LOCKED,
)

# Deny the request if the user account has expired.
# This check is only done for regular users, not appservice ones.
if not allow_expired:
Expand Down
2 changes: 2 additions & 0 deletions synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class Codes(str, Enum):
WEAK_PASSWORD = "M_WEAK_PASSWORD"
INVALID_SIGNATURE = "M_INVALID_SIGNATURE"
USER_DEACTIVATED = "M_USER_DEACTIVATED"
# USER_LOCKED = "M_USER_LOCKED"
USER_LOCKED = "ORG_MATRIX_MSC3939_USER_LOCKED"

# Part of MSC3848
# https://github.com/matrix-org/matrix-spec-proposals/pull/3848
Expand Down
1 change: 1 addition & 0 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ async def get_user(self, user: UserID) -> Optional[JsonDict]:
"name",
"admin",
"deactivated",
"locked",
"shadow_banned",
"creation_ts",
"appservice_id",
Expand Down
17 changes: 17 additions & 0 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,17 @@ async def on_PUT(
HTTPStatus.BAD_REQUEST, "'deactivated' parameter is not of type boolean"
)

lock = body.get("locked", False)
if not isinstance(lock, bool):
raise SynapseError(
HTTPStatus.BAD_REQUEST, "'locked' parameter is not of type boolean"
)

if deactivate and lock:
raise SynapseError(
HTTPStatus.BAD_REQUEST, "An user can't be deactivated and locked"
)

approved: Optional[bool] = None
if "approved" in body and self._msc3866_enabled:
approved = body["approved"]
Expand Down Expand Up @@ -388,6 +399,12 @@ async def on_PUT(
target_user.to_string()
)

if "locked" in body:
if lock and not user["locked"]:
await self.store.set_user_locked_status(user_id, True)
elif not lock and user["lock"]:
await self.store.set_user_locked_status(user_id, False)

if "user_type" in body:
await self.store.set_user_type(target_user, user_type)

Expand Down
50 changes: 50 additions & 0 deletions synapse/storage/databases/main/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,27 @@ async def get_user_deactivated_status(self, user_id: str) -> bool:
# Convert the integer into a boolean.
return res == 1

@cached()
async def get_user_locked_status(self, user_id: str) -> bool:
"""Retrieve the value for the `locked` property for the provided user.
Args:
user_id: The ID of the user to retrieve the status for.
Returns:
True if the user was locked, false if the user is still active.
"""

res = await self.db_pool.simple_select_one_onecol(
table="users",
keyvalues={"name": user_id},
retcol="locked",
desc="get_user_locked_status",
)

# Convert the integer into a boolean.
return res == 1

async def get_threepid_validation_session(
self,
medium: Optional[str],
Expand Down Expand Up @@ -2111,6 +2132,35 @@ def set_user_deactivated_status_txn(
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
txn.call_after(self.is_guest.invalidate, (user_id,))

async def set_user_locked_status(self, user_id: str, locked: bool) -> None:
"""Set the `locked` property for the provided user to the provided value.
Args:
user_id: The ID of the user to set the status for.
locked: The value to set for `locked`.
"""

await self.db_pool.runInteraction(
"set_user_locked_status",
self.set_user_locked_status_txn,
user_id,
locked,
)

def set_user_locked_status_txn(
self, txn: LoggingTransaction, user_id: str, locked: bool
) -> None:
self.db_pool.simple_update_one_txn(
txn=txn,
table="users",
keyvalues={"name": user_id},
updatevalues={"locked": 1 if locked else 0},
)
self._invalidate_cache_and_stream(txn, self.get_user_locked_status, (user_id,))
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
# TODO is it useful ?
txn.call_after(self.is_guest.invalidate, (user_id,))

def update_user_approval_status_txn(
self, txn: LoggingTransaction, user_id: str, approved: bool
) -> None:
Expand Down
16 changes: 16 additions & 0 deletions synapse/storage/schema/main/delta/78/05_users_alter_locked.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

ALTER TABLE users ADD locked SMALLINT DEFAULT 0 NOT NULL;

0 comments on commit 4d4054e

Please sign in to comment.