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

Replace /admin/v1/users_paginate endpoint with /admin/v2/users #5925

Merged
merged 12 commits into from
Dec 5, 2019
1 change: 1 addition & 0 deletions changelog.d/5925.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add admin/v2/users endpoint with pagination. Contributed by Awesome Technologies Innovationslabor GmbH.
1 change: 1 addition & 0 deletions changelog.d/5925.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove admin/v1/users_paginate endpoint. Contributed by Awesome Technologies Innovationslabor GmbH.
richvdh marked this conversation as resolved.
Show resolved Hide resolved
43 changes: 43 additions & 0 deletions docs/admin_api/user_admin_api.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
List Accounts
=============

This API returns all local user accounts.

The api is::

GET /_synapse/admin/v2/users?offset=0&limit=10&guests=false

including an ``access_token`` of a server admin.
The parameters ``offset`` and ``limit`` are required only for pagination.
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved
Per default a ``limit`` of 100 is used. If the endpoint returns less entries
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved
than specified by ``limit`` then there are no more users left.
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved
The parameter ``name`` can be used to filter by user name.
richvdh marked this conversation as resolved.
Show resolved Hide resolved
richvdh marked this conversation as resolved.
Show resolved Hide resolved
The parameter ``guests`` can be used to exclude guest users.
richvdh marked this conversation as resolved.
Show resolved Hide resolved
The parameter ``deactivated`` can be used to include deactivated users.
richvdh marked this conversation as resolved.
Show resolved Hide resolved

It returns a JSON body like the following:

.. code:: json
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved

{
"users": [
{
"name": "<user_id1>",
"password_hash": "<password_hash1>",
"is_guest": 0,
"admin": 0,
"user_type": null,
"deactivated": 0
}, {
"name": "<user_id2>",
"password_hash": "<password_hash2>",
"is_guest": 0,
"admin": 1,
"user_type": null,
"deactivated": 0
}
],
"next_token": 100
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved
}


Query Account
=============

Expand Down
21 changes: 12 additions & 9 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_whois(self, user):

@defer.inlineCallbacks
def get_users(self):
"""Function to reterive a list of users in users table.
"""Function to retrieve a list of users in users table.

Args:
Returns:
Expand All @@ -67,19 +67,22 @@ def get_users(self):
return ret

@defer.inlineCallbacks
def get_users_paginate(self, order, start, limit):
"""Function to reterive a paginated list of users from
users list. This will return a json object, which contains
list of users and the total number of users in users table.
def get_users_paginate(self, start, limit, name, guests, deactivated):
"""Function to retrieve a paginated list of users from
users list. This will return a json list of users.

Args:
order (str): column name to order the select by this column
start (int): start number to begin the query from
limit (int): number of rows to reterive
limit (int): number of rows to retrieve
name (string): filter for user names
guests (bool): whether to in include guest users
deactivated (bool): whether to include deactivated users
Returns:
defer.Deferred: resolves to json object {list[dict[str, Any]], count}
defer.Deferred: resolves to json list[dict[str, Any]]
"""
ret = yield self.store.get_users_paginate(order, start, limit)
ret = yield self.store.get_users_paginate(
start, limit, name, guests, deactivated
)

return ret

Expand Down
4 changes: 2 additions & 2 deletions synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
from synapse.rest.admin.users import (
AccountValidityRenewServlet,
DeactivateAccountRestServlet,
GetUsersPaginatedRestServlet,
ResetPasswordRestServlet,
SearchUsersRestServlet,
UserAdminServlet,
UserRegisterServlet,
UsersRestServlet,
UsersRestServletV2,
WhoisRestServlet,
)
from synapse.util.versionstring import get_version_string
Expand Down Expand Up @@ -191,6 +191,7 @@ def register_servlets(hs, http_server):
SendServerNoticeServlet(hs).register(http_server)
VersionServlet(hs).register(http_server)
UserAdminServlet(hs).register(http_server)
UsersRestServletV2(hs).register(http_server)


def register_servlets_for_client_rest_resource(hs, http_server):
Expand All @@ -201,7 +202,6 @@ def register_servlets_for_client_rest_resource(hs, http_server):
PurgeHistoryRestServlet(hs).register(http_server)
UsersRestServlet(hs).register(http_server)
ResetPasswordRestServlet(hs).register(http_server)
GetUsersPaginatedRestServlet(hs).register(http_server)
SearchUsersRestServlet(hs).register(http_server)
ShutdownRoomRestServlet(hs).register(http_server)
UserRegisterServlet(hs).register(http_server)
Expand Down
84 changes: 28 additions & 56 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
parse_boolean,
parse_integer,
parse_json_object_from_request,
parse_string,
Expand Down Expand Up @@ -59,72 +60,43 @@ async def on_GET(self, request, user_id):
return 200, ret


class GetUsersPaginatedRestServlet(RestServlet):
"""Get request to get specific number of users from Synapse.
class UsersRestServletV2(RestServlet):
PATTERNS = (re.compile("^/_synapse/admin/v2/users$"),)

"""Get request to list all local users.
This needs user to have administrator access in Synapse.
Example:
http://localhost:8008/_synapse/admin/v1/users_paginate/
@admin:user?access_token=admin_access_token&start=0&limit=10
Returns:
200 OK with json object {list[dict[str, Any]], count} or empty object.
"""

PATTERNS = historical_admin_path_patterns(
"/users_paginate/(?P<target_user_id>[^/]*)"
)
GET /_synapse/admin/v2/users?offset=0&limit=10&guests=false

returns:
200 OK with list of users if success otherwise an error.

The parameters `offset` and `limit` are required only for pagination.
Per default a `limit` of 100 is used. If the endpoint returns less entries
than specified by `limit` then there are no more users left.
The parameter `name` can be used to filter by user name.
The parameter `guests` can be used to exclude guest users.
The parameter `deactivated` can be used to include deactivated users.
"""

def __init__(self, hs):
self.store = hs.get_datastore()
self.hs = hs
self.auth = hs.get_auth()
self.handlers = hs.get_handlers()

async def on_GET(self, request, target_user_id):
"""Get request to get specific number of users from Synapse.
This needs user to have administrator access in Synapse.
"""
await assert_requester_is_admin(self.auth, request)

target_user = UserID.from_string(target_user_id)

if not self.hs.is_mine(target_user):
raise SynapseError(400, "Can only users a local user")

order = "name" # order by name in user table
start = parse_integer(request, "start", required=True)
limit = parse_integer(request, "limit", required=True)

logger.info("limit: %s, start: %s", limit, start)

ret = await self.handlers.admin_handler.get_users_paginate(order, start, limit)
return 200, ret
self.admin_handler = hs.get_handlers().admin_handler

async def on_POST(self, request, target_user_id):
"""Post request to get specific number of users from Synapse..
This needs user to have administrator access in Synapse.
Example:
http://localhost:8008/_synapse/admin/v1/users_paginate/
@admin:user?access_token=admin_access_token
JsonBodyToSend:
{
"start": "0",
"limit": "10
}
Returns:
200 OK with json object {list[dict[str, Any]], count} or empty object.
"""
async def on_GET(self, request):
await assert_requester_is_admin(self.auth, request)
UserID.from_string(target_user_id)

order = "name" # order by name in user table
params = parse_json_object_from_request(request)
assert_params_in_dict(params, ["limit", "start"])
limit = params["limit"]
start = params["start"]
logger.info("limit: %s, start: %s", limit, start)
start = parse_integer(request, "offset", default=0)
limit = parse_integer(request, "limit", default=100)
name = parse_string(request, "name", default=None)
richvdh marked this conversation as resolved.
Show resolved Hide resolved
guests = parse_boolean(request, "guests", default=True)
deactivated = parse_boolean(request, "deactivated", default=False)
richvdh marked this conversation as resolved.
Show resolved Hide resolved

ret = await self.handlers.admin_handler.get_users_paginate(order, start, limit)
return 200, ret
users = await self.admin_handler.get_users_paginate(
start, limit, name, guests, deactivated
)
return 200, {"users": users, "next_token": start + len(users)}


class UserRegisterServlet(RestServlet):
Expand Down
29 changes: 14 additions & 15 deletions synapse/storage/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,7 @@ def get_cache_stream_token(self):
def _simple_select_list_paginate(
self,
table,
filters,
keyvalues,
orderby,
start,
Expand All @@ -1549,6 +1550,9 @@ def _simple_select_list_paginate(

Args:
table (str): the table name
filters (dict[str, T] | None):
column names and values to filter the rows with, or None to not
apply a WHERE ? LIKE ? clause.
keyvalues (dict[str, T] | None):
column names and values to select the rows with, or None to not
apply a WHERE clause.
Expand All @@ -1564,6 +1568,7 @@ def _simple_select_list_paginate(
desc,
self._simple_select_list_paginate_txn,
table,
filters,
keyvalues,
orderby,
start,
Expand All @@ -1577,6 +1582,7 @@ def _simple_select_list_paginate_txn(
cls,
txn,
table,
filters,
keyvalues,
orderby,
start,
Expand All @@ -1592,6 +1598,9 @@ def _simple_select_list_paginate_txn(
Args:
txn : Transaction object
table (str): the table name
filters (dict[str, T] | None):
richvdh marked this conversation as resolved.
Show resolved Hide resolved
column names and values to filter the rows with, or None to not
apply a WHERE ? LIKE ? clause.
keyvalues (dict[str, T] | None):
column names and values to select the rows with, or None to not
apply a WHERE clause.
Expand All @@ -1606,10 +1615,12 @@ def _simple_select_list_paginate_txn(
if order_direction not in ["ASC", "DESC"]:
raise ValueError("order_direction must be one of 'ASC' or 'DESC'.")

where_clause = "WHERE " if filters or keyvalues else ""
if filters:
where_clause += " AND ".join("%s LIKE ?" % (k,) for k in filters)
where_clause += " AND " if filters and keyvalues else ""
if keyvalues:
where_clause = "WHERE " + " AND ".join("%s = ?" % (k,) for k in keyvalues)
else:
where_clause = ""
where_clause += " AND ".join("%s = ?" % (k,) for k in keyvalues)

sql = "SELECT %s FROM %s %s ORDER BY %s %s LIMIT ? OFFSET ?" % (
", ".join(retcols),
Expand All @@ -1622,18 +1633,6 @@ def _simple_select_list_paginate_txn(

return cls.cursor_to_dict(txn)

def get_user_count_txn(self, txn):
"""Get a total number of registered users in the users list.

Args:
txn : Transaction object
Returns:
int : number of users
"""
sql_count = "SELECT COUNT(*) FROM users WHERE is_guest = 0;"
txn.execute(sql_count)
return txn.fetchone()[0]

def _simple_search_list(
self, table, term, col, retcols, desc="_simple_search_list"
):
Expand Down
Loading