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).
16 changes: 16 additions & 0 deletions docs/admin_api/user_admin_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1057,3 +1057,19 @@ 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.


Checks to see if a username is available, and valid, for the server. This will
work even if registration is disabled on the server, unlike the Client-Server endpoint counterpart.
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

The API is:

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

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.

To use it, you will need to authenticate by providing an `access_token` for a
server admin: [Admin API](../usage/administration/admin_api)
2 changes: 2 additions & 0 deletions synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
)
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
from synapse.rest.admin.statistics import UserMediaStatisticsRestServlet
from synapse.rest.admin.username_available import UsernameAvailableRestServlet
from synapse.rest.admin.users import (
AccountValidityRenewServlet,
DeactivateAccountRestServlet,
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
51 changes: 51 additions & 0 deletions synapse/rest/admin/username_available.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2019 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.
import logging
from http import HTTPStatus
from typing import TYPE_CHECKING, Tuple

from synapse.http.servlet import RestServlet, parse_string
from synapse.http.site import SynapseRequest
from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin
from synapse.types import JsonDict

if TYPE_CHECKING:
from synapse.server import HomeServer

logger = logging.getLogger(__name__)


class UsernameAvailableRestServlet(RestServlet):
"""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 HTTPStatus.OK, {"available": True}
66 changes: 66 additions & 0 deletions tests/rest/admin/test_username_available.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2021 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.

import synapse.rest.admin
from synapse.api.errors import Codes, SynapseError
from synapse.rest.client.v1 import login

from tests import unittest


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"
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

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
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

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
"""

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.")