From ced0e5abe4ff266a5f572e5411c5d18c3f76cc08 Mon Sep 17 00:00:00 2001 From: James Kunstle Date: Jun 21 2021 21:04:53 +0000 Subject: Pagure / DistGit token config-file cli-interface Added an interface that allows a user to update relevant expired API keys via the command line interface. Files: cli.py: register_set_pagure_token cli.py: register_set_distgit_token cli.py: set_pagure_token cli.py: set_distgit_token cli.py: _set_token cli.py: _check_token Tests: test_cli.py: TestSetToken (class) test_cli.py: get_cli test_cli.py: test_token_input_mixed_lowercase_numerical test_cli.py: test_token_input_none test_cli.py: test_token_input_lowercase test_cli.py: test_token_input_too_short test_cli.py: test_token_input_too_long Fixes #192 Jira: RHELCMP-58 Signed-off-by: James Kunstle --- diff --git a/conf/bash-completion/fedpkg.bash b/conf/bash-completion/fedpkg.bash index 83c4d66..a7a71f9 100644 --- a/conf/bash-completion/fedpkg.bash +++ b/conf/bash-completion/fedpkg.bash @@ -35,7 +35,8 @@ _fedpkg() module-scratch-build \ new new-sources patch prep pull push retire request-branch request-repo \ request-tests-repo request-side-tag list-side-tags remove-side-tag \ - scratch-build sources srpm switch-branch tag unused-patches update upload \ + scratch-build set-distgit-token set-pagure-token sources srpm switch-branch \ + tag unused-patches update upload \ verify-files verrel override fork" # parse main options and get command @@ -95,7 +96,7 @@ _fedpkg() local after= after_more= case $command in - help|gimmespec|gitbuildhash|giturl|new|push|unused-patches|verrel) + help|gimmespec|gitbuildhash|giturl|new|push|unused-patches|verrel|set-distgit-token|set-pagure-token) ;; build) options="--nowait --background --skip-tag --scratch --skip-remote-rules-validation --fail-fast" diff --git a/fedpkg/cli.py b/fedpkg/cli.py index 2e9b4f9..1dda1a7 100644 --- a/fedpkg/cli.py +++ b/fedpkg/cli.py @@ -25,7 +25,7 @@ from datetime import datetime import pkg_resources import six from six.moves import configparser -from six.moves.configparser import NoOptionError, NoSectionError +from six.moves.configparser import NoOptionError, NoSectionError, ConfigParser from six.moves.urllib_parse import urlparse from fedpkg.bugzilla import BugzillaClient @@ -122,6 +122,8 @@ class fedpkgClient(cliClient): self.register_request_branch() self.register_do_fork() self.register_override() + self.register_set_distgit_token() + self.register_set_pagure_token() # Target registry goes here def register_update(self): @@ -261,16 +263,20 @@ class fedpkgClient(cliClient): pagure_section = '{0}.pagure'.format(self.name) pagure_url = config_get_safely(self.config, pagure_section, 'url') pagure_url_parsed = urlparse(pagure_url).netloc + description = textwrap.dedent(''' Request a new dist-git repository Before the operation, you need to generate a pagure.io API token at: https://{1}/settings/token/new - ACL required: - "Create a new ticket" + ACL required: + "Create a new ticket" + + Update your token with the following command: + fedpkg set-pagure-token - Save the API token to local user configuration located at: + Command saves token to fedpkg config file: ~/.config/rpkg/{0}.conf For example: @@ -450,6 +456,7 @@ class fedpkgClient(cliClient): help_msg = 'Create a new fork of the current repository' distgit_section = '{0}.distgit'.format(self.name) distgit_api_base_url = config_get_safely(self.config, distgit_section, "apibaseurl") + description = textwrap.dedent(''' Create a new fork of the current repository @@ -459,7 +466,10 @@ class fedpkgClient(cliClient): ACL required: "Fork a project" - Save the API token to local user configuration located at: + Update your token with the following command: + fedpkg set-distgit-token + + Command saves token to fedpkg config file: ~/.config/rpkg/{0}.conf For example: @@ -508,6 +518,38 @@ class fedpkgClient(cliClient): parser.set_defaults(command=self.show_releases_info) + def register_set_distgit_token(self): + help_msg = \ + 'Updates the fedpkg.distgit API token in ~/.config/rpkg/{0}.conf file.\n\n\ + Tokens are of length 64 and contain only uppercase and numerical values.'\ + .format(self.name) + + parser = self.subparsers.add_parser( + 'set-distgit-token', + help=help_msg, + description=help_msg) + parser.add_argument( + 'token', + help='The new API token.') + + parser.set_defaults(command=self.set_distgit_token) + + def register_set_pagure_token(self): + help_msg = \ + 'Updates the fedpkg.pagure API token in ~/.config/rpkg/{0}.conf file.\n\n\ + Tokens are of length 64 and contain only uppercase and numerical values.'\ + .format(self.name) + + parser = self.subparsers.add_parser( + 'set-pagure-token', + help=help_msg, + description=help_msg) + parser.add_argument( + 'token', + help='The new API token.') + + parser.set_defaults(command=self.set_pagure_token) + def register_override(self): """Register command line parser for subcommand override @@ -1318,6 +1360,73 @@ class fedpkgClient(cliClient): print('Fedora: {0}'.format(_join(releases['fedora']))) print('EPEL: {0}'.format(_join(releases['epel']))) + def _check_token(self, token, token_type): + + if token is None: + self.log.error("ERROR: No input.") + return False + + match = re.search(r'^\s*[A-Z0-9]{64}\s*$', token) + if match is None: + self.log.error("ERROR: Token is not properly formatted.") + return False + else: + return True + + def _set_token(self, token_type): + + # Pop token off of the parse stack + TOKEN = self.args.token + + # Get the path to the fedpkg config file. + PATH = os.path.join(os.path.expanduser('~'), + '.config', + 'rpkg', + '{0}.conf'.format(self.name)) + + # load new config parser + local_config = ConfigParser() + local_config.read(PATH) + + # Ensure that user config file exists. + if not os.path.isfile(PATH): + self.log.error("ERROR: User config file not found at: {0}\n".format(PATH)) + return + + # Check that the user passed a valid token + if self._check_token(TOKEN, token_type): + + print("updating config") + + # Update the token in the config object. + section = "{0}.{1}".format(self.name, token_type) + + # add the section if it doesn't already exist + if section not in local_config: + local_config.add_section(section) + + # set the distgit / pagure urls as needed (might already be set) + if(token_type == "pagure"): + local_config.set(section, "url", "https://pagure.io/") + else: + local_config.set(section, "apibaseurl", "https://src.fedoraproject.org") + + # update the token + local_config.set(section, "token", TOKEN) + + # Write the config to the user's config file. + with open(PATH, "w") as fp: + try: + local_config.write(fp) + except configparser.Error: + self.log.error("ERROR: Could not write to user config file.") + + def set_pagure_token(self): + self._set_token("pagure") + + def set_distgit_token(self): + self._set_token("distgit") + def retire(self): """ Runs the rpkg retire command after check. Check includes reading the state diff --git a/test/test_cli.py b/test/test_cli.py index f41d1ef..7bc5aef 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -2406,3 +2406,100 @@ class TestRetire(CliTestCase): self.retire_release("epel7", "pending") self.retire_release("epel7", None) self.retire_release("epel8", None) + + +class TestSetToken(CliTestCase): + """ + Test the set-x-token cli command. Pagure / Distgit have the same tests. + """ + + def get_cli(self, cli_cmd, name='fedpkg', cfg=None): + with patch('sys.argv', new=cli_cmd): + return self.new_cli(name=name, cfg=cfg) + + # Test: using lowercase characters rather than upper-case. + def test_token_input_mixed_lowercase_numerical(self): + + LOWERCASE_TOKEN = "".join(["x" for _ in range(64)]) + NUMERICAL_TOKEN = "".join([str(i % 10) for i in range(64)]) + + MIXED_LOWERCASE_NUMERICAL_TOKEN = "" + for i in range(32): + MIXED_LOWERCASE_NUMERICAL_TOKEN += LOWERCASE_TOKEN[i] + MIXED_LOWERCASE_NUMERICAL_TOKEN += NUMERICAL_TOKEN[i] + + cli_cmd = ['fedpkg', 'set-pagure-token', MIXED_LOWERCASE_NUMERICAL_TOKEN] + cli = self.get_cli(cli_cmd) + + try: + cli.set_pagure_token() + except rpkgError as error: + expected_error = "ERROR: Token is not properly formatted." + self.assertEqual(error, expected_error) + + # Test: no input, none input. + def test_token_input_none(self): + + cli_cmd = ["fedpkg", "set-pagure-token", None] + cli = self.get_cli(cli_cmd) + + try: + cli.set_pagure_token() + except rpkgError as error: + expected_error = "ERROR: No input." + self.assertEqual(error, expected_error) + + """ + EXAMPLE: + cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path, + '--name', 'nethack', 'request-branch', 'f27', + '--all-releases'] + cli = self.get_cli(cli_cmd) + expected_error = \ + 'You cannot specify a branch with the "--all-releases" option' + try: + cli.request_branch() + assert False, 'rpkgError not raised' + except rpkgError as error: + self.assertEqual(str(error), expected_error) + """ + + # Test: input is only lowercase. + def test_token_input_lowercase(self): + + LOWERCASE_TOKEN = "".join(["x" for _ in range(64)]) + + cli_cmd = ['fedpkg', 'set-pagure-token', LOWERCASE_TOKEN] + cli = self.get_cli(cli_cmd) + + try: + cli.set_pagure_token() + except rpkgError as error: + expected_error = "ERROR: Token is not properly formatted." + self.assertEqual(error, expected_error) + + def test_token_input_too_short(self): + + SHORT_TOKEN = "".join(['x' for _ in range(63)]) + + cli_cmd = ['fedpkg', 'set-pagure-token', SHORT_TOKEN] + cli = self.get_cli(cli_cmd) + + try: + cli.set_pagure_token() + except rpkgError as error: + expected_error = "ERROR: Token is not properly formatted." + self.assertEqual(error, expected_error) + + def test_token_input_too_long(self): + + LONG_TOKEN = "".join(['x' for _ in range(65)]) + + cli_cmd = ['fedpkg', 'set-pagure-token', LONG_TOKEN] + cli = self.get_cli(cli_cmd) + + try: + cli.set_pagure_token() + except rpkgError as error: + expected_error = "ERROR: Token is not properly formatted." + self.assertEqual(error, expected_error)