From e14cf479757b781b1aea606523efd91a68d5d2c8 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 15:20:17 +0200 Subject: [PATCH 1/9] add user modify api wrapper to not duplicate the same query code --- synadm/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 2535e122..f230a3e7 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -630,6 +630,15 @@ def user_modify(self, user_id, password, display_name, threepid, return self.query("put", "v2/users/{user_id}", data=data, user_id=user_id) + def _user_modify_api(self, user_id, data): + """ + Wrapper for the user modify API + + Passes data to synapse as is, and returns the response of the query. + """ + return self.query("put", "v2/users/{user_id}", data=data, + user_id=user_id) + def user_whois(self, user_id): """ Return information about the active sessions for a specific user """ From a7657755fcefc42ee39f6f92eee485ed106a8195 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 15:21:41 +0200 Subject: [PATCH 2/9] refactor all to use the user mod wrapper API --- synadm/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/synadm/api.py b/synadm/api.py index f230a3e7..857ac74a 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -627,8 +627,7 @@ def user_modify(self, user_id, password, display_name, threepid, if user_type: data.update({"user_type": None if user_type == 'null' else user_type}) - return self.query("put", "v2/users/{user_id}", data=data, - user_id=user_id) + return self._user_modify_api(user_id, data) def _user_modify_api(self, user_id, data): """ From 6316036871062ebc0ff606169233340f62cf5050 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 15:20:59 +0200 Subject: [PATCH 3/9] implement setting user display name --- synadm/api.py | 6 ++++++ synadm/cli/user.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 857ac74a..71b90d9d 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -638,6 +638,12 @@ def _user_modify_api(self, user_id, data): return self.query("put", "v2/users/{user_id}", data=data, user_id=user_id) + def user_set_display_name(self, user_id, display_name): + data = { + "displayname": display_name + } + return self._user_modify_api(user_id, data) + def user_whois(self, user_id): """ Return information about the active sessions for a specific user """ diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 97104b4a..942dfd54 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -505,6 +505,31 @@ def modify(ctx, helper, user_id, password, password_prompt, display_name, click.echo("Abort.") +@user.command() +@click.argument("user_id", type=str) +@click.argument("display_name", type=str) +@click.pass_obj +def set_display_name(helper, user_id, display_name): + """ + Set the display name of a user. + + Display name can be set to an empty string to remove it. + """ + mxid = helper.generate_mxid(user_id) + modified = helper.api.user_set_display_name(mxid, display_name) + if modified is None: + click.echo("Could not set display name.", err=True) + raise SystemExit(1) + if helper.output_format == "human": + new_display_name = modified["displayname"] + if new_display_name is None: + click.echo(f"Removed display name for {mxid}") + else: + click.echo(f"Set display name for {mxid} to {new_display_name}") + else: + helper.output(modified) + + @user.command() @click.argument("user_id", type=str) @click.pass_obj From 6bebbd41af1256226ed32ce0def3e7fa1b50c9ca Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:00:10 +0200 Subject: [PATCH 4/9] implement setting profile pictures --- synadm/api.py | 6 ++++++ synadm/cli/user.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 71b90d9d..3bef9ca7 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -644,6 +644,12 @@ def user_set_display_name(self, user_id, display_name): } return self._user_modify_api(user_id, data) + def user_set_profile_picture(self, user_id, mxc_uri): + data = { + "avatar_url": mxc_uri + } + return self._user_modify_api(user_id, data) + def user_whois(self, user_id): """ Return information about the active sessions for a specific user """ diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 942dfd54..1c9d4cf3 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -505,6 +505,23 @@ def modify(ctx, helper, user_id, password, password_prompt, display_name, click.echo("Abort.") +@user.command() +@click.argument("user_id", type=str) +@click.argument("mxc_uri", type=str) +@click.pass_obj +def set_profile_picture(helper, user_id, mxc_uri): + """ + Set a profile picture for a user by the MXC URI. + + Setting the MXC URI to an empty strings removes the profile picture. + """ + modified = helper.user_set_profile_picture(user_id, mxc_uri) + if modified is None: + click.echo("Could not set profile picture.", err=True) + raise SystemExit(1) + helper.output(modified) + + @user.command() @click.argument("user_id", type=str) @click.argument("display_name", type=str) From e2839bcaa2f920eb33edf130ca4f1fdf9dbb247b Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:01:15 +0200 Subject: [PATCH 5/9] implement user locking and unlocking --- synadm/api.py | 6 ++++++ synadm/cli/user.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 3bef9ca7..bfeda615 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -650,6 +650,12 @@ def user_set_profile_picture(self, user_id, mxc_uri): } return self._user_modify_api(user_id, data) + def user_set_lock(self, user_id, locked): + """ + Lock or unlock an account. + """ + return self._user_modify_api(user_id, {"locked": locked}) + def user_whois(self, user_id): """ Return information about the active sessions for a specific user """ diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 1c9d4cf3..8037483a 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -547,6 +547,30 @@ def set_display_name(helper, user_id, display_name): helper.output(modified) +@user.command() +@click.argument("user_id", type=str) +@click.pass_obj +def lock(helper, user_id): + """ + Lock a user account, preventing them from logging in or using the + account. + """ + result = helper.api.user_set_lock(user_id, True) + helper.output(result) + + +@user.command() +@click.argument("user_id", type=str) +@click.pass_obj +def unlock(helper, user_id): + """ + Unlock a user account, allowing a locked account to log in and use the + account. + """ + result = helper.api.user_set_lock(user_id, False) + helper.output(result) + + @user.command() @click.argument("user_id", type=str) @click.pass_obj From b8d7cd01b24f83f4b1c6952e356c8ec4877c21f1 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:01:45 +0200 Subject: [PATCH 6/9] implement admin grant and revoke --- synadm/api.py | 3 +++ synadm/cli/user.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index bfeda615..2f3475ba 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -656,6 +656,9 @@ def user_set_lock(self, user_id, locked): """ return self._user_modify_api(user_id, {"locked": locked}) + def user_set_admin(self, user_id, admin): + return self._user_modify_api(user_id, {"admin": admin}) + def user_whois(self, user_id): """ Return information about the active sessions for a specific user """ diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 8037483a..66620410 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -571,6 +571,22 @@ def unlock(helper, user_id): helper.output(result) +@user.command() +@click.argument("user_id", type=str) +@click.pass_obj +def admin_grant(helper, user_id): + result = helper.api.user_set_admin(user_id, True) + helper.output(result) + + +@user.command() +@click.argument("user_id", type=str) +@click.pass_obj +def admin_revoke(helper, user_id): + result = helper.api.user_set_admin(user_id, False) + helper.output(result) + + @user.command() @click.argument("user_id", type=str) @click.pass_obj From ad4072826071fe431db9b5c66f763f42b3b6d1d1 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:02:40 +0200 Subject: [PATCH 7/9] implement user reactivate --- synadm/api.py | 14 ++++++++++++++ synadm/cli/user.py | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 2f3475ba..dd299ddf 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -650,6 +650,20 @@ def user_set_profile_picture(self, user_id, mxc_uri): } return self._user_modify_api(user_id, data) + def user_reactivate(self, user_id, password=None): + """ + Args: + user_id (str): A Matrix user ID + password (str or None): A password. Not set if None Required + under certain conditions. + """ + data = { + "deactivated": False + } + if password is not None: + data["password"] = password + return self._user_modify_api(user_id, data) + def user_set_lock(self, user_id, locked): """ Lock or unlock an account. diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 66620410..a1484ce0 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -547,6 +547,19 @@ def set_display_name(helper, user_id, display_name): helper.output(modified) +@user.command() +@click.argument("user_id", type=str) +# TODO: check compatibility with --batch +@click.password_option( + required=False, + help="""The new password to set to. This is required when reactivating a + user on a Synapse installation with passwords enabled.""") +@click.pass_obj +def reactivate(helper, user_id, password): + result = helper.api.user_reactivate(user_id, password) + helper.output(result) + + @user.command() @click.argument("user_id", type=str) @click.pass_obj From 9aa5fbebfcda8445b5b158033aeff9dd7927867b Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:03:21 +0200 Subject: [PATCH 8/9] implement setting user type --- synadm/api.py | 15 +++++++++++++++ synadm/cli/user.py | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index dd299ddf..5b3b9bed 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -650,6 +650,21 @@ def user_set_profile_picture(self, user_id, mxc_uri): } return self._user_modify_api(user_id, data) + def user_set_type(self, user_id, user_type): + """ + Set user type for a user. + + Args: + user_id: A Matrix user ID. + user_type (str or None): A user type. Accepted value depends on + Synapse. A python None is the same as removing the user + type. + """ + data = { + "user_type": user_type + } + return self._user_modify_api(user_id, data) + def user_reactivate(self, user_id, password=None): """ Args: diff --git a/synadm/cli/user.py b/synadm/cli/user.py index a1484ce0..9a46ca51 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -505,6 +505,22 @@ def modify(ctx, helper, user_id, password, password_prompt, display_name, click.echo("Abort.") +@user.command() +@click.argument("user_id", type=str) +@click.argument("user_type", type=str) +@click.pass_obj +def set_user_type(helper, user_id, user_type): + """Set a user type. + + For the user_type argument, the accepted values are "null" (to + clear/remove the user type), or any other value that synapse accepts + ("bot" and "support"). + """ + if user_type == "null": + user_type = None + helper.output(helper.api.user_set_type(user_id, user_type)) + + @user.command() @click.argument("user_id", type=str) @click.argument("mxc_uri", type=str) From 70fdb899798a6f18ac1b39d425cbe3637fa87038 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 12 Oct 2023 14:13:45 +0200 Subject: [PATCH 9/9] crap: some comment and TODOs --- synadm/api.py | 1 + synadm/cli/user.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/synadm/api.py b/synadm/api.py index 5b3b9bed..842fc188 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -605,6 +605,7 @@ def user_modify(self, user_id, password, display_name, threepid, Threepid should be passed as a tuple in a tuple """ + # TODO: deprecate data = {} if password: data.update({"password": password}) diff --git a/synadm/cli/user.py b/synadm/cli/user.py index 9a46ca51..66808720 100644 --- a/synadm/cli/user.py +++ b/synadm/cli/user.py @@ -505,6 +505,11 @@ def modify(ctx, helper, user_id, password, password_prompt, display_name, click.echo("Abort.") +# for these placeholders, function similarly to the modify command, but in +# it's own command. +# TODO: find 3pid modify command + + @user.command() @click.argument("user_id", type=str) @click.argument("user_type", type=str)