Hello community, here is the log from the commit of package python-certbot for openSUSE:Factory checked in at 2019-11-15 00:20:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-certbot (Old) and /work/SRC/openSUSE:Factory/.python-certbot.new.26869 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-certbot" Fri Nov 15 00:20:59 2019 rev:20 rq:748664 version:0.40.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-certbot/python-certbot.changes 2019-10-31 18:13:58.953930837 +0100 +++ /work/SRC/openSUSE:Factory/.python-certbot.new.26869/python-certbot.changes 2019-11-15 00:21:00.975935178 +0100 @@ -1,0 +2,15 @@ +Thu Nov 14 12:19:12 UTC 2019 - Marketa Calabkova <mcalabk...@suse.com> + +- update to version 0.40.1 + * --server may now be combined with --dry-run. + * --dry-run now requests fresh authorizations every time, fixing + the issue where it was prone to falsely reporting success. + * The OS detection logic again uses distro library for Linux OSes + * certbot.plugins.common.TLSSNI01 has been deprecated and will be + removed in a future release. + * CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. + * The values tls-sni and tls-sni-01 for the --preferred-challenges + flag are no longer accepted. + * Removed the flags: --agree-dev-preview, --dialog, and --apache-init-script + +------------------------------------------------------------------- Old: ---- certbot-0.39.0.tar.gz New: ---- certbot-0.40.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-certbot.spec ++++++ --- /var/tmp/diff_new_pack.vjmFuD/_old 2019-11-15 00:21:01.787934896 +0100 +++ /var/tmp/diff_new_pack.vjmFuD/_new 2019-11-15 00:21:01.787934896 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-certbot # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LLC. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,13 +18,13 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-certbot -Version: 0.39.0 +Version: 0.40.1 Release: 0 Summary: ACME client License: Apache-2.0 URL: https://github.com/certbot/certbot Source: https://files.pythonhosted.org/packages/source/c/certbot/certbot-%{version}.tar.gz -BuildRequires: %{python_module acme >= 0.29.0} +BuildRequires: %{python_module acme >= 0.40.0} BuildRequires: %{python_module configargparse >= 0.9.3} BuildRequires: %{python_module configobj} BuildRequires: %{python_module cryptography >= 1.2.3} @@ -42,7 +42,7 @@ BuildRequires: fdupes BuildRequires: python-rpm-macros BuildRequires: python2-typing -Requires: python-acme >= 0.29.0 +Requires: python-acme >= 0.40.0 Requires: python-configargparse >= 0.9.3 Requires: python-configobj Requires: python-cryptography >= 1.2.3 ++++++ certbot-0.39.0.tar.gz -> certbot-0.40.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/CHANGELOG.md new/certbot-0.40.1/CHANGELOG.md --- old/certbot-0.39.0/CHANGELOG.md 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/CHANGELOG.md 2019-11-06 03:24:51.000000000 +0100 @@ -2,6 +2,52 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.40.1 - 2019-11-05 + +### Changed + +* Added back support for Python 3.4 to Certbot components and certbot-auto due + to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. + +More details about these changes can be found on our GitHub repo. + +## 0.40.0 - 2019-11-05 + +### Added + +* + +### Changed + +* We deprecated support for Python 3.4 in Certbot and its ACME library. Support + for Python 3.4 will be removed in the next major release of Certbot. + certbot-auto users on RHEL 6 based systems will be asked to enable Software + Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can + enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based + systems will be asked to do this manually. +* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the + staging server instead of the live server when `--dry-run` is used. +* `--dry-run` now requests fresh authorizations every time, fixing the issue + where it was prone to falsely reporting success. +* Updated certbot-dns-google to depend on newer versions of + google-api-python-client and oauth2client. +* The OS detection logic again uses distro library for Linux OSes +* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a + future release. +* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. +* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no + longer accepted. +* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script` +* acme.standalone.BaseRequestHandlerWithLogging and + acme.standalone.simple_tls_sni_01_server have been deprecated and will be + removed in a future release of the library. + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.39.0 - 2019-10-01 ### Added diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/PKG-INFO new/certbot-0.40.1/PKG-INFO --- old/certbot-0.39.0/PKG-INFO 2019-10-01 21:48:41.000000000 +0200 +++ new/certbot-0.40.1/PKG-INFO 2019-11-06 03:24:52.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: certbot -Version: 0.39.0 +Version: 0.40.1 Summary: ACME client Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/__init__.py new/certbot-0.40.1/certbot/__init__.py --- old/certbot-0.39.0/certbot/__init__.py 2019-10-01 21:48:41.000000000 +0200 +++ new/certbot-0.40.1/certbot/__init__.py 2019-11-06 03:24:52.000000000 +0100 @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.39.0' +__version__ = '0.40.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/account.py new/certbot-0.40.1/certbot/account.py --- old/certbot-0.39.0/certbot/account.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/account.py 2019-11-06 03:24:51.000000000 +0100 @@ -231,12 +231,7 @@ except IOError as error: raise errors.AccountStorageError(error) - acc = Account(regr, key, meta) - if acc.id != account_id: - raise errors.AccountStorageError( - "Account ids mismatch (expected: {0}, found: {1}".format( - account_id, acc.id)) - return acc + return Account(regr, key, meta) def load(self, account_id): return self._load_for_server_path(account_id, self.config.server_path) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/auth_handler.py new/certbot-0.40.1/certbot/auth_handler.py --- old/certbot-0.39.0/certbot/auth_handler.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/auth_handler.py 2019-11-06 03:24:51.000000000 +0100 @@ -7,8 +7,9 @@ from acme import challenges from acme import messages +from acme import errors as acme_errors # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List +from acme.magic_typing import Dict, List, Tuple # pylint: enable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors @@ -97,6 +98,31 @@ return authzrs_validated + def deactivate_valid_authorizations(self, orderr): + # type: (messages.OrderResource) -> Tuple[List, List] + """ + Deactivate all `valid` authorizations in the order, so that they cannot be re-used + in subsequent orders. + :param messages.OrderResource orderr: must have authorizations filled in + :returns: tuple of list of successfully deactivated authorizations, and + list of unsuccessfully deactivated authorizations. + :rtype: tuple + """ + to_deactivate = [authzr for authzr in orderr.authorizations + if authzr.body.status == messages.STATUS_VALID] + deactivated = [] + failed = [] + + for authzr in to_deactivate: + try: + authzr = self.acme.deactivate_authorization(authzr) + deactivated.append(authzr) + except acme_errors.Error as e: + failed.append(authzr) + logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) + + return (deactivated, failed) + def _poll_authorizations(self, authzrs, max_retries, best_effort): """ Poll the ACME CA server, to wait for confirmation that authorizations have their challenges @@ -182,9 +208,6 @@ achalls.extend(self._challenge_factory(authzr, path)) - if any(isinstance(achall.chall, challenges.TLSSNI01) for achall in achalls): - logger.warning("TLS-SNI-01 is deprecated, and will stop working soon.") - return achalls def _get_chall_pref(self, domain): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/cli.py new/certbot-0.40.1/certbot/cli.py --- old/certbot-0.39.0/certbot/cli.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/cli.py 2019-11-06 03:24:51.000000000 +0100 @@ -163,24 +163,6 @@ VAR_MODIFIERS.setdefault(var, set()).update(modifiers) -def possible_deprecation_warning(config): - "A deprecation warning for users with the old, not-self-upgrading letsencrypt-auto." - if cli_command != LEAUTO: - return - if config.no_self_upgrade: - # users setting --no-self-upgrade might be hanging on a client version like 0.3.0 - # or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't - # need warnings - return - if "CERTBOT_AUTO" not in os.environ: - logger.warning("You are running with an old copy of letsencrypt-auto" - " that does not receive updates, and is less reliable than more" - " recent versions. The letsencrypt client has also been renamed" - " to Certbot. We recommend upgrading to the latest certbot-auto" - " script, or using native OS packages.") - logger.debug("Deprecation warning circumstances: %s / %s", sys.argv[0], os.environ) - - class _Default(object): """A class to use as a default to detect if a value is set by a user""" @@ -642,20 +624,25 @@ raise errors.Error( "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - possible_deprecation_warning(parsed_args) - return parsed_args def set_test_server(self, parsed_args): """We have --staging/--dry-run; perform sanity check and set config.server""" - if parsed_args.server not in (flag_default("server"), constants.STAGING_URI): - conflicts = ["--staging"] if parsed_args.staging else [] - conflicts += ["--dry-run"] if parsed_args.dry_run else [] - raise errors.Error("--server value conflicts with {0}".format( - " and ".join(conflicts))) + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server <other> | Conflict error | Use <other> | + + default_servers = (flag_default("server"), constants.STAGING_URI) - parsed_args.server = constants.STAGING_URI + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI if parsed_args.dry_run: if self.verb not in ["certonly", "renew"]: @@ -1259,20 +1246,6 @@ default=flag_default("autorenew"), dest="autorenew", help="Disable auto renewal of certificates.") - helpful.add_deprecated_argument("--agree-dev-preview", 0) - helpful.add_deprecated_argument("--dialog", 0) - - # Deprecation of tls-sni-01 related cli flags - # TODO: remove theses flags completely in few releases - class _DeprecatedTLSSNIAction(util._ShowWarning): # pylint: disable=protected-access - def __call__(self, parser, namespace, values, option_string=None): - super(_DeprecatedTLSSNIAction, self).__call__(parser, namespace, values, option_string) - namespace.https_port = values - helpful.add( - ["testing", "standalone", "apache", "nginx"], "--tls-sni-01-port", - type=int, action=_DeprecatedTLSSNIAction, help=argparse.SUPPRESS) - helpful.add_deprecated_argument("--tls-sni-01-address", 1) - # Populate the command line parameters for new style enhancements enhancements.populate_cli(helpful.add) @@ -1559,18 +1532,10 @@ :raises errors.Error: if pref_challs is invalid """ - aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"} + aliases = {"dns": "dns-01", "http": "http-01"} challs = [c.strip() for c in pref_challs] challs = [aliases.get(c, c) for c in challs] - # Ignore tls-sni-01 from the list, and generates a deprecation warning - # TODO: remove this option completely in few releases - if "tls-sni-01" in challs: - logger.warning('TLS-SNI-01 support is deprecated. This value is being dropped from the ' - 'setting of --preferred-challenges and future versions of Certbot will ' - 'error if it is included.') - challs = [chall for chall in challs if chall != "tls-sni-01"] - unrecognized = ", ".join(name for name in challs if name not in challenges.Challenge.TYPES) if unrecognized: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/client.py new/certbot-0.40.1/certbot/client.py --- old/certbot-0.39.0/certbot/client.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/client.py 2019-11-06 03:24:51.000000000 +0100 @@ -15,7 +15,7 @@ from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Optional # pylint: disable=unused-import,no-name-in-module +from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module import certbot from certbot import account @@ -366,6 +366,7 @@ return cert, chain, key, csr def _get_order_and_authorizations(self, csr_pem, best_effort): + # type: (str, bool) -> List[messages.OrderResource] """Request a new order and complete its authorizations. :param str csr_pem: A CSR in PEM format. @@ -381,6 +382,17 @@ except acme_errors.WildcardUnsupportedError: raise errors.Error("The currently selected ACME CA endpoint does" " not support issuing wildcard certificates.") + + # For a dry run, ensure we have an order with fresh authorizations + if orderr and self.config.dry_run: + deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) + if deactivated: + logger.debug("Recreating order after authz deactivations") + orderr = self.acme.new_order(csr_pem) + if failed: + logger.warning("Certbot was unable to obtain fresh authorizations for every domain" + ". The dry run will continue, but results may not be accurate.") + authzr = self.auth_handler.handle_authorizations(orderr, best_effort) return orderr.update(authorizations=authzr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/compat/filesystem.py new/certbot-0.40.1/certbot/compat/filesystem.py --- old/certbot-0.39.0/certbot/compat/filesystem.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/compat/filesystem.py 2019-11-06 03:24:51.000000000 +0100 @@ -546,13 +546,7 @@ if rights_desc['write']: flag = flag | (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - # Despite bit `512` being present in ntsecuritycon.FILE_ALL_ACCESS, it is - # not effectively applied to the file or the directory. - # As _generate_windows_flags is also used to compare two dacls, we remove - # it right now to have flags that contain only the bits effectively applied - # by Windows. - ^ 512) + ^ ntsecuritycon.FILE_GENERIC_EXECUTE) if rights_desc['execute']: flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/plugins/common.py new/certbot-0.40.1/certbot/plugins/common.py --- old/certbot-0.39.0/certbot/plugins/common.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/plugins/common.py 2019-11-06 03:24:51.000000000 +0100 @@ -2,6 +2,7 @@ import logging import re import shutil +import sys import tempfile import warnings @@ -503,3 +504,34 @@ test_configs, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if attr == 'TLSSNI01': + warnings.warn('TLSSNI01 is deprecated and will be removed soon.', + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/plugins/common_test.py new/certbot-0.40.1/certbot/plugins/common_test.py --- old/certbot-0.39.0/certbot/plugins/common_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/plugins/common_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -352,6 +352,11 @@ self.assertEqual(self.sni.get_z_domain(achall), achall.response(achall.account_key).z_domain.decode("utf-8")) + def test_warning(self): + with mock.patch('certbot.plugins.common.warnings.warn') as mock_warn: + from certbot.plugins.common import TLSSNI01 # pylint: disable=unused-variable + self.assertTrue(mock_warn.call_args[0][0].startswith('TLSSNI01')) + class InstallVersionControlledFileTest(test_util.TempDirTestCase): """Tests for certbot.plugins.common.install_version_controlled_file.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/renewal.py new/certbot-0.40.1/certbot/renewal.py --- old/certbot-0.39.0/certbot/renewal.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/renewal.py 2019-11-06 03:24:51.000000000 +0100 @@ -434,7 +434,7 @@ if should_renew(lineage_config, renewal_candidate): # Apply random sleep upon first renewal if needed if apply_random_sleep: - sleep_time = random.randint(1, 60 * 8) + sleep_time = random.uniform(1, 60 * 8) logger.info("Non-interactive renewal: random delay of %s seconds", sleep_time) time.sleep(sleep_time) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/account_test.py new/certbot-0.40.1/certbot/tests/account_test.py --- old/certbot-0.39.0/certbot/tests/account_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/account_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -1,7 +1,6 @@ """Tests for certbot.account.""" import datetime import json -import shutil import unittest import josepy as jose @@ -170,13 +169,6 @@ def test_load_non_existent_raises_error(self): self.assertRaises(errors.AccountNotFound, self.storage.load, "missing") - def test_load_id_mismatch_raises_error(self): - self.storage.save(self.acc, self.mock_client) - shutil.move(os.path.join(self.config.accounts_dir, self.acc.id), - os.path.join(self.config.accounts_dir, "x" + self.acc.id)) - self.assertRaises(errors.AccountStorageError, self.storage.load, - "x" + self.acc.id) - def _set_server(self, server): self.config.server = server from certbot.account import AccountFileStorage diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/acme_util.py new/certbot-0.40.1/certbot/tests/acme_util.py --- old/certbot-0.39.0/certbot/tests/acme_util.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/acme_util.py 2019-11-06 03:24:51.000000000 +0100 @@ -18,12 +18,10 @@ # Challenges HTTP01 = challenges.HTTP01( token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") -TLSSNI01 = challenges.TLSSNI01( - token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA")) DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a") DNS01_2 = challenges.DNS01(token=b"cafecafecafecafecafecafe0feedbac") -CHALLENGES = [HTTP01, TLSSNI01, DNS01] +CHALLENGES = [HTTP01, DNS01] def gen_combos(challbs): @@ -47,21 +45,19 @@ # Pending ChallengeBody objects -TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING) HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING) DNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING) -CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS01_P] +CHALLENGES_P = [HTTP01_P, DNS01_P] # AnnotatedChallenge objects HTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, "example.com") -TLSSNI01_A = auth_handler.challb_to_achall(TLSSNI01_P, JWK, "example.net") DNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, "example.org") DNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, "esimerkki.example.org") -ACHALLENGES = [HTTP01_A, TLSSNI01_A, DNS01_A] +ACHALLENGES = [HTTP01_A, DNS01_A] def gen_authzr(authz_status, domain, challs, statuses, combos=True): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/auth_handler_test.py new/certbot-0.40.1/certbot/tests/auth_handler_test.py --- old/certbot-0.39.0/certbot/tests/auth_handler_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/auth_handler_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -9,6 +9,7 @@ from acme import challenges from acme import client as acme_client from acme import messages +from acme import errors as acme_errors from certbot import achallenges from certbot import errors @@ -39,11 +40,11 @@ self.assertEqual( [achall.chall for achall in achalls], acme_util.CHALLENGES) - def test_one_tls_sni(self): - achalls = self.handler._challenge_factory(self.authzr, [1]) + def test_one_http(self): + achalls = self.handler._challenge_factory(self.authzr, [0]) self.assertEqual( - [achall.chall for achall in achalls], [acme_util.TLSSNI01]) + [achall.chall for achall in achalls], [acme_util.HTTP01]) def test_unrecognized(self): authzr = acme_util.gen_authzr( @@ -73,7 +74,7 @@ self.mock_auth = mock.MagicMock(name="ApacheConfigurator") - self.mock_auth.get_chall_pref.return_value = [challenges.TLSSNI01] + self.mock_auth.get_chall_pref.return_value = [challenges.HTTP01] self.mock_auth.perform.side_effect = gen_auth_resp @@ -90,7 +91,7 @@ def tearDown(self): logging.disable(logging.NOTSET) - def _test_name1_tls_sni_01_1_common(self, combos): + def _test_name1_http_01_1_common(self, combos): authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos) mock_order = mock.MagicMock(authorizations=[authzr]) @@ -109,44 +110,42 @@ self.assertTrue(mock_time.sleep.call_args_list[1][0][0] > 3) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_acme_1(self): - self._test_name1_tls_sni_01_1_common(combos=True) + def test_name1_http_01_1_acme_1(self): + self._test_name1_http_01_1_common(combos=True) - def test_name1_tls_sni_01_1_acme_2(self): + def test_name1_http_01_1_acme_2(self): self.mock_net.acme_version = 2 - self._test_name1_tls_sni_01_1_common(combos=False) + self._test_name1_http_01_1_common(combos=False) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_1(self): + def test_name1_http_01_1_dns_1_acme_1(self): self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) mock_order = mock.MagicMock(authorizations=[authzr]) authzr = self.handler.handle_authorizations(mock_order) - self.assertEqual(self.mock_net.answer_challenge.call_count, 3) + self.assertEqual(self.mock_net.answer_challenge.call_count, 2) self.assertEqual(self.mock_net.poll.call_count, 1) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall for achall in self.mock_auth.cleanup.call_args[0][0]: - self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns-01"]) + self.assertTrue(achall.typ in ["http-01", "dns-01"]) # Length of authorizations list self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_2(self): + def test_name1_http_01_1_dns_1_acme_2(self): self.mock_net.acme_version = 2 self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) @@ -160,12 +159,12 @@ self.assertEqual(self.mock_auth.cleanup.call_count, 1) cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0] self.assertEqual(len(cleaned_up_achalls), 1) - self.assertEqual(cleaned_up_achalls[0].typ, "tls-sni-01") + self.assertEqual(cleaned_up_achalls[0].typ, "http-01") # Length of authorizations list self.assertEqual(len(authzr), 1) - def _test_name3_tls_sni_01_3_common(self, combos): + def _test_name3_http_01_3_common(self, combos): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.CHALLENGES, combos=combos) @@ -186,12 +185,12 @@ self.assertEqual(len(authzr), 3) - def test_name3_tls_sni_01_3_common_acme_1(self): - self._test_name3_tls_sni_01_3_common(combos=True) + def test_name3_http_01_3_common_acme_1(self): + self._test_name3_http_01_3_common(combos=True) - def test_name3_tls_sni_01_3_common_acme_2(self): + def test_name3_http_01_3_common_acme_2(self): self.mock_net.acme_version = 2 - self._test_name3_tls_sni_01_3_common(combos=False) + self._test_name3_http_01_3_common(combos=False) def test_debug_challenges(self): zope.component.provideUtility( @@ -257,7 +256,7 @@ def _test_preferred_challenges_not_supported_common(self, combos): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos)] mock_order = mock.MagicMock(authorizations=authzrs) - self.handler.pref_challs.append(challenges.HTTP01.typ) + self.handler.pref_challs.append(challenges.DNS01.typ) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) @@ -283,7 +282,7 @@ self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_answer_error(self): self.mock_net.answer_challenge.side_effect = errors.AuthorizationError @@ -295,7 +294,7 @@ errors.AuthorizationError, self.handler.handle_authorizations, mock_order) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_incomplete_authzr_error(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -308,7 +307,7 @@ self.assertTrue('Some challenges have failed.' in str(error.exception)) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_best_effort(self): def _conditional_mock_on_poll(authzr): @@ -344,27 +343,58 @@ self.assertTrue('All challenges have failed.' in str(error.exception)) def test_validated_challenge_not_rerun(self): - # With pending challenge, we expect the challenge to be tried, and fail. + # With a pending challenge that is not supported by the plugin, we + # expect an exception to be raised. authzr = acme_util.gen_authzr( messages.STATUS_PENDING, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_PENDING], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) - # With validated challenge; we expect the challenge not be tried again, and succeed. + # With a validated challenge that is not supported by the plugin, we + # expect the challenge to not be solved again and + # handle_authorizations() to succeed. authzr = acme_util.gen_authzr( messages.STATUS_VALID, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_VALID], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.handler.handle_authorizations(mock_order) - @mock.patch("certbot.auth_handler.logger") - def test_tls_sni_logs(self, logger): - self._test_name1_tls_sni_01_1_common(combos=True) - self.assertTrue("deprecated" in logger.warning.call_args[0][0]) + def test_valid_authzrs_deactivated(self): + """When we deactivate valid authzrs in an orderr, we expect them to become deactivated + and to receive a list of deactivated authzrs in return.""" + def _mock_deactivate(authzr): + if authzr.body.status == messages.STATUS_VALID: + if authzr.body.identifier.value == "is_valid_but_will_fail": + raise acme_errors.Error("Mock deactivation ACME error") + authzb = authzr.body.update(status=messages.STATUS_DEACTIVATED) + authzr = messages.AuthorizationResource(body=authzb) + else: # pragma: no cover + raise errors.Error("Can't deactivate non-valid authz") + return authzr + + to_deactivate = [("is_valid", messages.STATUS_VALID), + ("is_pending", messages.STATUS_PENDING), + ("is_valid_but_will_fail", messages.STATUS_VALID)] + + to_deactivate = [acme_util.gen_authzr(a[1], a[0], [acme_util.HTTP01], + [a[1], False]) for a in to_deactivate] + orderr = mock.MagicMock(authorizations=to_deactivate) + + self.mock_net.deactivate_authorization.side_effect = _mock_deactivate + + authzrs, failed = self.handler.deactivate_valid_authorizations(orderr) + + self.assertEqual(self.mock_net.deactivate_authorization.call_count, 2) + self.assertEqual(len(authzrs), 1) + self.assertEqual(len(failed), 1) + self.assertEqual(authzrs[0].body.identifier.value, "is_valid") + self.assertEqual(authzrs[0].body.status, messages.STATUS_DEACTIVATED) + self.assertEqual(failed[0].body.identifier.value, "is_valid_but_will_fail") + self.assertEqual(failed[0].body.status, messages.STATUS_VALID) def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): @@ -417,9 +447,9 @@ return gen_challenge_path(challbs, preferences, combinations) def test_common_case(self): - """Given TLSSNI01 and HTTP01 with appropriate combos.""" - challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P) - prefs = [challenges.TLSSNI01, challenges.HTTP01] + """Given DNS01 and HTTP01 with appropriate combos.""" + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.DNS01, challenges.HTTP01] combos = ((0,), (1,)) # Smart then trivial dumb path test @@ -430,8 +460,8 @@ self.assertTrue(self._call(challbs[::-1], prefs, None)) def test_not_supported(self): - challbs = (acme_util.DNS01_P, acme_util.TLSSNI01_P) - prefs = [challenges.TLSSNI01] + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.HTTP01] combos = ((0, 1),) # smart path fails because no challs in perfs satisfies combos @@ -459,19 +489,19 @@ http_01 = messages.ChallengeBody(**kwargs) - kwargs["chall"] = acme_util.TLSSNI01 - tls_sni_01 = messages.ChallengeBody(**kwargs) + kwargs["chall"] = acme_util.HTTP01 + http_01 = messages.ChallengeBody(**kwargs) self.authzr1 = mock.MagicMock() self.authzr1.body.identifier.value = 'example.com' - self.authzr1.body.challenges = [http_01, tls_sni_01] + self.authzr1.body.challenges = [http_01, http_01] kwargs["error"] = messages.Error(typ="dnssec", detail="detail") - tls_sni_01_diff = messages.ChallengeBody(**kwargs) + http_01_diff = messages.ChallengeBody(**kwargs) self.authzr2 = mock.MagicMock() self.authzr2.body.identifier.value = 'foo.bar' - self.authzr2.body.challenges = [tls_sni_01_diff] + self.authzr2.body.challenges = [http_01_diff] @test_util.patch_get_utility() def test_same_error_and_domain(self, mock_zope): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/cli_test.py new/certbot-0.40.1/certbot/tests/cli_test.py --- old/certbot-0.39.0/certbot/tests/cli_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/cli_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -249,13 +249,6 @@ expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(namespace.pref_challs, expected) - # TODO: to be removed once tls-sni deprecation logic is removed - with mock.patch('certbot.cli.logger.warning') as mock_warn: - self.assertEqual(self.parse(['--preferred-challenges', 'http, tls-sni']).pref_challs, - [challenges.HTTP01.typ]) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - short_args = ['--preferred-challenges', 'jumping-over-the-moon'] # argparse.ArgumentError makes argparse print more information # to stderr and call sys.exit() @@ -272,16 +265,6 @@ self.assertTrue(namespace.must_staple) self.assertTrue(namespace.staple) - def test_no_gui(self): - args = ['renew', '--dialog'] - with mock.patch("certbot.util.logger.warning") as mock_warn: - namespace = self.parse(args) - - self.assertTrue(namespace.noninteractive_mode) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) - self.assertEqual("--dialog", mock_warn.call_args[0][1]) - def _check_server_conflict_message(self, parser_args, conflicting_args): try: self.parse(parser_args) @@ -333,16 +316,26 @@ self._assert_dry_run_flag_worked(self.parse(short_args + ['auth']), True) self._assert_dry_run_flag_worked(self.parse(short_args + ['renew']), True) - short_args += ['certonly'] - self._assert_dry_run_flag_worked(self.parse(short_args), True) + self._assert_dry_run_flag_worked(self.parse(short_args + ['certonly']), True) - short_args += '--server example.com'.split() - conflicts = ['--dry-run'] - self._check_server_conflict_message(short_args, '--dry-run') + short_args += ['certonly'] - short_args += ['--staging'] - conflicts += ['--staging'] - self._check_server_conflict_message(short_args, conflicts) + # `--dry-run --server example.com` should emit example.com + self.assertEqual(self.parse(short_args + ['--server', 'example.com']).server, + 'example.com') + + # `--dry-run --server STAGING_URI` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', constants.STAGING_URI]).server, + constants.STAGING_URI) + + # `--dry-run --server LIVE` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', cli.flag_default("server")]).server, + constants.STAGING_URI) + + # `--dry-run --server example.com --staging` should emit an error + conflicts = ['--staging'] + self._check_server_conflict_message(short_args + ['--server', 'example.com', '--staging'], + conflicts) def test_option_was_set(self): key_size_option = 'rsa_key_size' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/client_test.py new/certbot-0.40.1/certbot/tests/client_test.py --- old/certbot-0.39.0/certbot/tests/client_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/client_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -256,6 +256,7 @@ def _mock_obtain_certificate(self): self.client.auth_handler = mock.MagicMock() self.client.auth_handler.handle_authorizations.return_value = [None] + self.client.auth_handler.deactivate_valid_authorizations.return_value = ([], []) self.acme.finalize_order.return_value = self.eg_order self.acme.new_order.return_value = self.eg_order self.eg_order.update.return_value = self.eg_order @@ -360,6 +361,47 @@ mock_crypto.init_save_csr.assert_not_called() self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1) + @mock.patch("certbot.client.logger") + @mock.patch("certbot.client.crypto_util") + @mock.patch("certbot.client.acme_crypto_util") + def test_obtain_certificate_dry_run_authz_deactivations_failed(self, mock_acme_crypto, + mock_crypto, mock_log): + from acme import messages + csr = util.CSR(form="pem", file=None, data=CSR_SAN) + mock_acme_crypto.make_csr.return_value = CSR_SAN + mock_crypto.make_key.return_value = mock.sentinel.key_pem + key = util.Key(file=None, pem=mock.sentinel.key_pem) + self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain) + + self._mock_obtain_certificate() + self.client.config.dry_run = True + + # Two authzs that are already valid and should get deactivated (dry run) + authzrs = self._authzr_from_domains(["example.com", "www.example.com"]) + for authzr in authzrs: + authzr.body.status = messages.STATUS_VALID + + # One deactivation succeeds, one fails + auth_handler = self.client.auth_handler + auth_handler.deactivate_valid_authorizations.return_value = ([authzrs[0]], [authzrs[1]]) + + # Certificate should get issued despite one failed deactivation + self.eg_order.authorizations = authzrs + self.client.auth_handler.handle_authorizations.return_value = authzrs + with test_util.patch_get_utility(): + result = self.client.obtain_certificate(self.eg_domains) + self.assertEqual(result, (mock.sentinel.cert, mock.sentinel.chain, key, csr)) + self._check_obtain_certificate(1) + + # Deactivation success/failure should have been handled properly + self.assertEqual(auth_handler.deactivate_valid_authorizations.call_count, 1, + "Deactivate authorizations should be called") + self.assertEqual(self.acme.new_order.call_count, 2, + "Order should be recreated due to successfully deactivated authorizations") + mock_log.warning.assert_called_with("Certbot was unable to obtain fresh authorizations for" + " every domain. The dry run will continue, but results" + " may not be accurate.") + def _set_mock_from_fullchain(self, mock_from_fullchain): mock_cert = mock.Mock() mock_cert.encode.return_value = mock.sentinel.cert diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/compat/filesystem_test.py new/certbot-0.40.1/certbot/tests/compat/filesystem_test.py --- old/certbot-0.39.0/certbot/tests/compat/filesystem_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/compat/filesystem_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -89,8 +89,8 @@ self.assertEqual(len(system_aces), 1) self.assertEqual(len(admin_aces), 1) - self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) - self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) + self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) + self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) def test_read_flag(self): self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ) @@ -101,12 +101,10 @@ def test_write_flag(self): self._test_flag(2, (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - ^ 512)) + ^ ntsecuritycon.FILE_GENERIC_EXECUTE)) def test_full_flag(self): - self._test_flag(7, (ntsecuritycon.FILE_ALL_ACCESS - ^ 512)) + self._test_flag(7, ntsecuritycon.FILE_ALL_ACCESS) def _test_flag(self, everyone_mode, windows_flag): # Note that flag is tested against `everyone`, not `user`, because practically these unit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/main_test.py new/certbot-0.40.1/certbot/tests/main_test.py --- old/certbot-0.39.0/certbot/tests/main_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/main_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -1381,11 +1381,6 @@ jose.ComparableX509(cert), mock.ANY) - def test_agree_dev_preview_config(self): - with mock.patch('certbot.main.run') as mocked_run: - self._call(['-c', test_util.vector_path('cli.ini')]) - self.assertTrue(mocked_run.called) - @mock.patch('certbot.log.post_arg_parse_setup') def test_register(self, _): with mock.patch('certbot.main.client') as mocked_client: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/renewal_test.py new/certbot-0.40.1/certbot/tests/renewal_test.py --- old/certbot-0.39.0/certbot/tests/renewal_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/renewal_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -75,15 +75,10 @@ @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_list(self, mock_set_by_cli): mock_set_by_cli.return_value = False - # TODO: remove tls-sni and related assertions to logger.warning call once - # the deprecation logic has been removed - renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')} - with mock.patch('certbot.renewal.cli.logger.warning') as mock_warn: - self._call(self.config, renewalparams) + renewalparams = {'pref_challs': 'http-01, dns'.split(',')} + self._call(self.config, renewalparams) expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(self.config.pref_challs, expected) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_str(self, mock_set_by_cli): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/util.py new/certbot-0.40.1/certbot/tests/util.py --- old/certbot-0.39.0/certbot/tests/util.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/util.py 2019-11-06 03:24:51.000000000 +0100 @@ -94,26 +94,6 @@ return OpenSSL.crypto.load_privatekey(loader, load_vector(*names)) -def skip_unless(condition, reason): # pragma: no cover - """Skip tests unless a condition holds. - - This implements the basic functionality of unittest.skipUnless - which is only available on Python 2.7+. - - :param bool condition: If ``False``, the test will be skipped - :param str reason: the reason for skipping the test - - :rtype: callable - :returns: decorator that hides tests unless condition is ``True`` - - """ - if hasattr(unittest, "skipUnless"): - return unittest.skipUnless(condition, reason) - elif condition: - return lambda cls: cls - return lambda cls: None - - def make_lineage(config_dir, testfile): """Creates a lineage defined by testfile. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/tests/util_test.py new/certbot-0.40.1/certbot/tests/util_test.py --- old/certbot-0.39.0/certbot/tests/util_test.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/tests/util_test.py 2019-11-06 03:24:51.000000000 +0100 @@ -1,6 +1,7 @@ """Tests for certbot.util.""" import argparse import errno +import sys import unittest import mock @@ -473,74 +474,92 @@ class OsInfoTest(unittest.TestCase): """Test OS / distribution detection""" - def test_systemd_os_release(self): - from certbot.util import (get_os_info, get_systemd_os_info, - get_os_info_ua) - - with mock.patch('certbot.compat.os.path.isfile', return_value=True): - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[0], 'systemdos') - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[1], '42') - self.assertEqual(get_systemd_os_info(os.devnull), ("", "")) - self.assertEqual(get_os_info_ua( - test_util.vector_path("os-release")), "SystemdOS") - with mock.patch('certbot.compat.os.path.isfile', return_value=False): - self.assertEqual(get_systemd_os_info(), ("", "")) - - def test_systemd_os_release_like(self): - from certbot.util import get_systemd_os_like - - with mock.patch('certbot.compat.os.path.isfile', return_value=True): - id_likes = get_systemd_os_like(test_util.vector_path( - "os-release")) - self.assertEqual(len(id_likes), 3) - self.assertTrue("debian" in id_likes) + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_systemd_os_release_like(self, m_distro): + import certbot.util as cbutil + m_distro.like.return_value = "first debian third" + id_likes = cbutil.get_systemd_os_like() + self.assertEqual(len(id_likes), 3) + self.assertTrue("debian" in id_likes) + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info_ua(self, m_distro): + import certbot.util as cbutil + with mock.patch('platform.system_alias', + return_value=('linux', '42', '42')): + m_distro.name.return_value = "" + m_distro.linux_distribution.return_value = ("something", "1.0", "codename") + cbutil.get_python_os_info(pretty=True) + self.assertEqual(cbutil.get_os_info_ua(), + " ".join(cbutil.get_python_os_info(pretty=True))) + + m_distro.name.return_value = "whatever" + self.assertEqual(cbutil.get_os_info_ua(), "whatever") + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info(self, m_distro): + import certbot.util as cbutil + with mock.patch("platform.system") as mock_platform: + m_distro.linux_distribution.return_value = ("name", "version", 'x') + mock_platform.return_value = "linux" + self.assertEqual(cbutil.get_os_info(), ("name", "version")) + + m_distro.linux_distribution.return_value = ("something", "else") + self.assertEqual(cbutil.get_os_info(), ("something", "else")) + + @mock.patch("warnings.warn") + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_systemd_os_info_deprecation(self, _, mock_warn): + import certbot.util as cbutil + cbutil.get_systemd_os_info() + self.assertTrue(mock_warn.called) @mock.patch("certbot.util.subprocess.Popen") def test_non_systemd_os_info(self, popen_mock): - from certbot.util import (get_os_info, get_python_os_info, - get_os_info_ua) - with mock.patch('certbot.compat.os.path.isfile', return_value=False): + import certbot.util as cbutil + with mock.patch('certbot.util._USE_DISTRO', False): with mock.patch('platform.system_alias', return_value=('NonSystemD', '42', '42')): - self.assertEqual(get_os_info()[0], 'nonsystemd') - self.assertEqual(get_os_info_ua(), - " ".join(get_python_os_info())) + self.assertEqual(cbutil.get_python_os_info()[0], 'nonsystemd') with mock.patch('platform.system_alias', return_value=('darwin', '', '')): comm_mock = mock.Mock() comm_attrs = {'communicate.return_value': - ('42.42.42', 'error')} + ('42.42.42', 'error')} comm_mock.configure_mock(**comm_attrs) popen_mock.return_value = comm_mock - self.assertEqual(get_os_info()[0], 'darwin') - self.assertEqual(get_os_info()[1], '42.42.42') - - with mock.patch('platform.system_alias', - return_value=('linux', '', '')): - with mock.patch('platform.linux_distribution', - side_effect=AttributeError, - create=True): - with mock.patch('distro.linux_distribution', - return_value=('', '', '')): - self.assertEqual(get_python_os_info(), ("linux", "")) - - with mock.patch('distro.linux_distribution', - return_value=('testdist', '42', '')): - self.assertEqual(get_python_os_info(), ("testdist", "42")) + self.assertEqual(cbutil.get_python_os_info()[0], 'darwin') + self.assertEqual(cbutil.get_python_os_info()[1], '42.42.42') with mock.patch('platform.system_alias', return_value=('freebsd', '9.3-RC3-p1', '')): - self.assertEqual(get_python_os_info(), ("freebsd", "9")) + self.assertEqual(cbutil.get_python_os_info(), ("freebsd", "9")) with mock.patch('platform.system_alias', return_value=('windows', '', '')): with mock.patch('platform.win32_ver', return_value=('4242', '95', '2', '')): - self.assertEqual(get_python_os_info(), - ("windows", "95")) + self.assertEqual(cbutil.get_python_os_info(), + ("windows", "95")) + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_notfound(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('', '', '') + self.assertEqual(cbutil.get_python_os_info()[0], "linux") + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_custom(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('testdist', '42', '') + self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42")) class AtexitRegisterTest(unittest.TestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot/util.py new/certbot-0.40.1/certbot/util.py --- old/certbot-0.39.0/certbot/util.py 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/certbot/util.py 2019-11-06 03:24:51.000000000 +0100 @@ -12,9 +12,10 @@ import re import socket import subprocess +import sys +import warnings import configargparse -import distro import six from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module @@ -25,6 +26,12 @@ from certbot.compat import os from certbot.compat import filesystem +if sys.platform.startswith('linux'): + import distro + _USE_DISTRO = True +else: + _USE_DISTRO = False + logger = logging.getLogger(__name__) @@ -277,77 +284,59 @@ logger.debug('Not suggesting name "%s"', name, exc_info=True) return filtered_names - -def get_os_info(filepath="/etc/os-release"): +def get_os_info(): """ Get OS name and version - :param str filepath: File path of os-release file :returns: (os_name, os_version) :rtype: `tuple` of `str` """ - if os.path.isfile(filepath): - # Systemd os-release parsing might be viable - os_name, os_version = get_systemd_os_info(filepath=filepath) - if os_name: - return os_name, os_version - - # Fallback to platform module - return get_python_os_info() - + return get_python_os_info(pretty=False) -def get_os_info_ua(filepath="/etc/os-release"): +def get_os_info_ua(): """ Get OS name and version string for User Agent - :param str filepath: File path of os-release file :returns: os_ua :rtype: `str` """ + if _USE_DISTRO: + os_info = distro.name(pretty=True) - if os.path.isfile(filepath): - os_ua = get_var_from_file("PRETTY_NAME", filepath=filepath) - if not os_ua: - os_ua = get_var_from_file("NAME", filepath=filepath) - if os_ua: - return os_ua - - # Fallback - return " ".join(get_python_os_info()) - + if not _USE_DISTRO or not os_info: + return " ".join(get_python_os_info(pretty=True)) + return os_info -def get_systemd_os_info(filepath="/etc/os-release"): +def get_systemd_os_info(): """ Parse systemd /etc/os-release for distribution information - :param str filepath: File path of os-release file :returns: (os_name, os_version) :rtype: `tuple` of `str` """ - os_name = get_var_from_file("ID", filepath=filepath) - os_version = get_var_from_file("VERSION_ID", filepath=filepath) - - return (os_name, os_version) + warnings.warn( + "The get_sytemd_os_like() function is deprecated and will be removed in " + "a future release.", DeprecationWarning, stacklevel=2) + return get_os_info()[:2] - -def get_systemd_os_like(filepath="/etc/os-release"): +def get_systemd_os_like(): """ Get a list of strings that indicate the distribution likeness to other distributions. - :param str filepath: File path of os-release file :returns: List of distribution acronyms :rtype: `list` of `str` """ - return get_var_from_file("ID_LIKE", filepath).split(" ") - + if _USE_DISTRO: + return distro.like().split(" ") + return [] def get_var_from_file(varname, filepath="/etc/os-release"): """ - Get single value from systemd /etc/os-release + Get single value from a file formatted like systemd /etc/os-release :param str varname: Name of variable to fetch :param str filepath: File path of os-release file @@ -367,7 +356,6 @@ return _normalize_string(line.strip()[len(var_string):]) return "" - def _normalize_string(orig): """ Helper function for get_var_from_file() to remove quotes @@ -375,12 +363,13 @@ """ return orig.replace('"', '').replace("'", "").strip() - -def get_python_os_info(): +def get_python_os_info(pretty=False): """ Get Operating System type/distribution and major version using python platform module + :param bool pretty: If the returned OS name should be in longer (pretty) form + :returns: (os_name, os_version) :rtype: `tuple` of `str` """ @@ -391,8 +380,8 @@ ) os_type, os_ver, _ = info os_type = os_type.lower() - if os_type.startswith('linux'): - info = _get_linux_distribution() + if os_type.startswith('linux') and _USE_DISTRO: + info = distro.linux_distribution(pretty) # On arch, distro.linux_distribution() is reportedly ('','',''), # so handle it defensively if info[0]: @@ -424,14 +413,6 @@ os_ver = '' return os_type, os_ver -def _get_linux_distribution(): - """Gets the linux distribution name from the underlying OS""" - - try: - return platform.linux_distribution() - except AttributeError: - return distro.linux_distribution() - # Just make sure we don't get pwned... Make sure that it also doesn't # start with a period or have two consecutive periods <- this needs to # be done in addition to the regex diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot.egg-info/PKG-INFO new/certbot-0.40.1/certbot.egg-info/PKG-INFO --- old/certbot-0.39.0/certbot.egg-info/PKG-INFO 2019-10-01 21:48:41.000000000 +0200 +++ new/certbot-0.40.1/certbot.egg-info/PKG-INFO 2019-11-06 03:24:52.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: certbot -Version: 0.39.0 +Version: 0.40.1 Summary: ACME client Home-page: https://github.com/letsencrypt/letsencrypt Author: Certbot Project diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/certbot.egg-info/requires.txt new/certbot-0.40.1/certbot.egg-info/requires.txt --- old/certbot-0.39.0/certbot.egg-info/requires.txt 2019-10-01 21:48:41.000000000 +0200 +++ new/certbot-0.40.1/certbot.egg-info/requires.txt 2019-11-06 03:24:52.000000000 +0100 @@ -1,4 +1,4 @@ -acme>=0.29.0 +acme>=0.40.0 ConfigArgParse>=0.9.3 configobj cryptography>=1.2.3 @@ -13,7 +13,7 @@ zope.interface [:sys_platform == "win32"] -pywin32>=224 +pywin32>=225 [dev] astroid==1.6.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/docs/cli-help.txt new/certbot-0.40.1/docs/cli-help.txt --- old/certbot-0.39.0/docs/cli-help.txt 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/docs/cli-help.txt 2019-11-06 03:24:51.000000000 +0100 @@ -113,7 +113,7 @@ case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/0.38.0 + "". (default: CertbotACMEClient/0.40.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/docs/contributing.rst new/certbot-0.40.1/docs/contributing.rst --- old/certbot-0.39.0/docs/contributing.rst 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/docs/contributing.rst 2019-11-06 03:24:51.000000000 +0100 @@ -197,15 +197,20 @@ Code components and layout ========================== +The following components of the Certbot repository are distributed to users: + acme contains all protocol specific code certbot main client code certbot-apache and certbot-nginx client code to configure specific web servers -certbot.egg-info - configuration for packaging Certbot - +certbot-dns-* + client code to configure DNS providers +certbot-auto and letsencrypt-auto + shell scripts to install Certbot and its dependencies on UNIX systems +windows installer + Installs Certbot on Windows and is built using the files in windows-installer/ Plugin-architecture ------------------- @@ -234,7 +239,7 @@ Authenticators are plugins that prove control of a domain name by solving a challenge provided by the ACME server. ACME currently defines several types of -challenges: HTTP, TLS-SNI (deprecated), TLS-ALPR, and DNS, represented by classes in `acme.challenges`. +challenges: HTTP, TLS-ALPN, and DNS, represented by classes in `acme.challenges`. An authenticator plugin should implement support for at least one challenge type. An Authenticator indicates which challenges it supports by implementing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/docs/using.rst new/certbot-0.40.1/docs/using.rst --- old/certbot-0.39.0/docs/using.rst 2019-10-01 21:48:40.000000000 +0200 +++ new/certbot-0.40.1/docs/using.rst 2019-11-06 03:24:51.000000000 +0100 @@ -917,8 +917,9 @@ of Certbot. Certificate specific configuration choices should be set in the ``.conf`` files that can be found in ``/etc/letsencrypt/renewal``. -By default no cli.ini file is created, after creating one -it is possible to specify the location of this configuration file with +By default no cli.ini file is created (though it may exist already if you installed Certbot +via a package manager, for instance). +After creating one it is possible to specify the location of this configuration file with ``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An example configuration file is shown below: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.39.0/setup.py new/certbot-0.40.1/setup.py --- old/certbot-0.39.0/setup.py 2019-10-01 21:48:41.000000000 +0200 +++ new/certbot-0.40.1/setup.py 2019-11-06 03:24:52.000000000 +0100 @@ -34,7 +34,7 @@ # specified here to avoid masking the more specific request requirements in # acme. See https://github.com/pypa/pip/issues/988 for more info. install_requires = [ - 'acme>=0.29.0', + 'acme>=0.40.0', # We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but # saying so here causes a runtime error against our temporary fork of 0.9.3 # in which we added 2.6 support (see #2243), so we relax the requirement. @@ -59,7 +59,7 @@ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. -pywin32_req = 'pywin32>=224' +pywin32_req = 'pywin32>=225' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py if StrictVersion(setuptools_version) >= StrictVersion('36.2'): install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: