diff --git a/changelog.d/5742.feature b/changelog.d/5742.feature new file mode 100644 index 000000000000..92882af4a5c5 --- /dev/null +++ b/changelog.d/5742.feature @@ -0,0 +1 @@ +Allow admin to register a new user. Contributed by Awesome Technologies Innovationslabor GmbH. diff --git a/docs/admin_api/user_admin_api.rst b/docs/admin_api/user_admin_api.rst index 6ee5080eedee..0cda26b837bf 100644 --- a/docs/admin_api/user_admin_api.rst +++ b/docs/admin_api/user_admin_api.rst @@ -1,3 +1,60 @@ +Create Account +============== + +This API allows an administrator to create a new user account. + +This api is:: + + POST /_synapse/admin/v1/users + +with a body of: + +.. code:: json + + { + "username": "user_id", + "password": "user_password", + "displayname": "User", + "admin": false + } + +including an ``access_token`` of a server admin. + +The parameter ``displayname`` is optional and defaults to ``username``. +The parameter ``admin`` is optional and defaults to 'false'. + + +List Accounts +============= + +This API returns all local user accounts. + +The api is:: + + GET /_synapse/admin/v1/users + +including an ``access_token`` of a server admin. + +It returns a JSON body like the following: + +.. code:: json + [ + { + "name": "", + "password_hash": "", + "is_guest": 0, + "admin": 0, + "user_type": null + }, { + "name": "", + "password_hash": "", + "is_guest": 0, + "admin": 1, + "user_type": null + } + ] + + Query Account ============= diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py index 9ab1c2c9e0cd..d50b0e61b55d 100644 --- a/synapse/rest/admin/__init__.py +++ b/synapse/rest/admin/__init__.py @@ -52,7 +52,30 @@ class UsersRestServlet(RestServlet): - PATTERNS = historical_admin_path_patterns("/users/(?P[^/]*)") + PATTERNS = historical_admin_path_patterns("/users") + + """Get request to list all local users. + This needs user to have administrator access in Synapse. + + GET /_synapse/admin/v1/users + + returns: + 200 OK with list of users if success otherwise an error. + + + Post request to allow an administrator to add a user. + This needs user to have administrator access in Synapse. + + POST /_synapse/admin/v1/users + { + "username": "user", + "password": "secret", + "displayname": "User" + } + + returns: + 200 OK with empty object if success otherwise an error. + """ def __init__(self, hs): self.hs = hs @@ -60,17 +83,67 @@ def __init__(self, hs): self.handlers = hs.get_handlers() @defer.inlineCallbacks - def on_GET(self, request, user_id): - target_user = UserID.from_string(user_id) - yield assert_requester_is_admin(self.auth, request) + def on_GET(self, request): + requester = yield self.auth.get_user_by_req(request) + yield assert_user_is_admin(self.auth, requester.user) - if not self.hs.is_mine(target_user): - raise SynapseError(400, "Can only users a local user") + if not self.hs.is_mine(requester.user): + raise SynapseError(400, "Can only list local users") ret = yield self.handlers.admin_handler.get_users() - return (200, ret) + @defer.inlineCallbacks + def on_POST(self, request): + yield assert_requester_is_admin(self.auth, request) + + body = parse_json_object_from_request(request) + + if "username" not in body: + raise SynapseError( + 400, "username must be specified", errcode=Codes.BAD_JSON + ) + else: + if ( + not isinstance(body["username"], text_type) + or len(body["username"]) > 512 + ): + raise SynapseError(400, "Invalid username") + + if "password" not in body: + raise SynapseError( + 400, "password must be specified", errcode=Codes.BAD_JSON + ) + else: + if ( + not isinstance(body["password"], text_type) + or len(body["password"]) > 512 + ): + raise SynapseError(400, "Invalid password") + + admin = body.get("admin", None) + user_type = body.get("user_type", None) + displayname = body.get("displayname", body["username"]) + + if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: + raise SynapseError(400, "Invalid user type") + + # Reuse the parts of RegisterRestServlet to reduce code duplication + from synapse.rest.client.v2_alpha.register import RegisterRestServlet + + register = RegisterRestServlet(self.hs) + + user_id = yield register.registration_handler.register_user( + localpart=body["username"].lower(), + password=body["password"], + admin=bool(admin), + default_display_name=displayname, + user_type=user_type, + ) + + result = yield register._create_registration_details(user_id, body) + return (200, result) + class VersionServlet(RestServlet): PATTERNS = (re.compile("^/_synapse/admin/v1/server_version$"),) diff --git a/tests/rest/admin/test_admin.py b/tests/rest/admin/test_admin.py index 5877bb21337f..249f648ac29c 100644 --- a/tests/rest/admin/test_admin.py +++ b/tests/rest/admin/test_admin.py @@ -561,3 +561,81 @@ def _get_groups_user_is_in(self, access_token): self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) return channel.json_body["groups"] + + +class UserCreateTestCase(unittest.HomeserverTestCase): + + servlets = [synapse.rest.admin.register_servlets_for_client_rest_resource] + + def make_homeserver(self, reactor, clock): + + self.url = "/_matrix/client/v1/admin/users" + + self.registration_handler = Mock() + self.identity_handler = Mock() + self.login_handler = Mock() + self.device_handler = Mock() + self.device_handler.check_device_registered = Mock(return_value="FAKE") + + self.datastore = Mock(return_value=Mock()) + self.datastore.get_current_state_deltas = Mock(return_value=[]) + + self.secrets = Mock() + + self.hs = self.setup_test_homeserver() + + self.hs.get_media_repository = Mock() + self.hs.get_deactivate_account_handler = Mock() + + self.admin_user = self.register_user("admin", "pass", admin=True) + self.admin_user_tok = self.login("admin", "pass") + + self.other_user = self.register_user("user", "pass") + self.other_user_token = self.login("user", "pass") + + return self.hs + + def test_requester_is_no_admin(self): + """ + If the user is not a server admin, an error is returned. + """ + self.hs.config.registration_shared_secret = None + + request, channel = self.make_request( + "POST", + self.url, + access_token=self.other_user_tok, + content=b"{}", + ) + self.render(request) + + self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual( + "You are not a server admin", + channel.json_body["error"] + ) + + def test_requester_is_admin(self): + """ + If the user is a server admin, a new user is created. + """ + self.hs.config.registration_shared_secret = None + + body = json.dumps( + { + "username": "bob", + "password": "abc123", + "admin": False, + } + ) + + request, channel = self.make_request( + "POST", + self.url, + access_token=self.admin_user_tok, + content=body.encode(encoding='utf_8'), + ) + self.render(request) + + self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual("@bob:test", channel.json_body["user_id"])