diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py
index 407fe9c804..e0257daa75 100644
--- a/synapse/rest/admin/users.py
+++ b/synapse/rest/admin/users.py
@@ -28,6 +28,7 @@ from synapse.http.servlet import (
parse_integer,
parse_json_object_from_request,
parse_string,
+ parse_strings_from_args,
)
from synapse.http.site import SynapseRequest
from synapse.rest.admin._base import (
@@ -64,6 +65,9 @@ class UsersRestServletV2(RestServlet):
The parameter `guests` can be used to exclude guest users.
The parameter `deactivated` can be used to include deactivated users.
The parameter `order_by` can be used to order the result.
+ The parameter `not_user_type` can be used to exclude certain user types.
+ Possible values are `bot`, `support` or "empty string".
+ "empty string" here means to exclude users without a type.
"""
def __init__(self, hs: "HomeServer"):
@@ -131,6 +135,10 @@ class UsersRestServletV2(RestServlet):
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
+ # twisted.web.server.Request.args is incorrectly defined as Optional[Any]
+ args: Dict[bytes, List[bytes]] = request.args # type: ignore
+ not_user_types = parse_strings_from_args(args, "not_user_type")
+
users, total = await self.store.get_users_paginate(
start,
limit,
@@ -141,6 +149,7 @@ class UsersRestServletV2(RestServlet):
order_by,
direction,
approved,
+ not_user_types,
)
# If support for MSC3866 is not enabled, don't show the approval flag.
diff --git a/synapse/storage/databases/main/__init__.py b/synapse/storage/databases/main/__init__.py
index 3a10c265c9..80c0304b19 100644
--- a/synapse/storage/databases/main/__init__.py
+++ b/synapse/storage/databases/main/__init__.py
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, cast
from synapse.api.constants import Direction
from synapse.config.homeserver import HomeServerConfig
+from synapse.storage._base import make_in_list_sql_clause
from synapse.storage.database import (
DatabasePool,
LoggingDatabaseConnection,
@@ -170,6 +171,7 @@ class DataStore(
order_by: str = UserSortOrder.NAME.value,
direction: Direction = Direction.FORWARDS,
approved: bool = True,
+ not_user_types: Optional[List[str]] = None,
) -> 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
@@ -185,6 +187,7 @@ class DataStore(
order_by: the sort order of the returned list
direction: sort ascending or descending
approved: whether to include approved users
+ not_user_types: list of user types to exclude
Returns:
A tuple of a list of mappings from user to information and a count of total users.
"""
@@ -222,6 +225,40 @@ class DataStore(
# be already existing users that we consider as already approved.
filters.append("approved IS FALSE")
+ if not_user_types:
+ if len(not_user_types) == 1 and not_user_types[0] == "":
+ # Only exclude NULL type users
+ filters.append("user_type IS NOT NULL")
+ else:
+ not_user_types_has_empty = False
+ not_user_types_without_empty = []
+
+ for not_user_type in not_user_types:
+ if not_user_type == "":
+ not_user_types_has_empty = True
+ else:
+ not_user_types_without_empty.append(not_user_type)
+
+ not_user_type_clause, not_user_type_args = make_in_list_sql_clause(
+ self.database_engine,
+ "u.user_type",
+ not_user_types_without_empty,
+ )
+
+ if not_user_types_has_empty:
+ # NULL values should be excluded.
+ # They evaluate to false > nothing to do here.
+ filters.append("NOT %s" % (not_user_type_clause))
+ else:
+ # NULL values should *not* be excluded.
+ # Add a special predicate to the query.
+ filters.append(
+ "(NOT %s OR %s IS NULL)"
+ % (not_user_type_clause, "u.user_type")
+ )
+
+ args.extend(not_user_type_args)
+
where_clause = "WHERE " + " AND ".join(filters) if len(filters) > 0 else ""
sql_base = f"""
|