From 1d6da55e77c27128814e8e9ce76bf65c8e170d21 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 14 Mar 2023 15:34:43 -0500 Subject: [PATCH] Unquote scope strings when seen with single quotes (#778) * Unquote scope strings when seen with single quotes This change originates with a bug report about `globus session consent` on Windows Command Prompt. The single quote chars are not interpreted by the shell, so we receive them verbatim. After discussing the tradeoffs between this fix and switching to double-quotes for the scope string emission piece, choose this one. The impact is small and easy to test in unit tests. Fun fact: there's also an "easy" bypass mechanism on modern shells if anyone needs to pass leading and trailing single quotes in the future. Just pass an extra (literal) leading and trailing quote. Because there is no suite of functional tests for `globus session consent` today, none is added here. * Fix mypy failure due to list invariance --- ...14_120621_sirosen_cmdprompt_quoting_fix.md | 4 ++++ src/globus_cli/commands/session/consent.py | 8 ++++++- src/globus_cli/utils.py | 18 ++++++++++++++ tests/unit/test_utils.py | 24 ++++++++++++++++++- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 changelog.d/20230314_120621_sirosen_cmdprompt_quoting_fix.md diff --git a/changelog.d/20230314_120621_sirosen_cmdprompt_quoting_fix.md b/changelog.d/20230314_120621_sirosen_cmdprompt_quoting_fix.md new file mode 100644 index 000000000..c95445c86 --- /dev/null +++ b/changelog.d/20230314_120621_sirosen_cmdprompt_quoting_fix.md @@ -0,0 +1,4 @@ +### Bugfixes + +* Strip single quotes from scope strings passed to `globus session consent`, + fixing the behavior of this command when run from Windows Command Prompt diff --git a/src/globus_cli/commands/session/consent.py b/src/globus_cli/commands/session/consent.py index a872c7ee0..a2b48b56f 100644 --- a/src/globus_cli/commands/session/consent.py +++ b/src/globus_cli/commands/session/consent.py @@ -1,7 +1,9 @@ from __future__ import annotations import click +from globus_sdk.scopes import MutableScope +from globus_cli import utils from globus_cli.login_manager import LoginManager from globus_cli.parsing import command, no_local_server_option @@ -21,7 +23,11 @@ def session_consent(scopes: tuple[str], no_local_server: bool) -> None: This command is necessary when the CLI needs access to resources which require the user to explicitly consent to access. """ + scope_list: list[str | MutableScope] = [ + utils.unquote_cmdprompt_single_quotes(s) for s in scopes + ] manager = LoginManager() + manager.run_login_flow( no_local_server=no_local_server, local_server_message=( @@ -33,5 +39,5 @@ def session_consent(scopes: tuple[str], no_local_server: bool) -> None: "\n---" ), epilog="\nYou have successfully updated your CLI session.\n", - scopes=list(scopes), + scopes=scope_list, ) diff --git a/src/globus_cli/utils.py b/src/globus_cli/utils.py index b4446f72d..b5f35a60e 100644 --- a/src/globus_cli/utils.py +++ b/src/globus_cli/utils.py @@ -9,6 +9,24 @@ F = t.TypeVar("F", bound=t.Callable) +def unquote_cmdprompt_single_quotes(arg: str) -> str: + """ + remove leading and trailing single quotes from a string when + there is a leading and trailing single quote + + per the name of this function, it is meant to provide compatibility + with cmdprompt which interprets inputs like + + $ mycmd 'foo' + + as including the single quote chars and passes "'foo'" to our + commands + """ + if len(arg) >= 2 and arg[0] == "'" and arg[-1] == "'": + return arg[1:-1] + return arg + + def fold_decorators(f: F, decorators: list[t.Callable[[F], F]]) -> F: for deco in decorators: f = deco(f) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 77c8c7229..2f947c845 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,4 +1,10 @@ -from globus_cli.utils import format_list_of_words, format_plural_str +import pytest + +from globus_cli.utils import ( + format_list_of_words, + format_plural_str, + unquote_cmdprompt_single_quotes, +) def test_format_word_list(): @@ -16,3 +22,19 @@ def test_format_plural_str(): wordforms = {"this": "these", "command": "commands"} assert format_plural_str(fmt, wordforms, True) == "you need to run these commands" assert format_plural_str(fmt, wordforms, False) == "you need to run this command" + + +@pytest.mark.parametrize( + "arg, expect", + ( + ("foo", "foo"), + ("'foo'", "foo"), + ("'", "'"), + ("'foo", "'foo"), + ("foo'", "foo'"), + ("''", ""), + ('"foo"', '"foo"'), + ), +) +def test_unquote_cmdprompt_squote(arg, expect): + assert unquote_cmdprompt_single_quotes(arg) == expect