From 79d0ea40eaf0e8101a3cca00cb88c73b1528a64d Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Mon, 2 Oct 2023 11:05:58 +0200 Subject: [PATCH 1/6] Move matrix-raw options into new cli._common module - Create a new module synadm.cli._module holding command options and option groups supposed to be shared between commands. - Move some of groups and options of matrix-raw command into the new module since. --- synadm/cli/_common.py | 57 +++++++++++++++++++++++++++++++++++++++++++ synadm/cli/matrix.py | 25 +++---------------- 2 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 synadm/cli/_common.py diff --git a/synadm/cli/_common.py b/synadm/cli/_common.py new file mode 100644 index 00000000..5440fec3 --- /dev/null +++ b/synadm/cli/_common.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# synadm +# Copyright (C) 2020-2023 Johannes Tiefenbacher +# +# synadm is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# synadm is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"Common CLI options, option groups, helpers and utilities." + +import click + +from click_option_group import MutuallyExclusiveOptionGroup + + +def common_opts_raw_command(function): + return click.argument( + "endpoint", type=str + )( + click.option( + "--method", "-m", + type=click.Choice(["get", "post", "put", "delete"]), + help="The HTTP method used for the request.", + default="get", show_default=True + )(function) + ) + + +data_group_raw_command = MutuallyExclusiveOptionGroup( + "Data", + help="" +) + + +def data_opts_raw_command(function): + return data_group_raw_command.option( + "--data", "-d", type=str, default='{}', show_default=True, + help="""The JSON string sent in the body of post, put and delete + requests - provided as a string. Make sure to escape it from shell + interpretation by using single quotes. E.g '{"key1": "value1", + "key2": 123}'""" + )( + data_group_raw_command.option( + "--data-file", "-f", type=click.File("rt"), + show_default=True, + help="""Read JSON data from file. To read from stdin use "-" as the + filename argument.""" + )(function) + ) diff --git a/synadm/cli/matrix.py b/synadm/cli/matrix.py index 5a64ca6b..489f688f 100644 --- a/synadm/cli/matrix.py +++ b/synadm/cli/matrix.py @@ -21,6 +21,7 @@ import click from synadm import cli +from synadm.cli._common import common_opts_raw_command, data_opts_raw_command from click_option_group import optgroup, MutuallyExclusiveOptionGroup @@ -72,28 +73,8 @@ def login_cmd(helper, user_id, password): @matrix.command(name="raw") -@click.argument( - "endpoint", type=str) -@click.option( - "--method", "-m", type=click.Choice(["get", "post", "put", "delete"]), - help="""The HTTP method used for the request.""", - default="get", show_default=True) -@optgroup.group( - "Data input", - cls=MutuallyExclusiveOptionGroup, - help="") -@optgroup.option( - "--data", "-d", type=str, default='{}', show_default=True, - help="""The JSON string sent in the body of post, put and delete requests - - provided as a string. Make sure to escape it from shell interpretation by - using single quotes. E.g '{"key1": "value1", "key2": 123}' - """) -@optgroup.option( - "--data-file", "-f", type=click.File("rt"), - show_default=True, - help="""Read JSON data from file. To read from stdin use "-" as the - filename argument. - """) +@common_opts_raw_command +@data_opts_raw_command @optgroup.group( "Matrix token", cls=MutuallyExclusiveOptionGroup, From d2e721d65533540378d72bde7b504164743ee136 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Mon, 2 Oct 2023 11:46:47 +0200 Subject: [PATCH 2/6] Simplify data opts handling in matrix raw cmd --- synadm/cli/matrix.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/synadm/cli/matrix.py b/synadm/cli/matrix.py index 489f688f..a43f5efe 100644 --- a/synadm/cli/matrix.py +++ b/synadm/cli/matrix.py @@ -110,15 +110,10 @@ def raw_request_cmd(helper, endpoint, method, data, data_file, token, prompt): token = click.prompt("Matrix token", type=str) if data_file: - raw_request = helper.matrix_api.raw_request( - endpoint, - method, - data_file.read(), - token=token - ) - else: - raw_request = helper.matrix_api.raw_request(endpoint, method, data, - token=token) + data = data_file.read() + + raw_request = helper.matrix_api.raw_request(endpoint, method, data, + token=token) if helper.no_confirm: if raw_request is None: From 8d928524c7422892fc9c9149f75b715d83a96d61 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Mon, 2 Oct 2023 12:10:24 +0200 Subject: [PATCH 3/6] Add raw command - New cli-level module "raw" containing "raw" command directly (without a group) - Copy boilerplate from "matrix raw" command - almost the same but still a little different - actually even simpler. - Import and use shared options from cli._common. - for `synadm raw --help` state the fact that URL encoding needs to be handled at this point already; add an example that includes an encoded URL for maximum clarity; and suggest the usage of the global verbose flag. --- synadm/api.py | 14 +++++++++++ synadm/cli/__init__.py | 2 +- synadm/cli/raw.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 synadm/cli/raw.py diff --git a/synadm/api.py b/synadm/api.py index 75c0cb3d..6c08293d 100644 --- a/synadm/api.py +++ b/synadm/api.py @@ -1366,3 +1366,17 @@ def notice_send(self, receivers, content_plain, content_html, paginate, else: data["user_id"] = receivers return [self.query("post", "v1/send_server_notice", data=data)] + + def raw_request(self, endpoint, method, data): + data_dict = {} + if method != "get": + self.log.debug("The data we are trying to parse and submit:") + self.log.debug(data) + try: # user provided json might be crap + data_dict = json.loads(data) + except Exception as error: + self.log.error("loading data: %s: %s", + type(error).__name__, error) + return None + + return self.query(method, endpoint, data=data_dict) diff --git a/synadm/cli/__init__.py b/synadm/cli/__init__.py index 8346743b..a758c997 100644 --- a/synadm/cli/__init__.py +++ b/synadm/cli/__init__.py @@ -505,4 +505,4 @@ def version(helper): # Import additional commands -from synadm.cli import room, user, media, group, history, matrix, regtok, notice # noqa: F401, E402, E501 +from synadm.cli import room, user, media, group, history, matrix, regtok, notice, raw # noqa: F401, E402, E501 diff --git a/synadm/cli/raw.py b/synadm/cli/raw.py new file mode 100644 index 00000000..8dd7c85f --- /dev/null +++ b/synadm/cli/raw.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# synadm +# Copyright (C) 2020-2023 Johannes Tiefenbacher +# +# synadm is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# synadm is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""This module holds the `raw` command only.""" + +import click + +from synadm import cli +from synadm.cli._common import common_opts_raw_command, data_opts_raw_command + + +@cli.root.command(name="raw") +@common_opts_raw_command +@data_opts_raw_command +@click.pass_obj +def raw_request_cmd(helper, endpoint, method, data, data_file): + """ Issue a custom request to the Synapse Admin API. + + The endpoint argument is the part of the URL _after_ the configured + "Synapse base URL" and "Synapse Admin API path" (see `synadm config`). + A get request to the "Query User Account API" would look like this: + `synadm raw v2/users/%40testuser%3Aexample.org`. URL encoding must be + handled at this point. Consider enabling debug outputs via synadm's global + flag `-vv` + """ + if data_file: + data = data_file.read() + + raw_request = helper.api.raw_request(endpoint, method, data) + + if helper.no_confirm: + if raw_request is None: + raise SystemExit(1) + helper.output(raw_request) + else: + if raw_request is None: + click.echo("The Admin API's response was empty or JSON data " + "could not be loaded.") + raise SystemExit(1) + else: + helper.output(raw_request) From 27de04120ccc80b0ba2e8f5669a3fc112d2f93b3 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Fri, 13 Oct 2023 10:21:53 +0200 Subject: [PATCH 4/6] Improve and clarify matrix raw command's help - Use proper rst syntax for stating commands/cli/monospaced (double backticks), which improves rendered docs and still looks good enough in --help on the shell. - Clarify which config settings we are referring to. - Format token precedence list with \b (displays linebreaks as-is; renders nicely on the shell as well as in the Sphinx docs. - Add an "insecure warning" --- synadm/cli/matrix.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/synadm/cli/matrix.py b/synadm/cli/matrix.py index a43f5efe..c233b7b2 100644 --- a/synadm/cli/matrix.py +++ b/synadm/cli/matrix.py @@ -82,29 +82,38 @@ def login_cmd(helper, user_id, password): @optgroup.option( "--token", "-t", type=str, envvar='MTOKEN', show_default=True, help="""Token used for Matrix authentication instead of the configured - admin user's token. If --token (and --prompt) option is missing, the token - is read from environment variable $MTOKEN instead. To make sure a user's - token does not show up in system logs, don't provide it on the shell - directly but set $MTOKEN with shell command `read MTOKEN`.""") + admin user's token. If ``--token`` (and ``--prompt``) option is missing, + the token is read from environment variable ``$MTOKEN`` instead. To make + sure a user's token does not show up in system logs, don't provide it on + the shell directly but set ``$MTOKEN`` with shell command ``read + MTOKEN``.""") @optgroup.option( "--prompt", "-p", is_flag=True, show_default=True, help="""Prompt for the token used for Matrix authentication. This option always overrides $MTOKEN.""") @click.pass_obj def raw_request_cmd(helper, endpoint, method, data, data_file, token, prompt): - """ Execute a raw request to the Matrix API. + """ Execute a custom request to the Matrix API. The endpoint argument is the part of the URL _after_ the configured base - URL and Matrix path (see `synadm config`). A simple get request would e.g - look like this: `synadm matrix raw client/versions` + URL (actually "Synapse base URL") and "Matrix API path" (see ``synadm + config``). A get request could look like this: ``synadm matrix raw + client/versions`` URL encoding must be handled at this point. Consider + enabling debug outputs via synadm's global flag ``-vv`` - Use either --token or --prompt to provide a user's token and execute Matrix - commands on their behalf. Respect the privacy of others! Be responsible! + Use either ``--token`` or ``--prompt`` to provide a user's token and + execute Matrix commands on their behalf. Respect the privacy of others! + Act responsible! + \b The precedence rules for token reading are: - 1. Interactive input using --prompt; 2. Set on CLI via --token string; - 3. Read from environment variable $MTOKEN; 4. Preconfigured admin token - set in synadm's config file. + 1. Interactive input using ``--prompt``; + 2. Set on CLI via ``--token`` + 3. Read from environment variable ``$MTOKEN``; + 4. Preconfigured admin token set via ``synadm config``. + + Caution: Passing secrets as CLI arguments or via environment variables is + not considered secure. Know what you are doing! """ if prompt: token = click.prompt("Matrix token", type=str) From c2556905e777799ce7458c4150fb1649d1956f88 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Fri, 13 Oct 2023 12:02:40 +0200 Subject: [PATCH 5/6] Fix raw command Sphinx doc by using backticks to ensure monospace font. --- synadm/cli/raw.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/synadm/cli/raw.py b/synadm/cli/raw.py index 8dd7c85f..0f1501e2 100644 --- a/synadm/cli/raw.py +++ b/synadm/cli/raw.py @@ -31,11 +31,11 @@ def raw_request_cmd(helper, endpoint, method, data, data_file): """ Issue a custom request to the Synapse Admin API. The endpoint argument is the part of the URL _after_ the configured - "Synapse base URL" and "Synapse Admin API path" (see `synadm config`). + "Synapse base URL" and "Synapse Admin API path" (see ``synadm config``). A get request to the "Query User Account API" would look like this: - `synadm raw v2/users/%40testuser%3Aexample.org`. URL encoding must be + ``synadm raw v2/users/%40testuser%3Aexample.org``. URL encoding must be handled at this point. Consider enabling debug outputs via synadm's global - flag `-vv` + flag ``-vv`` """ if data_file: data = data_file.read() From 6e52699498764ed8c393a1b343976302888492ba Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Fri, 13 Oct 2023 12:08:42 +0200 Subject: [PATCH 6/6] Add new file/menuentry for raw command to CLI docs --- doc/source/index_cli_reference.rst | 1 + doc/source/synadm.cli.raw.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 doc/source/synadm.cli.raw.rst diff --git a/doc/source/index_cli_reference.rst b/doc/source/index_cli_reference.rst index 51ae6d7d..d64ba248 100644 --- a/doc/source/index_cli_reference.rst +++ b/doc/source/index_cli_reference.rst @@ -15,3 +15,4 @@ Command Line Reference synadm.cli.matrix synadm.cli.regtok synadm.cli.notice + synadm.cli.raw diff --git a/doc/source/synadm.cli.raw.rst b/doc/source/synadm.cli.raw.rst new file mode 100644 index 00000000..b386fdd2 --- /dev/null +++ b/doc/source/synadm.cli.raw.rst @@ -0,0 +1,6 @@ +Raw +==== + +.. click:: synadm.cli.raw:raw_request_cmd + :prog: synadm raw + :nested: full