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

Commit

Permalink
Allow admin users to create or modify users without a shared secret (#…
Browse files Browse the repository at this point in the history
…6495)

Signed-off-by: Manuel Stahl <manuel.stahl@awesome-technologies.de>
  • Loading branch information
awesome-manuel authored and richvdh committed Jan 9, 2020
1 parent d773290 commit d2906fe
Show file tree
Hide file tree
Showing 9 changed files with 655 additions and 339 deletions.
1 change: 1 addition & 0 deletions changelog.d/5742.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow admin to create or modify a user. Contributed by Awesome Technologies Innovationslabor GmbH.
33 changes: 32 additions & 1 deletion docs/admin_api/user_admin_api.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
Create or modify Account
========================

This API allows an administrator to create or modify a user account with a
specific ``user_id``.

This api is::

PUT /_synapse/admin/v2/users/<user_id>

with a body of:

.. code:: json
{
"password": "user_password",
"displayname": "User",
"avatar_url": "<avatar_url>",
"admin": false,
"deactivated": false
}
including an ``access_token`` of a server admin.

The parameter ``displayname`` is optional and defaults to ``user_id``.
The parameter ``avatar_url`` is optional.
The parameter ``admin`` is optional and defaults to 'false'.
The parameter ``deactivated`` is optional and defaults to 'false'.
If the user already exists then optional parameters default to the current value.

List Accounts
=============

Expand Down Expand Up @@ -50,7 +80,8 @@ This API returns information about a specific user account.

The api is::

GET /_synapse/admin/v1/whois/<user_id>
GET /_synapse/admin/v1/whois/<user_id> (deprecated)
GET /_synapse/admin/v2/users/<user_id>

including an ``access_token`` of a server admin.

Expand Down
9 changes: 9 additions & 0 deletions synapse/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ async def get_whois(self, user):

return ret

async def get_user(self, user):
"""Function to get user details"""
ret = await self.store.get_user_by_id(user.to_string())
if ret:
profile = await self.store.get_profileinfo(user.localpart)
ret["displayname"] = profile.display_name
ret["avatar_url"] = profile.avatar_url
return ret

async def get_users(self):
"""Function to retrieve a list of users in users table.
Expand Down
2 changes: 2 additions & 0 deletions synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
SearchUsersRestServlet,
UserAdminServlet,
UserRegisterServlet,
UserRestServletV2,
UsersRestServlet,
UsersRestServletV2,
WhoisRestServlet,
Expand Down Expand Up @@ -191,6 +192,7 @@ def register_servlets(hs, http_server):
SendServerNoticeServlet(hs).register(http_server)
VersionServlet(hs).register(http_server)
UserAdminServlet(hs).register(http_server)
UserRestServletV2(hs).register(http_server)
UsersRestServletV2(hs).register(http_server)


Expand Down
142 changes: 142 additions & 0 deletions synapse/rest/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,148 @@ async def on_GET(self, request):
return 200, ret


class UserRestServletV2(RestServlet):
PATTERNS = (re.compile("^/_synapse/admin/v2/users/(?P<user_id>@[^/]+)$"),)

"""Get request to list user details.
This needs user to have administrator access in Synapse.
GET /_synapse/admin/v2/users/<user_id>
returns:
200 OK with user details if success otherwise an error.
Put request to allow an administrator to add or modify a user.
This needs user to have administrator access in Synapse.
We use PUT instead of POST since we already know the id of the user
object to create. POST could be used to create guests.
PUT /_synapse/admin/v2/users/<user_id>
{
"password": "secret",
"displayname": "User"
}
returns:
201 OK with new user object if user was created or
200 OK with modified user object if user was modified
otherwise an error.
"""

def __init__(self, hs):
self.hs = hs
self.auth = hs.get_auth()
self.admin_handler = hs.get_handlers().admin_handler
self.profile_handler = hs.get_profile_handler()
self.set_password_handler = hs.get_set_password_handler()
self.deactivate_account_handler = hs.get_deactivate_account_handler()
self.registration_handler = hs.get_registration_handler()

async def on_GET(self, request, user_id):
await assert_requester_is_admin(self.auth, request)

target_user = UserID.from_string(user_id)
if not self.hs.is_mine(target_user):
raise SynapseError(400, "Can only lookup local users")

ret = await self.admin_handler.get_user(target_user)

return 200, ret

async def on_PUT(self, request, user_id):
await assert_requester_is_admin(self.auth, request)

target_user = UserID.from_string(user_id)
body = parse_json_object_from_request(request)

if not self.hs.is_mine(target_user):
raise SynapseError(400, "This endpoint can only be used with local users")

user = await self.admin_handler.get_user(target_user)

if user: # modify user
requester = await self.auth.get_user_by_req(request)

if "displayname" in body:
await self.profile_handler.set_displayname(
target_user, requester, body["displayname"], True
)

if "avatar_url" in body:
await self.profile_handler.set_avatar_url(
target_user, requester, body["avatar_url"], True
)

if "admin" in body:
set_admin_to = bool(body["admin"])
if set_admin_to != user["admin"]:
auth_user = requester.user
if target_user == auth_user and not set_admin_to:
raise SynapseError(400, "You may not demote yourself.")

await self.admin_handler.set_user_server_admin(
target_user, set_admin_to
)

if "password" in body:
if (
not isinstance(body["password"], text_type)
or len(body["password"]) > 512
):
raise SynapseError(400, "Invalid password")
else:
new_password = body["password"]
await self._set_password_handler.set_password(
target_user, new_password, requester
)

if "deactivated" in body:
deactivate = bool(body["deactivated"])
if deactivate and not user["deactivated"]:
result = await self.deactivate_account_handler.deactivate_account(
target_user.to_string(), False
)
if not result:
raise SynapseError(500, "Could not deactivate user")

user = await self.admin_handler.get_user(target_user)
return 200, user

else: # create user
if "password" not in body:
raise SynapseError(
400, "password must be specified", errcode=Codes.BAD_JSON
)
elif (
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", None)

if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES:
raise SynapseError(400, "Invalid user type")

user_id = await self.registration_handler.register_user(
localpart=target_user.localpart,
password=body["password"],
admin=bool(admin),
default_display_name=displayname,
user_type=user_type,
)
if "avatar_url" in body:
await self.profile_handler.set_avatar_url(
user_id, requester, body["avatar_url"], True
)

ret = await self.admin_handler.get_user(target_user)

return 201, ret


class UserRegisterServlet(RestServlet):
"""
Attributes:
Expand Down
2 changes: 2 additions & 0 deletions synapse/storage/data_stores/main/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ def get_user_by_id(self, user_id):
"name",
"password_hash",
"is_guest",
"admin",
"consent_version",
"consent_server_notice_sent",
"appservice_id",
"creation_ts",
"user_type",
"deactivated",
],
allow_none=True,
desc="get_user_by_id",
Expand Down
Loading

0 comments on commit d2906fe

Please sign in to comment.