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

Commit

Permalink
Change admin APIs to support checking and updating the approval statu…
Browse files Browse the repository at this point in the history
…s of a user
  • Loading branch information
babolivier committed Aug 19, 2022
1 parent 73b4791 commit 115b439
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 2 deletions.
5 changes: 5 additions & 0 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, hs: "HomeServer"):
self.store = hs.get_datastores().main
self._storage_controllers = hs.get_storage_controllers()
self._state_storage_controller = self._storage_controllers.state
self._msc3866_enabled = hs.config.experimental.msc3866.enabled

async def get_whois(self, user: UserID) -> JsonDict:
connections = []
Expand Down Expand Up @@ -74,6 +75,10 @@ async def get_user(self, user: UserID) -> Optional[JsonDict]:
"is_guest",
}

if self._msc3866_enabled:
# Only include the approved flag if support for MSC3866 is enabled.
user_info_to_return.add("approved")

# Restrict returned keys to a known set.
user_info_dict = {
key: value
Expand Down
8 changes: 8 additions & 0 deletions synapse/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ async def register_user(
by_admin: bool = False,
user_agent_ips: Optional[List[Tuple[str, str]]] = None,
auth_provider_id: Optional[str] = None,
approved: bool = False,
) -> str:
"""Registers a new client on the server.
Expand All @@ -243,6 +244,8 @@ async def register_user(
user_agent_ips: Tuples of user-agents and IP addresses used
during the registration process.
auth_provider_id: The SSO IdP the user used, if any.
approved: True if the new user should be considered already
approved by an administrator.
Returns:
The registered user_id.
Raises:
Expand Down Expand Up @@ -304,6 +307,7 @@ async def register_user(
user_type=user_type,
address=address,
shadow_banned=shadow_banned,
approved=approved,
)

profile = await self.store.get_profileinfo(localpart)
Expand Down Expand Up @@ -692,6 +696,7 @@ async def register_with_store(
user_type: Optional[str] = None,
address: Optional[str] = None,
shadow_banned: bool = False,
approved: bool = False,
) -> None:
"""Register user in the datastore.
Expand All @@ -710,6 +715,7 @@ async def register_with_store(
api.constants.UserTypes, or None for a normal user.
address: the IP address used to perform the registration.
shadow_banned: Whether to shadow-ban the user
approved: Whether to mark the user as approved by an administrator
"""
if self.hs.config.worker.worker_app:
await self._register_client(
Expand All @@ -723,6 +729,7 @@ async def register_with_store(
user_type=user_type,
address=address,
shadow_banned=shadow_banned,
approved=approved,
)
else:
await self.store.register_user(
Expand All @@ -735,6 +742,7 @@ async def register_with_store(
admin=admin,
user_type=user_type,
shadow_banned=shadow_banned,
approved=approved,
)

# Only call the account validity module(s) on the main process, to avoid
Expand Down
5 changes: 5 additions & 0 deletions synapse/replication/http/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async def _serialize_payload( # type: ignore[override]
user_type: Optional[str],
address: Optional[str],
shadow_banned: bool,
approved: bool,
) -> JsonDict:
"""
Args:
Expand All @@ -68,6 +69,8 @@ async def _serialize_payload( # type: ignore[override]
or None for a normal user.
address: the IP address used to perform the regitration.
shadow_banned: Whether to shadow-ban the user
approved: Whether the user should be considered already approved by an
administrator.
"""
return {
"password_hash": password_hash,
Expand All @@ -79,6 +82,7 @@ async def _serialize_payload( # type: ignore[override]
"user_type": user_type,
"address": address,
"shadow_banned": shadow_banned,
"approved": approved,
}

async def _handle_request( # type: ignore[override]
Expand All @@ -99,6 +103,7 @@ async def _handle_request( # type: ignore[override]
user_type=content["user_type"],
address=content["address"],
shadow_banned=content["shadow_banned"],
approved=content["approved"],
)

return 200, {}
Expand Down
43 changes: 42 additions & 1 deletion synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def __init__(self, hs: "HomeServer"):
self.store = hs.get_datastores().main
self.auth = hs.get_auth()
self.admin_handler = hs.get_admin_handler()
self._msc3866_enabled = hs.config.experimental.msc3866.enabled

async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
await assert_requester_is_admin(self.auth, request)
Expand All @@ -95,6 +96,13 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
guests = parse_boolean(request, "guests", default=True)
deactivated = parse_boolean(request, "deactivated", default=False)

# If support for MSC3866 is not enabled, apply no filtering based on the
# `approved` column.
if self._msc3866_enabled:
approved = parse_boolean(request, "approved", default=True)
else:
approved = True

order_by = parse_string(
request,
"order_by",
Expand All @@ -115,8 +123,22 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
direction = parse_string(request, "dir", default="f", allowed_values=("f", "b"))

users, total = await self.store.get_users_paginate(
start, limit, user_id, name, guests, deactivated, order_by, direction
start,
limit,
user_id,
name,
guests,
deactivated,
order_by,
direction,
approved,
)

# If support for MSC3866 is not enabled, don't show the approval flag.
if not self._msc3866_enabled:
for user in users:
del user["approved"]

ret = {"users": users, "total": total}
if (start + limit) < total:
ret["next_token"] = str(start + len(users))
Expand Down Expand Up @@ -163,6 +185,7 @@ def __init__(self, hs: "HomeServer"):
self.deactivate_account_handler = hs.get_deactivate_account_handler()
self.registration_handler = hs.get_registration_handler()
self.pusher_pool = hs.get_pusherpool()
self._msc3866_enabled = hs.config.experimental.msc3866.enabled

async def on_GET(
self, request: SynapseRequest, user_id: str
Expand Down Expand Up @@ -239,6 +262,15 @@ async def on_PUT(
HTTPStatus.BAD_REQUEST, "'deactivated' parameter is not of type boolean"
)

approved: Optional[bool] = None
if "approved" in body and self._msc3866_enabled:
approved = body["approved"]
if not isinstance(approved, bool):
raise SynapseError(
HTTPStatus.BAD_REQUEST,
"'approved' parameter is not of type boolean",
)

# convert List[Dict[str, str]] into List[Tuple[str, str]]
if external_ids is not None:
new_external_ids = [
Expand Down Expand Up @@ -343,6 +375,9 @@ async def on_PUT(
if "user_type" in body:
await self.store.set_user_type(target_user, user_type)

if approved is not None:
await self.store.update_user_approval_status(target_user, approved)

user = await self.admin_handler.get_user(target_user)
assert user is not None

Expand All @@ -355,13 +390,18 @@ async def on_PUT(
if password is not None:
password_hash = await self.auth_handler.hash(password)

new_user_approved = False
if self._msc3866_enabled and approved is True:
new_user_approved = True

user_id = await self.registration_handler.register_user(
localpart=target_user.localpart,
password_hash=password_hash,
admin=set_admin_to,
default_display_name=displayname,
user_type=user_type,
by_admin=True,
approved=new_user_approved,
)

if threepids is not None:
Expand Down Expand Up @@ -550,6 +590,7 @@ async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
user_type=user_type,
default_display_name=displayname,
by_admin=True,
approved=True,
)

result = await register._create_registration_details(user_id, body)
Expand Down
9 changes: 8 additions & 1 deletion synapse/storage/databases/main/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ async def get_users_paginate(
deactivated: bool = False,
order_by: str = UserSortOrder.USER_ID.value,
direction: str = "f",
approved: bool = True,
) -> Tuple[List[JsonDict], int]:
"""Function to retrieve a paginated list of users from
users list. This will return a json list of users and the
Expand All @@ -217,6 +218,7 @@ async def get_users_paginate(
deactivated: whether to include deactivated users
order_by: the sort order of the returned list
direction: sort ascending or descending
approved: whether to include approved users
Returns:
A tuple of a list of mappings from user to information and a count of total users.
"""
Expand Down Expand Up @@ -249,6 +251,11 @@ def get_users_paginate_txn(
if not deactivated:
filters.append("deactivated = 0")

if not approved:
# We ignore NULL values for the approved flag because these should only
# be already existing users that we consider as already approved.
filters.append("approved = 0")

where_clause = "WHERE " + " AND ".join(filters) if len(filters) > 0 else ""

sql_base = f"""
Expand All @@ -262,7 +269,7 @@ def get_users_paginate_txn(

sql = f"""
SELECT name, user_type, is_guest, admin, deactivated, shadow_banned,
displayname, avatar_url, creation_ts * 1000 as creation_ts
displayname, avatar_url, creation_ts * 1000 as creation_ts, approved
{sql_base}
ORDER BY {order_by_column} {order}, u.name ASC
LIMIT ? OFFSET ?
Expand Down
Loading

0 comments on commit 115b439

Please sign in to comment.