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

Add an admin API to check if a username is available #10578

Merged
merged 14 commits into from
Aug 17, 2021
1 change: 1 addition & 0 deletions changelog.d/10578.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add an admin API (`GET /_synapse/admin/username_available`) to check if a username is available (regardless of registration settings).
13 changes: 13 additions & 0 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1013,3 +1013,16 @@ The following parameters should be set in the URL:
- `user_id` - The fully qualified MXID: for example, `@user:server.com`. The user must
be local.

### Check username availability
Copy link
Member

Choose a reason for hiding this comment

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

please can you define what "available" actually means here?

Copy link
Member

Choose a reason for hiding this comment

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

Or rather, I guess, give a short summary and link to the /register/available API for more details.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a short description, there is a link already here to the existing docs further down.

Copy link
Member

Choose a reason for hiding this comment

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

the link further down just talks about the request/response format shape. It doesn't say anything about defining what "available" actually means. I'm just talking about a "see ... for more information" link. See the suggestion below.


The API is:

```
POST /_synapse/admin/v1/username_availabile?username=$localpart
```

To use it, you will need to authenticate by providing an `access_token` for a
server admin: [Admin API](../usage/administration/admin_api)


The request and response format is the same as the [/_matrix/client/r0/register/available](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-register-available) API
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
UserAdminServlet,
UserMediaRestServlet,
UserMembershipRestServlet,
UsernameAvailableRestServlet,
UserRegisterServlet,
UserRestServletV2,
UsersRestServletV2,
Expand Down Expand Up @@ -241,6 +242,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
ForwardExtremitiesRestServlet(hs).register(http_server)
RoomEventContextServlet(hs).register(http_server)
RateLimitRestServlet(hs).register(http_server)
UsernameAvailableRestServlet(hs).register(http_server)


def register_servlets_for_client_rest_resource(
Expand Down
25 changes: 25 additions & 0 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -1097,3 +1097,28 @@ async def on_DELETE(
await self.store.delete_ratelimit_for_user(user_id)

return 200, {}


class UsernameAvailableRestServlet(RestServlet):
Copy link
Member

Choose a reason for hiding this comment

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

we ought to break up this file into smaller modules. Also, there ought to be a "register servlets" function in here so that they don't all have to be imported individually into synapse.rest.admin.__init__.

Still, I guess this straw isn't going to break the camel's back.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've broken out both the tests and the rest handler, as both were easy to move :)

"""An admin API to check if a given username is available, regardless of whether registration is enabled.

Example:
GET /_synapse/admin/v1/username_available?username=foo
200 OK
{
"available": true
}
"""

PATTERNS = admin_patterns("/username_available")

def __init__(self, hs: "HomeServer"):
self.auth = hs.get_auth()
self.registration_handler = hs.get_registration_handler()

async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
await assert_requester_is_admin(self.auth, request)

username = parse_string(request, "username", required=True)
await self.registration_handler.check_username(username)
return 200, {"available": True}
58 changes: 57 additions & 1 deletion tests/rest/admin/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@

import synapse.rest.admin
from synapse.api.constants import UserTypes
from synapse.api.errors import Codes, HttpResponseException, ResourceLimitError
from synapse.api.errors import (
Codes,
HttpResponseException,
ResourceLimitError,
SynapseError,
)
from synapse.api.room_versions import RoomVersions
from synapse.rest.client.v1 import login, logout, profile, room
from synapse.rest.client.v2_alpha import devices, sync
Expand Down Expand Up @@ -3406,3 +3411,54 @@ def test_success(self):
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertNotIn("messages_per_second", channel.json_body)
self.assertNotIn("burst_count", channel.json_body)


class UsernameAvailableTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
login.register_servlets,
]

def prepare(self, reactor, clock, hs):
self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")
self.url = "/_synapse/admin/v1/username_available"

async def check_username(username):
if username == "allowed":
return True
raise SynapseError(400, "User ID already taken.", errcode=Codes.USER_IN_USE)

handler = self.hs.get_registration_handler()
handler.check_username = check_username

def make_homeserver(self, reactor, clock):
self.hs = self.setup_test_homeserver()
return self.hs

def test_username_available(self):
"""
The endpoint should return a 200 response if the username does not exist
"""

url = "%s?username=%s" % (self.url, "allowed")
channel = self.make_request("GET", url, None, self.admin_user_tok)

self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
self.assertTrue(channel.json_body["available"])

def test_username_unavailable(self):
"""
The endpoint should return a 200 response if the username does not exist
"""

async def check_username(username):
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
print("fibble sibble")
raise SynapseError(400, "User ID already taken.", errcode=Codes.USER_IN_USE)

url = "%s?username=%s" % (self.url, "disallowed")
channel = self.make_request("GET", url, None, self.admin_user_tok)

self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(channel.json_body["errcode"], "M_USER_IN_USE")
self.assertEqual(channel.json_body["error"], "User ID already taken.")