Prepare certbot module for mypy check untyped defs (#6005)

* Prepare certbot module for mypy check untyped defs

* Fix #5952

* Bump mypy to version 0.600 and fix associated bugs

* Fix pylint bugs after introducing mypy

* Implement Brad's suggestions

* Reenabling pylint and adding nginx mypy back
This commit is contained in:
Dmitry Figol
2018-05-18 06:28:17 -07:00
committed by Brad Warren
parent 250c0d6691
commit 36dfd06503
36 changed files with 316 additions and 214 deletions
+2 -2
View File
@@ -147,9 +147,9 @@ class KeyAuthorizationChallenge(_TokenChallenge):
:param response_cls: Subclass of `KeyAuthorizationChallengeResponse` :param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
that will be used to generate `response`. that will be used to generate `response`.
:param str typ: type of the challenge
""" """
typ = NotImplemented
response_cls = NotImplemented response_cls = NotImplemented
thumbprint_hash_function = ( thumbprint_hash_function = (
KeyAuthorizationChallengeResponse.thumbprint_hash_function) KeyAuthorizationChallengeResponse.thumbprint_hash_function)
+16 -6
View File
@@ -12,7 +12,8 @@ import josepy as jose
from acme import errors from acme import errors
# pylint: disable=unused-import, no-name-in-module # pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Callable, Text, Union from acme.magic_typing import Callable, Union, Tuple, Optional
# pylint: enable=unused-import, no-name-in-module
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -135,14 +136,23 @@ def probe_sni(name, host, port=443, timeout=300,
socket_kwargs = {'source_address': source_address} socket_kwargs = {'source_address': source_address}
host_protocol_agnostic = None if host == '::' or host == '0' else host host_protocol_agnostic = host
if host == '::' or host == '0':
# https://github.com/python/typeshed/pull/2136
# while PR is not merged, we need to ignore
host_protocol_agnostic = None
try: try:
# pylint: disable=star-args # pylint: disable=star-args
logger.debug("Attempting to connect to %s:%d%s.", host_protocol_agnostic, port, logger.debug(
" from {0}:{1}".format(source_address[0], source_address[1]) if \ "Attempting to connect to %s:%d%s.", host_protocol_agnostic, port,
socket_kwargs else "") " from {0}:{1}".format(
sock = socket.create_connection((host_protocol_agnostic, port), **socket_kwargs) source_address[0],
source_address[1]
) if socket_kwargs else ""
)
socket_tuple = (host_protocol_agnostic, port) # type: Tuple[Optional[str], int]
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore
except socket.error as error: except socket.error as error:
raise errors.Error(error) raise errors.Error(error)
+4 -1
View File
@@ -8,6 +8,9 @@ class TypingClass(object):
try: try:
# mypy doesn't respect modifying sys.modules # mypy doesn't respect modifying sys.modules
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
# pylint: disable=unused-import
from typing import Collection, IO # type: ignore
# pylint: enable=unused-import
except ImportError: except ImportError:
sys.modules[__name__] = TypingClass() sys.modules[__name__] = TypingClass()
+13 -11
View File
@@ -8,7 +8,9 @@ import zope.component
from acme import challenges from acme import challenges
from acme import messages from acme import messages
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import DefaultDict, Dict, List, Set, Collection
# pylint: enable=unused-import, no-name-in-module
from certbot import achallenges from certbot import achallenges
from certbot import errors from certbot import errors
from certbot import error_handler from certbot import error_handler
@@ -117,7 +119,7 @@ class AuthHandler(object):
def _solve_challenges(self, aauthzrs): def _solve_challenges(self, aauthzrs):
"""Get Responses for challenges from authenticators.""" """Get Responses for challenges from authenticators."""
resp = [] resp = [] # type: Collection[acme.challenges.ChallengeResponse]
all_achalls = self._get_all_achalls(aauthzrs) all_achalls = self._get_all_achalls(aauthzrs)
try: try:
if all_achalls: if all_achalls:
@@ -133,10 +135,9 @@ class AuthHandler(object):
def _get_all_achalls(self, aauthzrs): def _get_all_achalls(self, aauthzrs):
"""Return all active challenges.""" """Return all active challenges."""
all_achalls = [] all_achalls = [] # type: Collection[challenges.ChallengeResponse]
for aauthzr in aauthzrs: for aauthzr in aauthzrs:
all_achalls.extend(aauthzr.achalls) all_achalls.extend(aauthzr.achalls)
return all_achalls return all_achalls
def _respond(self, aauthzrs, resp, best_effort): def _respond(self, aauthzrs, resp, best_effort):
@@ -146,7 +147,8 @@ class AuthHandler(object):
""" """
# TODO: chall_update is a dirty hack to get around acme-spec #105 # TODO: chall_update is a dirty hack to get around acme-spec #105
chall_update = dict() chall_update = dict() \
# type: Dict[int, List[achallenges.KeyAuthorizationAnnotatedChallenge]]
self._send_responses(aauthzrs, resp, chall_update) self._send_responses(aauthzrs, resp, chall_update)
# Check for updated status... # Check for updated status...
@@ -198,7 +200,7 @@ class AuthHandler(object):
while indices_to_check and rounds < max_rounds: while indices_to_check and rounds < max_rounds:
# TODO: Use retry-after... # TODO: Use retry-after...
time.sleep(min_sleep) time.sleep(min_sleep)
all_failed_achalls = set() all_failed_achalls = set() # type: Set[achallenges.KeyAuthorizationAnnotatedChallenge]
for index in indices_to_check: for index in indices_to_check:
comp_achalls, failed_achalls = self._handle_check( comp_achalls, failed_achalls = self._handle_check(
aauthzrs, index, chall_update[index]) aauthzrs, index, chall_update[index])
@@ -424,7 +426,7 @@ def _find_smart_path(challbs, preferences, combinations):
# max_cost is now equal to sum(indices) + 1 # max_cost is now equal to sum(indices) + 1
best_combo = [] best_combo = None
# Set above completing all of the available challenges # Set above completing all of the available challenges
best_combo_cost = max_cost best_combo_cost = max_cost
@@ -479,7 +481,7 @@ def _report_no_chall_path(challbs):
msg += ( msg += (
" You may need to use an authenticator " " You may need to use an authenticator "
"plugin that can do challenges over DNS.") "plugin that can do challenges over DNS.")
logger.fatal(msg) logger.critical(msg)
raise errors.AuthorizationError(msg) raise errors.AuthorizationError(msg)
@@ -522,11 +524,11 @@ def _report_failed_challs(failed_achalls):
:class:`certbot.achallenges.AnnotatedChallenge`. :class:`certbot.achallenges.AnnotatedChallenge`.
""" """
problems = dict() problems = collections.defaultdict(list)\
# type: DefaultDict[str, List[achallenges.KeyAuthorizationAnnotatedChallenge]]
for achall in failed_achalls: for achall in failed_achalls:
if achall.error: if achall.error:
problems.setdefault(achall.error.typ, []).append(achall) problems[achall.error.typ].append(achall)
reporter = zope.component.getUtility(interfaces.IReporter) reporter = zope.component.getUtility(interfaces.IReporter)
for achalls in six.itervalues(problems): for achalls in six.itervalues(problems):
reporter.add_message( reporter.add_message(
+3 -2
View File
@@ -7,6 +7,7 @@ import re
import traceback import traceback
import zope.component import zope.component
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import crypto_util from certbot import crypto_util
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -226,7 +227,7 @@ def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func
def find_matches(candidate_lineage, return_value, acceptable_matches): def find_matches(candidate_lineage, return_value, acceptable_matches):
"""Returns a list of matches using _search_lineages.""" """Returns a list of matches using _search_lineages."""
acceptable_matches = [func(candidate_lineage) for func in acceptable_matches] acceptable_matches = [func(candidate_lineage) for func in acceptable_matches]
acceptable_matches_rv = [] acceptable_matches_rv = [] # type: List[str]
for item in acceptable_matches: for item in acceptable_matches:
if isinstance(item, list): if isinstance(item, list):
acceptable_matches_rv += item acceptable_matches_rv += item
@@ -340,7 +341,7 @@ def _report_human_readable(config, parsed_certs):
def _describe_certs(config, parsed_certs, parse_failures): def _describe_certs(config, parsed_certs, parse_failures):
"""Print information about the certs we know about""" """Print information about the certs we know about"""
out = [] out = [] # type: List[str]
notify = out.append notify = out.append
+34 -20
View File
@@ -12,10 +12,14 @@ import sys
import configargparse import configargparse
import six import six
import zope.component import zope.component
import zope.interface
from zope.interface import interfaces as zope_interfaces from zope.interface import interfaces as zope_interfaces
from acme import challenges from acme import challenges
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Any, Dict, Optional
# pylint: enable=unused-import, no-name-in-module
import certbot import certbot
@@ -33,7 +37,7 @@ import certbot.plugins.selection as plugin_selection
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Global, to save us from a lot of argument passing within the scope of this module # Global, to save us from a lot of argument passing within the scope of this module
helpful_parser = None helpful_parser = None # type: Optional[HelpfulArgumentParser]
# For help strings, figure out how the user ran us. # For help strings, figure out how the user ran us.
# When invoked from letsencrypt-auto, sys.argv[0] is something like: # When invoked from letsencrypt-auto, sys.argv[0] is something like:
@@ -196,17 +200,17 @@ def set_by_cli(var):
(CLI or config file) including if the user explicitly set it to the (CLI or config file) including if the user explicitly set it to the
default. Returns False if the variable was assigned a default value. default. Returns False if the variable was assigned a default value.
""" """
detector = set_by_cli.detector detector = set_by_cli.detector # type: ignore
if detector is None: if detector is None and helpful_parser is not None:
# Setup on first run: `detector` is a weird version of config in which # Setup on first run: `detector` is a weird version of config in which
# the default value of every attribute is wrangled to be boolean-false # the default value of every attribute is wrangled to be boolean-false
plugins = plugins_disco.PluginsRegistry.find_all() plugins = plugins_disco.PluginsRegistry.find_all()
# reconstructed_args == sys.argv[1:], or whatever was passed to main() # reconstructed_args == sys.argv[1:], or whatever was passed to main()
reconstructed_args = helpful_parser.args + [helpful_parser.verb] reconstructed_args = helpful_parser.args + [helpful_parser.verb]
detector = set_by_cli.detector = prepare_and_parse_args( detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore
plugins, reconstructed_args, detect_defaults=True) plugins, reconstructed_args, detect_defaults=True)
# propagate plugin requests: eg --standalone modifies config.authenticator # propagate plugin requests: eg --standalone modifies config.authenticator
detector.authenticator, detector.installer = ( detector.authenticator, detector.installer = ( # type: ignore
plugin_selection.cli_plugin_requests(detector)) plugin_selection.cli_plugin_requests(detector))
if not isinstance(getattr(detector, var), _Default): if not isinstance(getattr(detector, var), _Default):
@@ -220,7 +224,10 @@ def set_by_cli(var):
return True return True
return False return False
# static housekeeping var # static housekeeping var
# functions attributed are not supported by mypy
# https://github.com/python/mypy/issues/2087
set_by_cli.detector = None # type: ignore set_by_cli.detector = None # type: ignore
@@ -236,8 +243,10 @@ def has_default_value(option, value):
:rtype: bool :rtype: bool
""" """
return (option in helpful_parser.defaults and if helpful_parser is not None:
helpful_parser.defaults[option] == value) return (option in helpful_parser.defaults and
helpful_parser.defaults[option] == value)
return False
def option_was_set(option, value): def option_was_set(option, value):
@@ -254,11 +263,12 @@ def option_was_set(option, value):
def argparse_type(variable): def argparse_type(variable):
"Return our argparse type function for a config variable (default: str)" """Return our argparse type function for a config variable (default: str)"""
# pylint: disable=protected-access # pylint: disable=protected-access
for action in helpful_parser.parser._actions: if helpful_parser is not None:
if action.type is not None and action.dest == variable: for action in helpful_parser.parser._actions:
return action.type if action.type is not None and action.dest == variable:
return action.type
return str return str
def read_file(filename, mode="rb"): def read_file(filename, mode="rb"):
@@ -291,10 +301,12 @@ def flag_default(name):
def config_help(name, hidden=False): def config_help(name, hidden=False):
"""Extract the help message for an `.IConfig` attribute.""" """Extract the help message for an `.IConfig` attribute."""
# pylint: disable=no-member
if hidden: if hidden:
return argparse.SUPPRESS return argparse.SUPPRESS
else: else:
return interfaces.IConfig[name].__doc__ field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute
return field.__doc__
class HelpfulArgumentGroup(object): class HelpfulArgumentGroup(object):
@@ -473,7 +485,7 @@ class HelpfulArgumentParser(object):
HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"]
plugin_names = list(plugins) plugin_names = list(plugins)
self.help_topics = HELP_TOPICS + plugin_names + [None] self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore
self.detect_defaults = detect_defaults self.detect_defaults = detect_defaults
self.args = args self.args = args
@@ -492,8 +504,11 @@ class HelpfulArgumentParser(object):
short_usage = self._usage_string(plugins, self.help_arg) short_usage = self._usage_string(plugins, self.help_arg)
self.visible_topics = self.determine_help_topics(self.help_arg) self.visible_topics = self.determine_help_topics(self.help_arg)
self.groups = {} # elements are added by .add_group()
self.defaults = {} # elements are added by .parse_args() # elements are added by .add_group()
self.groups = {} # type: Dict[str, argparse._ArgumentGroup]
# elements are added by .parse_args()
self.defaults = {} # type: Dict[str, Any]
self.parser = configargparse.ArgParser( self.parser = configargparse.ArgParser(
prog="certbot", prog="certbot",
@@ -805,7 +820,6 @@ class HelpfulArgumentParser(object):
if self.help_arg: if self.help_arg:
for v in verbs: for v in verbs:
self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"])
return HelpfulArgumentGroup(self, topic) return HelpfulArgumentGroup(self, topic)
def add_plugin_args(self, plugins): def add_plugin_args(self, plugins):
@@ -1296,14 +1310,14 @@ def _paths_parser(helpful):
verb = helpful.help_arg verb = helpful.help_arg
cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked."
section = ["paths", "install", "revoke", "certonly", "manage"] sections = ["paths", "install", "revoke", "certonly", "manage"]
if verb == "certonly": if verb == "certonly":
add(section, "--cert-path", type=os.path.abspath, add(sections, "--cert-path", type=os.path.abspath,
default=flag_default("auth_cert_path"), help=cph) default=flag_default("auth_cert_path"), help=cph)
elif verb == "revoke": elif verb == "revoke":
add(section, "--cert-path", type=read_file, required=True, help=cph) add(sections, "--cert-path", type=read_file, required=True, help=cph)
else: else:
add(section, "--cert-path", type=os.path.abspath, help=cph) add(sections, "--cert-path", type=os.path.abspath, help=cph)
section = "paths" section = "paths"
if verb in ("install", "revoke"): if verb in ("install", "revoke"):
+10 -6
View File
@@ -5,7 +5,9 @@ import os
import platform import platform
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa # https://github.com/python/typeshed/blob/master/third_party/
# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore
import josepy as jose import josepy as jose
import OpenSSL import OpenSSL
import zope.component import zope.component
@@ -160,11 +162,11 @@ def register(config, account_storage, tos_cb=None):
config.email = None config.email = None
# Each new registration shall use a fresh new key # Each new registration shall use a fresh new key
key = jose.JWKRSA(key=jose.ComparableRSAKey( rsa_key = generate_private_key(
rsa.generate_private_key(
public_exponent=65537, public_exponent=65537,
key_size=config.rsa_key_size, key_size=config.rsa_key_size,
backend=default_backend()))) backend=default_backend())
key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key))
acme = acme_from_config_key(config, key) acme = acme_from_config_key(config, key)
# TODO: add phone? # TODO: add phone?
regr = perform_registration(acme, config, tos_cb) regr = perform_registration(acme, config, tos_cb)
@@ -609,8 +611,10 @@ def validate_key_csr(privkey, csr=None):
if csr.form == "der": if csr.form == "der":
csr_obj = OpenSSL.crypto.load_certificate_request( csr_obj = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr.data) OpenSSL.crypto.FILETYPE_ASN1, csr.data)
csr = util.CSR(csr.file, OpenSSL.crypto.dump_certificate( cert_buffer = OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_obj), "pem") OpenSSL.crypto.FILETYPE_PEM, csr_obj
)
csr = util.CSR(csr.file, cert_buffer, "pem")
# If CSR is provided, it must be readable and valid. # If CSR is provided, it must be readable and valid.
if csr.data and not crypto_util.valid_csr(csr.data): if csr.data and not crypto_util.valid_csr(csr.data):
+66 -62
View File
@@ -8,15 +8,18 @@ import hashlib
import logging import logging
import os import os
import OpenSSL
import pyrfc3339 import pyrfc3339
import six import six
import zope.component import zope.component
from OpenSSL import crypto
from OpenSSL import SSL # type: ignore
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography import x509 # type: ignore # https://github.com/python/typeshed/tree/master/third_party/2/cryptography
from cryptography import x509 # type: ignore
from acme import crypto_util as acme_crypto_util from acme import crypto_util as acme_crypto_util
from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
from certbot import util from certbot import util
@@ -47,7 +50,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
try: try:
key_pem = make_key(key_size) key_pem = make_key(key_size)
except ValueError as err: except ValueError as err:
logger.exception(err) logger.error("", exc_info=True)
raise err raise err
config = zope.component.getUtility(interfaces.IConfig) config = zope.component.getUtility(interfaces.IConfig)
@@ -111,11 +114,11 @@ def valid_csr(csr):
""" """
try: try:
req = OpenSSL.crypto.load_certificate_request( req = crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr) crypto.FILETYPE_PEM, csr)
return req.verify(req.get_pubkey()) return req.verify(req.get_pubkey())
except OpenSSL.crypto.Error as error: except crypto.Error:
logger.debug(error, exc_info=True) logger.debug("", exc_info=True)
return False return False
@@ -129,13 +132,13 @@ def csr_matches_pubkey(csr, privkey):
:rtype: bool :rtype: bool
""" """
req = OpenSSL.crypto.load_certificate_request( req = crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr) crypto.FILETYPE_PEM, csr)
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey)
try: try:
return req.verify(pkey) return req.verify(pkey)
except OpenSSL.crypto.Error as error: except crypto.Error:
logger.debug(error, exc_info=True) logger.debug("", exc_info=True)
return False return False
@@ -145,26 +148,26 @@ def import_csr_file(csrfile, data):
:param str csrfile: CSR filename :param str csrfile: CSR filename
:param str data: contents of the CSR file :param str data: contents of the CSR file
:returns: (`OpenSSL.crypto.FILETYPE_PEM`, :returns: (`crypto.FILETYPE_PEM`,
util.CSR object representing the CSR, util.CSR object representing the CSR,
list of domains requested in the CSR) list of domains requested in the CSR)
:rtype: tuple :rtype: tuple
""" """
PEM = OpenSSL.crypto.FILETYPE_PEM PEM = crypto.FILETYPE_PEM
load = OpenSSL.crypto.load_certificate_request load = crypto.load_certificate_request
try: try:
# Try to parse as DER first, then fall back to PEM. # Try to parse as DER first, then fall back to PEM.
csr = load(OpenSSL.crypto.FILETYPE_ASN1, data) csr = load(crypto.FILETYPE_ASN1, data)
except OpenSSL.crypto.Error: except crypto.Error:
try: try:
csr = load(PEM, data) csr = load(PEM, data)
except OpenSSL.crypto.Error: except crypto.Error:
raise errors.Error("Failed to parse CSR file: {0}".format(csrfile)) raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))
domains = _get_names_from_loaded_cert_or_req(csr) domains = _get_names_from_loaded_cert_or_req(csr)
# Internally we always use PEM, so re-encode as PEM before returning. # Internally we always use PEM, so re-encode as PEM before returning.
data_pem = OpenSSL.crypto.dump_certificate_request(PEM, csr) data_pem = crypto.dump_certificate_request(PEM, csr)
return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains
@@ -178,9 +181,9 @@ def make_key(bits):
""" """
assert bits >= 1024 # XXX assert bits >= 1024 # XXX
key = OpenSSL.crypto.PKey() key = crypto.PKey()
key.generate_key(OpenSSL.crypto.TYPE_RSA, bits) key.generate_key(crypto.TYPE_RSA, bits)
return OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
def valid_privkey(privkey): def valid_privkey(privkey):
@@ -193,9 +196,9 @@ def valid_privkey(privkey):
""" """
try: try:
return OpenSSL.crypto.load_privatekey( return crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, privkey).check() crypto.FILETYPE_PEM, privkey).check()
except (TypeError, OpenSSL.crypto.Error): except (TypeError, crypto.Error):
return False return False
@@ -224,13 +227,14 @@ def verify_renewable_cert_sig(renewable_cert):
:raises errors.Error: If signature verification fails. :raises errors.Error: If signature verification fails.
""" """
try: try:
with open(renewable_cert.chain, 'rb') as chain: with open(renewable_cert.chain, 'rb') as chain_file: # type: IO[bytes]
chain, _ = pyopenssl_load_certificate(chain.read()) chain, _ = pyopenssl_load_certificate(chain_file.read())
with open(renewable_cert.cert, 'rb') as cert: with open(renewable_cert.cert, 'rb') as cert_file: # type: IO[bytes]
cert = x509.load_pem_x509_certificate(cert.read(), default_backend()) cert = x509.load_pem_x509_certificate(
cert_file.read(), default_backend())
hash_name = cert.signature_hash_algorithm.name hash_name = cert.signature_hash_algorithm.name
OpenSSL.crypto.verify(chain, cert.signature, cert.tbs_certificate_bytes, hash_name) crypto.verify(chain, cert.signature, cert.tbs_certificate_bytes, hash_name)
except (IOError, ValueError, OpenSSL.crypto.Error) as e: except (IOError, ValueError, crypto.Error) as e:
error_str = "verifying the signature of the cert located at {0} has failed. \ error_str = "verifying the signature of the cert located at {0} has failed. \
Details: {1}".format(renewable_cert.cert, e) Details: {1}".format(renewable_cert.cert, e)
logger.exception(error_str) logger.exception(error_str)
@@ -246,11 +250,11 @@ def verify_cert_matches_priv_key(cert_path, key_path):
:raises errors.Error: If they don't match. :raises errors.Error: If they don't match.
""" """
try: try:
context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) context = SSL.Context(SSL.SSLv23_METHOD)
context.use_certificate_file(cert_path) context.use_certificate_file(cert_path)
context.use_privatekey_file(key_path) context.use_privatekey_file(key_path)
context.check_privatekey() context.check_privatekey()
except (IOError, OpenSSL.SSL.Error) as e: except (IOError, SSL.Error) as e:
error_str = "verifying the cert located at {0} matches the \ error_str = "verifying the cert located at {0} matches the \
private key located at {1} has failed. \ private key located at {1} has failed. \
Details: {2}".format(cert_path, Details: {2}".format(cert_path,
@@ -267,12 +271,12 @@ def verify_fullchain(renewable_cert):
:raises errors.Error: If cert and chain do not combine to fullchain. :raises errors.Error: If cert and chain do not combine to fullchain.
""" """
try: try:
with open(renewable_cert.chain) as chain: with open(renewable_cert.chain) as chain_file: # type: IO[str]
chain = chain.read() chain = chain_file.read()
with open(renewable_cert.cert) as cert: with open(renewable_cert.cert) as cert_file: # type: IO[str]
cert = cert.read() cert = cert_file.read()
with open(renewable_cert.fullchain) as fullchain: with open(renewable_cert.fullchain) as fullchain_file: # type: IO[str]
fullchain = fullchain.read() fullchain = fullchain_file.read()
if (cert + chain) != fullchain: if (cert + chain) != fullchain:
error_str = "fullchain does not match cert + chain for {0}!" error_str = "fullchain does not match cert + chain for {0}!"
error_str = error_str.format(renewable_cert.lineagename) error_str = error_str.format(renewable_cert.lineagename)
@@ -294,43 +298,43 @@ def pyopenssl_load_certificate(data):
openssl_errors = [] openssl_errors = []
for file_type in (OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1): for file_type in (crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1):
try: try:
return OpenSSL.crypto.load_certificate(file_type, data), file_type return crypto.load_certificate(file_type, data), file_type
except OpenSSL.crypto.Error as error: # TODO: other errors? except crypto.Error as error: # TODO: other errors?
openssl_errors.append(error) openssl_errors.append(error)
raise errors.Error("Unable to load: {0}".format(",".join( raise errors.Error("Unable to load: {0}".format(",".join(
str(error) for error in openssl_errors))) str(error) for error in openssl_errors)))
def _load_cert_or_req(cert_or_req_str, load_func, def _load_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM): typ=crypto.FILETYPE_PEM):
try: try:
return load_func(typ, cert_or_req_str) return load_func(typ, cert_or_req_str)
except OpenSSL.crypto.Error as error: except crypto.Error:
logger.exception(error) logger.error("", exc_info=True)
raise raise
def _get_sans_from_cert_or_req(cert_or_req_str, load_func, def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM): typ=crypto.FILETYPE_PEM):
# pylint: disable=protected-access # pylint: disable=protected-access
return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req( return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(
cert_or_req_str, load_func, typ)) cert_or_req_str, load_func, typ))
def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM): def get_sans_from_cert(cert, typ=crypto.FILETYPE_PEM):
"""Get a list of Subject Alternative Names from a certificate. """Get a list of Subject Alternative Names from a certificate.
:param str cert: Certificate (encoded). :param str cert: Certificate (encoded).
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1` :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1`
:returns: A list of Subject Alternative Names. :returns: A list of Subject Alternative Names.
:rtype: list :rtype: list
""" """
return _get_sans_from_cert_or_req( return _get_sans_from_cert_or_req(
cert, OpenSSL.crypto.load_certificate, typ) cert, crypto.load_certificate, typ)
def _get_names_from_cert_or_req(cert_or_req, load_func, typ): def _get_names_from_cert_or_req(cert_or_req, load_func, typ):
@@ -343,24 +347,24 @@ def _get_names_from_loaded_cert_or_req(loaded_cert_or_req):
return acme_crypto_util._pyopenssl_cert_or_req_all_names(loaded_cert_or_req) return acme_crypto_util._pyopenssl_cert_or_req_all_names(loaded_cert_or_req)
def get_names_from_cert(csr, typ=OpenSSL.crypto.FILETYPE_PEM): def get_names_from_cert(csr, typ=crypto.FILETYPE_PEM):
"""Get a list of domains from a cert, including the CN if it is set. """Get a list of domains from a cert, including the CN if it is set.
:param str cert: Certificate (encoded). :param str cert: Certificate (encoded).
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1` :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1`
:returns: A list of domain names. :returns: A list of domain names.
:rtype: list :rtype: list
""" """
return _get_names_from_cert_or_req( return _get_names_from_cert_or_req(
csr, OpenSSL.crypto.load_certificate, typ) csr, crypto.load_certificate, typ)
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM): def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle. """Dump certificate chain into a bundle.
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in :param list chain: List of `crypto.X509` (or wrapped in
:class:`josepy.util.ComparableX509`). :class:`josepy.util.ComparableX509`).
""" """
@@ -378,7 +382,7 @@ def notBefore(cert_path):
:rtype: :class:`datetime.datetime` :rtype: :class:`datetime.datetime`
""" """
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notBefore) return _notAfterBefore(cert_path, crypto.X509.get_notBefore)
def notAfter(cert_path): def notAfter(cert_path):
@@ -390,15 +394,15 @@ def notAfter(cert_path):
:rtype: :class:`datetime.datetime` :rtype: :class:`datetime.datetime`
""" """
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notAfter) return _notAfterBefore(cert_path, crypto.X509.get_notAfter)
def _notAfterBefore(cert_path, method): def _notAfterBefore(cert_path, method):
"""Internal helper function for finding notbefore/notafter. """Internal helper function for finding notbefore/notafter.
:param str cert_path: path to a cert in PEM format :param str cert_path: path to a cert in PEM format
:param function method: one of ``OpenSSL.crypto.X509.get_notBefore`` :param function method: one of ``crypto.X509.get_notBefore``
or ``OpenSSL.crypto.X509.get_notAfter`` or ``crypto.X509.get_notAfter``
:returns: the notBefore or notAfter value from the cert at cert_path :returns: the notBefore or notAfter value from the cert at cert_path
:rtype: :class:`datetime.datetime` :rtype: :class:`datetime.datetime`
@@ -406,7 +410,7 @@ def _notAfterBefore(cert_path, method):
""" """
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
with open(cert_path) as f: with open(cert_path) as f:
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
f.read()) f.read())
# pyopenssl always returns bytes # pyopenssl always returns bytes
timestamp = method(x509) timestamp = method(x509)
@@ -443,7 +447,7 @@ def cert_and_chain_from_fullchain(fullchain_pem):
:rtype: tuple :rtype: tuple
""" """
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert = crypto.dump_certificate(crypto.FILETYPE_PEM,
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fullchain_pem)).decode() crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode()
chain = fullchain_pem[len(cert):].lstrip() chain = fullchain_pem[len(cert):].lstrip()
return (cert, chain) return (cert, chain)
+10 -6
View File
@@ -5,6 +5,10 @@ import os
import signal import signal
import traceback import traceback
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Any, Callable, Dict, List, Union
# pylint: enable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -56,9 +60,9 @@ class ErrorHandler(object):
def __init__(self, func=None, *args, **kwargs): def __init__(self, func=None, *args, **kwargs):
self.call_on_regular_exit = False self.call_on_regular_exit = False
self.body_executed = False self.body_executed = False
self.funcs = [] self.funcs = [] # type: List[Callable[[], Any]]
self.prev_handlers = {} self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]]
self.received_signals = [] self.received_signals = [] # type: List[int]
if func is not None: if func is not None:
self.register(func, *args, **kwargs) self.register(func, *args, **kwargs)
@@ -88,6 +92,7 @@ class ErrorHandler(object):
return retval return retval
def register(self, func, *args, **kwargs): def register(self, func, *args, **kwargs):
# type: (Callable, *Any, **Any) -> None
"""Sets func to be run with the given arguments during cleanup. """Sets func to be run with the given arguments during cleanup.
:param function func: function to be called in case of an error :param function func: function to be called in case of an error
@@ -101,9 +106,8 @@ class ErrorHandler(object):
while self.funcs: while self.funcs:
try: try:
self.funcs[-1]() self.funcs[-1]()
except Exception as error: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.error("Encountered exception during recovery") logger.error("Encountered exception during recovery: ", exc_info=True)
logger.exception(error)
self.funcs.pop() self.funcs.pop()
def _set_signal_handlers(self): def _set_signal_handlers(self):
+10 -7
View File
@@ -6,6 +6,7 @@ import os
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
from certbot import util from certbot import util
@@ -76,7 +77,8 @@ def pre_hook(config):
if cmd: if cmd:
_run_pre_hook_if_necessary(cmd) _run_pre_hook_if_necessary(cmd)
pre_hook.already = set() # type: ignore
executed_pre_hooks = set() # type: Set[str]
def _run_pre_hook_if_necessary(command): def _run_pre_hook_if_necessary(command):
@@ -88,12 +90,12 @@ def _run_pre_hook_if_necessary(command):
:param str command: pre-hook to be run :param str command: pre-hook to be run
""" """
if command in pre_hook.already: if command in executed_pre_hooks:
logger.info("Pre-hook command already run, skipping: %s", command) logger.info("Pre-hook command already run, skipping: %s", command)
else: else:
logger.info("Running pre-hook command: %s", command) logger.info("Running pre-hook command: %s", command)
_run_hook(command) _run_hook(command)
pre_hook.already.add(command) executed_pre_hooks.add(command)
def post_hook(config): def post_hook(config):
@@ -127,7 +129,8 @@ def post_hook(config):
logger.info("Running post-hook command: %s", cmd) logger.info("Running post-hook command: %s", cmd)
_run_hook(cmd) _run_hook(cmd)
post_hook.eventually = [] # type: ignore
post_hooks = [] # type: List[str]
def _run_eventually(command): def _run_eventually(command):
@@ -139,13 +142,13 @@ def _run_eventually(command):
:param str command: post-hook to register to be run :param str command: post-hook to register to be run
""" """
if command not in post_hook.eventually: if command not in post_hooks:
post_hook.eventually.append(command) post_hooks.append(command)
def run_saved_post_hooks(): def run_saved_post_hooks():
"""Run any post hooks that were saved up in the course of the 'renew' verb""" """Run any post hooks that were saved up in the course of the 'renew' verb"""
for cmd in post_hook.eventually: for cmd in post_hooks:
logger.info("Running post-hook command: %s", cmd) logger.info("Running post-hook command: %s", cmd)
_run_hook(cmd) _run_hook(cmd)
+1 -2
View File
@@ -191,9 +191,8 @@ class MemoryHandler(logging.handlers.MemoryHandler):
only happens when flush(force=True) is called. only happens when flush(force=True) is called.
""" """
def __init__(self, target=None): def __init__(self, target=None, capacity=10000):
# capacity doesn't matter because should_flush() is overridden # capacity doesn't matter because should_flush() is overridden
capacity = float('inf')
super(MemoryHandler, self).__init__(capacity, target=target) super(MemoryHandler, self).__init__(capacity, target=target)
def close(self): def close(self):
+5 -3
View File
@@ -11,6 +11,7 @@ import josepy as jose
import zope.component import zope.component
from acme import errors as acme_errors from acme import errors as acme_errors
from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module
import certbot import certbot
@@ -520,8 +521,8 @@ def _determine_account(config):
config, account_storage, tos_cb=_tos_cb) config, account_storage, tos_cb=_tos_cb)
except errors.MissingCommandlineFlag: except errors.MissingCommandlineFlag:
raise raise
except errors.Error as error: except errors.Error:
logger.debug(error, exc_info=True) logger.debug("", exc_info=True)
raise errors.Error( raise errors.Error(
"Unable to register an account with ACME server") "Unable to register an account with ACME server")
@@ -1271,7 +1272,8 @@ def set_displayer(config):
""" """
if config.quiet: if config.quiet:
config.noninteractive_mode = True config.noninteractive_mode = True
displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) \
# type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay]
elif config.noninteractive_mode: elif config.noninteractive_mode:
displayer = display_util.NoninteractiveDisplay(sys.stdout) displayer = display_util.NoninteractiveDisplay(sys.stdout)
else: else:
+4 -2
View File
@@ -11,6 +11,8 @@ import zope.interface
from josepy import util as jose_util from josepy import util as jose_util
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges # pylint: disable=unused-import
from certbot import constants from certbot import constants
from certbot import crypto_util from certbot import crypto_util
from certbot import errors from certbot import errors
@@ -331,8 +333,8 @@ class ChallengePerformer(object):
def __init__(self, configurator): def __init__(self, configurator):
self.configurator = configurator self.configurator = configurator
self.achalls = [] self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge]
self.indices = [] self.indices = [] # type: List[int]
def add_chall(self, achall, idx=None): def add_chall(self, achall, idx=None):
"""Store challenge to be performed when perform() is called. """Store challenge to be performed when perform() is called.
+2 -1
View File
@@ -10,6 +10,7 @@ from collections import OrderedDict
import zope.interface import zope.interface
import zope.interface.verify import zope.interface.verify
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -189,7 +190,7 @@ class PluginsRegistry(collections.Mapping):
@classmethod @classmethod
def find_all(cls): def find_all(cls):
"""Find plugins using setuptools entry points.""" """Find plugins using setuptools entry points."""
plugins = {} plugins = {} # type: Dict[str, PluginEntryPoint]
# pylint: disable=not-callable # pylint: disable=not-callable
entry_points = itertools.chain( entry_points = itertools.chain(
pkg_resources.iter_entry_points( pkg_resources.iter_entry_points(
+2 -1
View File
@@ -8,6 +8,7 @@ import pkg_resources
import six import six
import zope.interface import zope.interface
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -250,7 +251,7 @@ class PluginsRegistryTest(unittest.TestCase):
self.plugin_ep.prepare.assert_called_once_with() self.plugin_ep.prepare.assert_called_once_with()
def test_prepare_order(self): def test_prepare_order(self):
order = [] order = [] # type: List[str]
plugins = dict( plugins = dict(
(c, mock.MagicMock(prepare=functools.partial(order.append, c))) (c, mock.MagicMock(prepare=functools.partial(order.append, c)))
for c in string.ascii_letters) for c in string.ascii_letters)
+4 -1
View File
@@ -5,7 +5,9 @@ import zope.component
import zope.interface import zope.interface
from acme import challenges from acme import challenges
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges # pylint: disable=unused-import
from certbot import interfaces from certbot import interfaces
from certbot import errors from certbot import errors
from certbot import hooks from certbot import hooks
@@ -98,7 +100,8 @@ when it receives a TLS ClientHello with the SNI extension set to
super(Authenticator, self).__init__(*args, **kwargs) super(Authenticator, self).__init__(*args, **kwargs)
self.reverter = reverter.Reverter(self.config) self.reverter = reverter.Reverter(self.config)
self.reverter.recovery_routine() self.reverter.recovery_routine()
self.env = dict() self.env = dict() \
# type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]]
self.tls_sni_01 = None self.tls_sni_01 = None
@classmethod @classmethod
+2 -1
View File
@@ -6,6 +6,7 @@ import unittest
import mock import mock
import zope.component import zope.component
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot.display import util as display_util from certbot.display import util as display_util
from certbot.tests import util as test_util from certbot.tests import util as test_util
from certbot import interfaces from certbot import interfaces
@@ -47,7 +48,7 @@ class PickPluginTest(unittest.TestCase):
self.default = None self.default = None
self.reg = mock.MagicMock() self.reg = mock.MagicMock()
self.question = "Question?" self.question = "Question?"
self.ifaces = [] self.ifaces = [] # type: List[interfaces.IPlugin]
def _call(self): def _call(self):
from certbot.plugins.selection import pick_plugin from certbot.plugins.selection import pick_plugin
+21 -8
View File
@@ -3,6 +3,8 @@ import argparse
import collections import collections
import logging import logging
import socket import socket
# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi
from socket import errno as socket_errors # type: ignore
import OpenSSL import OpenSSL
import six import six
@@ -10,7 +12,10 @@ import zope.interface
from acme import challenges from acme import challenges
from acme import standalone as acme_standalone from acme import standalone as acme_standalone
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING
from certbot import achallenges # pylint: disable=unused-import
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -18,6 +23,11 @@ from certbot.plugins import common
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING:
ServedType = DefaultDict[
acme_standalone.BaseDualNetworkedServers,
Set[achallenges.KeyAuthorizationAnnotatedChallenge]
]
class ServerManager(object): class ServerManager(object):
"""Standalone servers manager. """Standalone servers manager.
@@ -33,7 +43,7 @@ class ServerManager(object):
""" """
def __init__(self, certs, http_01_resources): def __init__(self, certs, http_01_resources):
self._instances = {} self._instances = {} # type: Dict[int, acme_standalone.BaseDualNetworkedServers]
self.certs = certs self.certs = certs
self.http_01_resources = http_01_resources self.http_01_resources = http_01_resources
@@ -59,7 +69,8 @@ class ServerManager(object):
address = (listenaddr, port) address = (listenaddr, port)
try: try:
if challenge_type is challenges.TLSSNI01: if challenge_type is challenges.TLSSNI01:
servers = acme_standalone.TLSSNI01DualNetworkedServers(address, self.certs) servers = acme_standalone.TLSSNI01DualNetworkedServers(
address, self.certs) # type: acme_standalone.BaseDualNetworkedServers
else: # challenges.HTTP01 else: # challenges.HTTP01
servers = acme_standalone.HTTP01DualNetworkedServers( servers = acme_standalone.HTTP01DualNetworkedServers(
address, self.http_01_resources) address, self.http_01_resources)
@@ -103,7 +114,8 @@ class ServerManager(object):
return self._instances.copy() return self._instances.copy()
SUPPORTED_CHALLENGES = [challenges.TLSSNI01, challenges.HTTP01] SUPPORTED_CHALLENGES = [challenges.TLSSNI01, challenges.HTTP01] \
# type: List[Type[challenges.KeyAuthorizationChallenge]]
class SupportedChallengesAction(argparse.Action): class SupportedChallengesAction(argparse.Action):
@@ -179,14 +191,15 @@ class Authenticator(common.Plugin):
self.key = OpenSSL.crypto.PKey() self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
self.served = collections.defaultdict(set) self.served = collections.defaultdict(set) # type: ServedType
# Stuff below is shared across threads (i.e. servers read # Stuff below is shared across threads (i.e. servers read
# values, main thread writes). Due to the nature of CPython's # values, main thread writes). Due to the nature of CPython's
# GIL, the operations are safe, c.f. # GIL, the operations are safe, c.f.
# https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
self.certs = {} self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]]
self.http_01_resources = set() self.http_01_resources = set() \
# type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource]
self.servers = ServerManager(self.certs, self.http_01_resources) self.servers = ServerManager(self.certs, self.http_01_resources)
@@ -265,13 +278,13 @@ class Authenticator(common.Plugin):
def _handle_perform_error(error): def _handle_perform_error(error):
if error.socket_error.errno == socket.errno.EACCES: if error.socket_error.errno == socket_errors.EACCES:
raise errors.PluginError( raise errors.PluginError(
"Could not bind TCP port {0} because you don't have " "Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you " "the appropriate permissions (for example, you "
"aren't running this program as " "aren't running this program as "
"root).".format(error.port)) "root).".format(error.port))
elif error.socket_error.errno == socket.errno.EADDRINUSE: elif error.socket_error.errno == socket_errors.EADDRINUSE:
display = zope.component.getUtility(interfaces.IDisplay) display = zope.component.getUtility(interfaces.IDisplay)
msg = ( msg = (
"Could not bind TCP port {0} because it is already in " "Could not bind TCP port {0} because it is already in "
+13 -6
View File
@@ -2,12 +2,18 @@
import argparse import argparse
import socket import socket
import unittest import unittest
# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi
from socket import errno as socket_errors # type: ignore
import josepy as jose import josepy as jose
import mock import mock
import six import six
import OpenSSL.crypto # pylint: disable=unused-import
from acme import challenges from acme import challenges
from acme import standalone as acme_standalone # pylint: disable=unused-import
from acme.magic_typing import Dict, Tuple, Set # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges from certbot import achallenges
from certbot import errors from certbot import errors
@@ -21,8 +27,9 @@ class ServerManagerTest(unittest.TestCase):
def setUp(self): def setUp(self):
from certbot.plugins.standalone import ServerManager from certbot.plugins.standalone import ServerManager
self.certs = {} self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]]
self.http_01_resources = {} self.http_01_resources = {} \
# type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource]
self.mgr = ServerManager(self.certs, self.http_01_resources) self.mgr = ServerManager(self.certs, self.http_01_resources)
def test_init(self): def test_init(self):
@@ -159,7 +166,7 @@ class AuthenticatorTest(unittest.TestCase):
@test_util.patch_get_utility() @test_util.patch_get_utility()
def test_perform_eaddrinuse_retry(self, mock_get_utility): def test_perform_eaddrinuse_retry(self, mock_get_utility):
mock_utility = mock_get_utility() mock_utility = mock_get_utility()
errno = socket.errno.EADDRINUSE errno = socket_errors.EADDRINUSE
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()] self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]
mock_yesno = mock_utility.yesno mock_yesno = mock_utility.yesno
@@ -174,7 +181,7 @@ class AuthenticatorTest(unittest.TestCase):
mock_yesno = mock_utility.yesno mock_yesno = mock_utility.yesno
mock_yesno.return_value = False mock_yesno.return_value = False
errno = socket.errno.EADDRINUSE errno = socket_errors.EADDRINUSE
self.assertRaises(errors.PluginError, self._fail_perform, errno) self.assertRaises(errors.PluginError, self._fail_perform, errno)
self._assert_correct_yesno_call(mock_yesno) self._assert_correct_yesno_call(mock_yesno)
@@ -184,11 +191,11 @@ class AuthenticatorTest(unittest.TestCase):
self.assertFalse(yesno_kwargs.get("default", True)) self.assertFalse(yesno_kwargs.get("default", True))
def test_perform_eacces(self): def test_perform_eacces(self):
errno = socket.errno.EACCES errno = socket_errors.EACCES
self.assertRaises(errors.PluginError, self._fail_perform, errno) self.assertRaises(errors.PluginError, self._fail_perform, errno)
def test_perform_unexpected_socket_error(self): def test_perform_unexpected_socket_error(self):
errno = socket.errno.ENOTCONN errno = socket_errors.ENOTCONN
self.assertRaises( self.assertRaises(
errors.StandaloneBindError, self._fail_perform, errno) errors.StandaloneBindError, self._fail_perform, errno)
+2 -1
View File
@@ -3,6 +3,7 @@ import json
import logging import logging
import os import os
from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -38,7 +39,7 @@ class PluginStorage(object):
:raises .errors.PluginStorageError: when unable to open or read the file :raises .errors.PluginStorageError: when unable to open or read the file
""" """
data = dict() data = dict() # type: Dict[str, Any]
filedata = "" filedata = ""
try: try:
with open(self._storagepath, 'r') as fh: with open(self._storagepath, 'r') as fh:
+10 -7
View File
@@ -10,8 +10,12 @@ import six
import zope.component import zope.component
import zope.interface import zope.interface
from acme import challenges from acme import challenges # pylint: disable=unused-import
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Dict, Set, DefaultDict, List
# pylint: enable=unused-import, no-name-in-module
from certbot import achallenges # pylint: disable=unused-import
from certbot import cli from certbot import cli
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -64,10 +68,11 @@ to serve all files under specified web root ({0})."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs) super(Authenticator, self).__init__(*args, **kwargs)
self.full_roots = {} self.full_roots = {} # type: Dict[str, str]
self.performed = collections.defaultdict(set) self.performed = collections.defaultdict(set) \
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
# stack of dirs successfully created by this authenticator # stack of dirs successfully created by this authenticator
self._created_dirs = [] self._created_dirs = [] # type: List[str]
def prepare(self): # pylint: disable=missing-docstring def prepare(self): # pylint: disable=missing-docstring
pass pass
@@ -156,7 +161,6 @@ to serve all files under specified web root ({0})."""
" --help webroot for examples.") " --help webroot for examples.")
for name, path in path_map.items(): for name, path in path_map.items():
self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)
logger.debug("Creating root challenges validation dir at %s", logger.debug("Creating root challenges validation dir at %s",
self.full_roots[name]) self.full_roots[name])
@@ -207,7 +211,6 @@ to serve all files under specified web root ({0})."""
os.umask(old_umask) os.umask(old_umask)
self.performed[root_path].add(achall) self.performed[root_path].add(achall)
return response return response
def cleanup(self, achalls): # pylint: disable=missing-docstring def cleanup(self, achalls): # pylint: disable=missing-docstring
@@ -219,7 +222,7 @@ to serve all files under specified web root ({0})."""
os.remove(validation_path) os.remove(validation_path)
self.performed[root_path].remove(achall) self.performed[root_path].remove(achall)
not_removed = [] not_removed = [] # type: List[str]
while len(self._created_dirs) > 0: while len(self._created_dirs) > 0:
path = self._created_dirs.pop() path = self._created_dirs.pop()
try: try:
+10 -7
View File
@@ -11,6 +11,8 @@ import zope.component
import OpenSSL import OpenSSL
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import cli from certbot import cli
from certbot import crypto_util from certbot import crypto_util
from certbot import errors from certbot import errors
@@ -59,8 +61,8 @@ def _reconstitute(config, full_path):
""" """
try: try:
renewal_candidate = storage.RenewableCert(full_path, config) renewal_candidate = storage.RenewableCert(full_path, config)
except (errors.CertStorageError, IOError) as exc: except (errors.CertStorageError, IOError):
logger.warning(exc) logger.warning("", exc_info=True)
logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) logger.warning("Renewal configuration file %s is broken. Skipping.", full_path)
logger.debug("Traceback was:\n%s", traceback.format_exc()) logger.debug("Traceback was:\n%s", traceback.format_exc())
return None return None
@@ -133,14 +135,15 @@ def _restore_plugin_configs(config, renewalparams):
# longer defined, stored copies of that parameter will be # longer defined, stored copies of that parameter will be
# deserialized as strings by this logic even if they were # deserialized as strings by this logic even if they were
# originally meant to be some other type. # originally meant to be some other type.
plugin_prefixes = [] # type: List[str]
if renewalparams["authenticator"] == "webroot": if renewalparams["authenticator"] == "webroot":
_restore_webroot_config(config, renewalparams) _restore_webroot_config(config, renewalparams)
plugin_prefixes = []
else: else:
plugin_prefixes = [renewalparams["authenticator"]] plugin_prefixes.append(renewalparams["authenticator"])
if renewalparams.get("installer", None) is not None: if renewalparams.get("installer") is not None:
plugin_prefixes.append(renewalparams["installer"]) plugin_prefixes.append(renewalparams["installer"])
for plugin_prefix in set(plugin_prefixes): for plugin_prefix in set(plugin_prefixes):
plugin_prefix = plugin_prefix.replace('-', '_') plugin_prefix = plugin_prefix.replace('-', '_')
for config_item, config_value in six.iteritems(renewalparams): for config_item, config_value in six.iteritems(renewalparams):
@@ -316,13 +319,13 @@ def report(msgs, category):
def _renew_describe_results(config, renew_successes, renew_failures, def _renew_describe_results(config, renew_successes, renew_failures,
renew_skipped, parse_failures): renew_skipped, parse_failures):
out = [] out = [] # type: List[str]
notify = out.append notify = out.append
disp = zope.component.getUtility(interfaces.IDisplay) disp = zope.component.getUtility(interfaces.IDisplay)
def notify_error(err): def notify_error(err):
"""Notify and log errors.""" """Notify and log errors."""
notify(err) notify(str(err))
logger.error(err) logger.error(err)
if config.dry_run: if config.dry_run:
+7 -5
View File
@@ -82,8 +82,10 @@ class Reverter(object):
self._recover_checkpoint(self.config.temp_checkpoint_dir) self._recover_checkpoint(self.config.temp_checkpoint_dir)
except errors.ReverterError: except errors.ReverterError:
# We have a partial or incomplete recovery # We have a partial or incomplete recovery
logger.fatal("Incomplete or failed recovery for %s", logger.critical(
self.config.temp_checkpoint_dir) "Incomplete or failed recovery for %s",
self.config.temp_checkpoint_dir,
)
raise errors.ReverterError("Unable to revert temporary config") raise errors.ReverterError("Unable to revert temporary config")
def rollback_checkpoints(self, rollback=1): def rollback_checkpoints(self, rollback=1):
@@ -123,7 +125,7 @@ class Reverter(object):
try: try:
self._recover_checkpoint(cp_dir) self._recover_checkpoint(cp_dir)
except errors.ReverterError: except errors.ReverterError:
logger.fatal("Failed to load checkpoint during rollback") logger.critical("Failed to load checkpoint during rollback")
raise errors.ReverterError( raise errors.ReverterError(
"Unable to load checkpoint during rollback") "Unable to load checkpoint during rollback")
rollback -= 1 rollback -= 1
@@ -457,7 +459,7 @@ class Reverter(object):
self._recover_checkpoint(self.config.in_progress_dir) self._recover_checkpoint(self.config.in_progress_dir)
except errors.ReverterError: except errors.ReverterError:
# We have a partial or incomplete recovery # We have a partial or incomplete recovery
logger.fatal("Incomplete or failed recovery for IN_PROGRESS " logger.critical("Incomplete or failed recovery for IN_PROGRESS "
"checkpoint - %s", "checkpoint - %s",
self.config.in_progress_dir) self.config.in_progress_dir)
raise errors.ReverterError( raise errors.ReverterError(
@@ -494,7 +496,7 @@ class Reverter(object):
"Certbot probably shut down unexpectedly", "Certbot probably shut down unexpectedly",
os.linesep, path) os.linesep, path)
except (IOError, OSError): except (IOError, OSError):
logger.fatal( logger.critical(
"Unable to remove filepaths contained within %s", file_list) "Unable to remove filepaths contained within %s", file_list)
raise errors.ReverterError( raise errors.ReverterError(
"Unable to remove filepaths contained within " "Unable to remove filepaths contained within "
+3 -1
View File
@@ -10,6 +10,7 @@ import zope.component
from acme import challenges from acme import challenges
from acme import client as acme_client from acme import client as acme_client
from acme import messages from acme import messages
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges from certbot import achallenges
from certbot import errors from certbot import errors
@@ -354,12 +355,13 @@ class PollChallengesTest(unittest.TestCase):
acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False), []) acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False), [])
] ]
self.chall_update = {} self.chall_update = {} # type: Dict[int, achallenges.KeyAuthorizationAnnotatedChallenge]
for i, aauthzr in enumerate(self.aauthzrs): for i, aauthzr in enumerate(self.aauthzrs):
self.chall_update[i] = [ self.chall_update[i] = [
challb_to_achall(challb, mock.Mock(key="dummy_key"), self.doms[i]) challb_to_achall(challb, mock.Mock(key="dummy_key"), self.doms[i])
for challb in aauthzr.authzr.body.challenges] for challb in aauthzr.authzr.body.challenges]
@mock.patch("certbot.auth_handler.time") @mock.patch("certbot.auth_handler.time")
def test_poll_challenges(self, unused_mock_time): def test_poll_challenges(self, unused_mock_time):
self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid
+2 -1
View File
@@ -495,7 +495,8 @@ class SetByCliTest(unittest.TestCase):
for v in ('manual', 'manual_auth_hook', 'manual_public_ip_logging_ok'): for v in ('manual', 'manual_auth_hook', 'manual_public_ip_logging_ok'):
self.assertTrue(_call_set_by_cli(v, args, verb)) self.assertTrue(_call_set_by_cli(v, args, verb))
cli.set_by_cli.detector = None # https://github.com/python/mypy/issues/2087
cli.set_by_cli.detector = None # type: ignore
args = ['--manual-auth-hook', 'command'] args = ['--manual-auth-hook', 'command']
for v in ('manual_auth_hook', 'manual_public_ip_logging_ok'): for v in ('manual_auth_hook', 'manual_public_ip_logging_ok'):
+2 -1
View File
@@ -8,6 +8,7 @@ import unittest
import mock import mock
from six.moves import reload_module # pylint: disable=import-error from six.moves import reload_module # pylint: disable=import-error
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot.tests.util import TempDirTestCase from certbot.tests.util import TempDirTestCase
class CompleterTest(TempDirTestCase): class CompleterTest(TempDirTestCase):
@@ -21,7 +22,7 @@ class CompleterTest(TempDirTestCase):
if self.tempdir[-1] != os.sep: if self.tempdir[-1] != os.sep:
self.tempdir += os.sep self.tempdir += os.sep
self.paths = [] self.paths = [] # type: List[str]
# create some files and directories in temp_dir # create some files and directories in temp_dir
for c in string.ascii_lowercase: for c in string.ascii_lowercase:
path = os.path.join(self.tempdir, c) path = os.path.join(self.tempdir, c)
+4 -2
View File
@@ -6,6 +6,9 @@ import sys
import unittest import unittest
import mock import mock
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Callable, Dict, Union
# pylint: enable=unused-import, no-name-in-module
def get_signals(signums): def get_signals(signums):
@@ -23,8 +26,7 @@ def set_signals(sig_handler_dict):
def signal_receiver(signums): def signal_receiver(signums):
"""Context manager to catch signals""" """Context manager to catch signals"""
signals = [] signals = []
prev_handlers = {} prev_handlers = get_signals(signums) # type: Dict[int, Union[int, None, Callable]]
prev_handlers = get_signals(signums)
set_signals(dict((s, lambda s, _: signals.append(s)) for s in signums)) set_signals(dict((s, lambda s, _: signals.append(s)) for s in signums))
yield signals yield signals
set_signals(prev_handlers) set_signals(prev_handlers)
+11 -10
View File
@@ -5,6 +5,7 @@ import unittest
import mock import mock
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import errors from certbot import errors
from certbot.tests import util from certbot.tests import util
@@ -106,8 +107,8 @@ class PreHookTest(HookTest):
super(PreHookTest, self).tearDown() super(PreHookTest, self).tearDown()
def _reset_pre_hook_already(self): def _reset_pre_hook_already(self):
from certbot.hooks import pre_hook from certbot.hooks import executed_pre_hooks
pre_hook.already.clear() executed_pre_hooks.clear()
def test_certonly(self): def test_certonly(self):
self.config.verb = "certonly" self.config.verb = "certonly"
@@ -184,8 +185,8 @@ class PostHookTest(HookTest):
super(PostHookTest, self).tearDown() super(PostHookTest, self).tearDown()
def _reset_post_hook_eventually(self): def _reset_post_hook_eventually(self):
from certbot.hooks import post_hook from certbot.hooks import post_hooks
post_hook.eventually = [] del post_hooks[:]
def test_certonly_and_run_with_hook(self): def test_certonly_and_run_with_hook(self):
for verb in ("certonly", "run",): for verb in ("certonly", "run",):
@@ -238,8 +239,8 @@ class PostHookTest(HookTest):
self.assertEqual(self._get_eventually(), expected) self.assertEqual(self._get_eventually(), expected)
def _get_eventually(self): def _get_eventually(self):
from certbot.hooks import post_hook from certbot.hooks import post_hooks
return post_hook.eventually return post_hooks
class RunSavedPostHooksTest(HookTest): class RunSavedPostHooksTest(HookTest):
@@ -248,23 +249,23 @@ class RunSavedPostHooksTest(HookTest):
@classmethod @classmethod
def _call(cls, *args, **kwargs): def _call(cls, *args, **kwargs):
from certbot.hooks import run_saved_post_hooks from certbot.hooks import run_saved_post_hooks
return run_saved_post_hooks(*args, **kwargs) return run_saved_post_hooks()
def _call_with_mock_execute_and_eventually(self, *args, **kwargs): def _call_with_mock_execute_and_eventually(self, *args, **kwargs):
"""Call run_saved_post_hooks but mock out execute and eventually """Call run_saved_post_hooks but mock out execute and eventually
certbot.hooks.post_hook.eventually is replaced with certbot.hooks.post_hooks is replaced with
self.eventually. The mock execute object is returned rather than self.eventually. The mock execute object is returned rather than
the return value of run_saved_post_hooks. the return value of run_saved_post_hooks.
""" """
eventually_path = "certbot.hooks.post_hook.eventually" eventually_path = "certbot.hooks.post_hooks"
with mock.patch(eventually_path, new=self.eventually): with mock.patch(eventually_path, new=self.eventually):
return self._call_with_mock_execute(*args, **kwargs) return self._call_with_mock_execute(*args, **kwargs)
def setUp(self): def setUp(self):
super(RunSavedPostHooksTest, self).setUp() super(RunSavedPostHooksTest, self).setUp()
self.eventually = [] self.eventually = [] # type: List[str]
def test_empty(self): def test_empty(self):
self.assertFalse(self._call_with_mock_execute_and_eventually().called) self.assertFalse(self._call_with_mock_execute_and_eventually().called)
+7 -6
View File
@@ -10,6 +10,7 @@ import mock
import six import six
from acme import messages from acme import messages
from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
@@ -21,9 +22,9 @@ class PreArgParseSetupTest(unittest.TestCase):
"""Tests for certbot.log.pre_arg_parse_setup.""" """Tests for certbot.log.pre_arg_parse_setup."""
@classmethod @classmethod
def _call(cls, *args, **kwargs): def _call(cls, *args, **kwargs): # pylint: disable=unused-argument
from certbot.log import pre_arg_parse_setup from certbot.log import pre_arg_parse_setup
return pre_arg_parse_setup(*args, **kwargs) return pre_arg_parse_setup()
@mock.patch('certbot.log.sys') @mock.patch('certbot.log.sys')
@mock.patch('certbot.log.pre_arg_parse_except_hook') @mock.patch('certbot.log.pre_arg_parse_except_hook')
@@ -38,16 +39,16 @@ class PreArgParseSetupTest(unittest.TestCase):
mock_root_logger.setLevel.assert_called_once_with(logging.DEBUG) mock_root_logger.setLevel.assert_called_once_with(logging.DEBUG)
self.assertEqual(mock_root_logger.addHandler.call_count, 2) self.assertEqual(mock_root_logger.addHandler.call_count, 2)
MemoryHandler = logging.handlers.MemoryHandler memory_handler = None # type: Optional[logging.handlers.MemoryHandler]
memory_handler = None
for call in mock_root_logger.addHandler.call_args_list: for call in mock_root_logger.addHandler.call_args_list:
handler = call[0][0] handler = call[0][0]
if memory_handler is None and isinstance(handler, MemoryHandler): if memory_handler is None and isinstance(handler, logging.handlers.MemoryHandler):
memory_handler = handler memory_handler = handler
target = memory_handler.target # type: ignore
else: else:
self.assertTrue(isinstance(handler, logging.StreamHandler)) self.assertTrue(isinstance(handler, logging.StreamHandler))
self.assertTrue( self.assertTrue(
isinstance(memory_handler.target, logging.StreamHandler)) isinstance(target, logging.StreamHandler))
mock_register.assert_called_once_with(logging.shutdown) mock_register.assert_called_once_with(logging.shutdown)
mock_sys.excepthook(1, 2, 3) mock_sys.excepthook(1, 2, 3)
+9 -8
View File
@@ -16,12 +16,14 @@ import josepy as jose
import six import six
from six.moves import reload_module # pylint: disable=import-error from six.moves import reload_module # pylint: disable=import-error
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import account from certbot import account
from certbot import cli from certbot import cli
from certbot import constants from certbot import constants
from certbot import configuration from certbot import configuration
from certbot import crypto_util from certbot import crypto_util
from certbot import errors from certbot import errors
from certbot import interfaces # pylint: disable=unused-import
from certbot import main from certbot import main
from certbot import updater from certbot import updater
from certbot import util from certbot import util
@@ -600,14 +602,14 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
if mockisfile: if mockisfile:
orig_open = os.path.isfile orig_open = os.path.isfile
def mock_isfile(fn, *args, **kwargs): def mock_isfile(fn, *args, **kwargs): # pylint: disable=unused-argument
"""Mock os.path.isfile()""" """Mock os.path.isfile()"""
if (fn.endswith("cert") or if (fn.endswith("cert") or
fn.endswith("chain") or fn.endswith("chain") or
fn.endswith("privkey")): fn.endswith("privkey")):
return True return True
else: else:
return orig_open(fn, *args, **kwargs) return orig_open(fn)
with mock.patch("os.path.isfile") as mock_if: with mock.patch("os.path.isfile") as mock_if:
mock_if.side_effect = mock_isfile mock_if.side_effect = mock_isfile
@@ -836,7 +838,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.main.plugins_disco') @mock.patch('certbot.main.plugins_disco')
@mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_no_args(self, _det, mock_disco): def test_plugins_no_args(self, _det, mock_disco):
ifaces = [] ifaces = [] # type: List[interfaces.IPlugin]
plugins = mock_disco.PluginsRegistry.find_all() plugins = mock_disco.PluginsRegistry.find_all()
stdout = six.StringIO() stdout = six.StringIO()
@@ -851,7 +853,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.main.plugins_disco') @mock.patch('certbot.main.plugins_disco')
@mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_no_args_unprivileged(self, _det, mock_disco): def test_plugins_no_args_unprivileged(self, _det, mock_disco):
ifaces = [] ifaces = [] # type: List[interfaces.IPlugin]
plugins = mock_disco.PluginsRegistry.find_all() plugins = mock_disco.PluginsRegistry.find_all()
def throw_error(directory, mode, uid, strict): def throw_error(directory, mode, uid, strict):
@@ -873,7 +875,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.main.plugins_disco') @mock.patch('certbot.main.plugins_disco')
@mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_init(self, _det, mock_disco): def test_plugins_init(self, _det, mock_disco):
ifaces = [] ifaces = [] # type: List[interfaces.IPlugin]
plugins = mock_disco.PluginsRegistry.find_all() plugins = mock_disco.PluginsRegistry.find_all()
stdout = six.StringIO() stdout = six.StringIO()
@@ -891,7 +893,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
@mock.patch('certbot.main.plugins_disco') @mock.patch('certbot.main.plugins_disco')
@mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_prepare(self, _det, mock_disco): def test_plugins_prepare(self, _det, mock_disco):
ifaces = [] ifaces = [] # type: List[interfaces.IPlugin]
plugins = mock_disco.PluginsRegistry.find_all() plugins = mock_disco.PluginsRegistry.find_all()
stdout = six.StringIO() stdout = six.StringIO()
@@ -1040,9 +1042,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
mock_client.obtain_certificate.return_value = (mock_certr, 'chain', mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
mock_key, 'csr') mock_key, 'csr')
def write_msg(message, *args, **kwargs): def write_msg(message, *args, **kwargs): # pylint: disable=unused-argument
"""Write message to stdout.""" """Write message to stdout."""
_, _ = args, kwargs
stdout.write(message) stdout.write(message)
try: try:
+9 -9
View File
@@ -12,7 +12,7 @@ class ReporterTest(unittest.TestCase):
from certbot import reporter from certbot import reporter
self.reporter = reporter.Reporter(mock.MagicMock(quiet=False)) self.reporter = reporter.Reporter(mock.MagicMock(quiet=False))
self.old_stdout = sys.stdout self.old_stdout = sys.stdout # type: ignore
sys.stdout = six.StringIO() sys.stdout = six.StringIO()
def tearDown(self): def tearDown(self):
@@ -21,32 +21,32 @@ class ReporterTest(unittest.TestCase):
def test_multiline_message(self): def test_multiline_message(self):
self.reporter.add_message("Line 1\nLine 2", self.reporter.LOW_PRIORITY) self.reporter.add_message("Line 1\nLine 2", self.reporter.LOW_PRIORITY)
self.reporter.print_messages() self.reporter.print_messages()
output = sys.stdout.getvalue() output = sys.stdout.getvalue() # type: ignore
self.assertTrue("Line 1\n" in output) self.assertTrue("Line 1\n" in output)
self.assertTrue("Line 2" in output) self.assertTrue("Line 2" in output)
def test_tty_print_empty(self): def test_tty_print_empty(self):
sys.stdout.isatty = lambda: True sys.stdout.isatty = lambda: True # type: ignore
self.test_no_tty_print_empty() self.test_no_tty_print_empty()
def test_no_tty_print_empty(self): def test_no_tty_print_empty(self):
self.reporter.print_messages() self.reporter.print_messages()
self.assertEqual(sys.stdout.getvalue(), "") self.assertEqual(sys.stdout.getvalue(), "") # type: ignore
try: try:
raise ValueError raise ValueError
except ValueError: except ValueError:
self.reporter.print_messages() self.reporter.print_messages()
self.assertEqual(sys.stdout.getvalue(), "") self.assertEqual(sys.stdout.getvalue(), "") # type: ignore
def test_tty_successful_exit(self): def test_tty_successful_exit(self):
sys.stdout.isatty = lambda: True sys.stdout.isatty = lambda: True # type: ignore
self._successful_exit_common() self._successful_exit_common()
def test_no_tty_successful_exit(self): def test_no_tty_successful_exit(self):
self._successful_exit_common() self._successful_exit_common()
def test_tty_unsuccessful_exit(self): def test_tty_unsuccessful_exit(self):
sys.stdout.isatty = lambda: True sys.stdout.isatty = lambda: True # type: ignore
self._unsuccessful_exit_common() self._unsuccessful_exit_common()
def test_no_tty_unsuccessful_exit(self): def test_no_tty_unsuccessful_exit(self):
@@ -55,7 +55,7 @@ class ReporterTest(unittest.TestCase):
def _successful_exit_common(self): def _successful_exit_common(self):
self._add_messages() self._add_messages()
self.reporter.print_messages() self.reporter.print_messages()
output = sys.stdout.getvalue() output = sys.stdout.getvalue() # type: ignore
self.assertTrue("IMPORTANT NOTES:" in output) self.assertTrue("IMPORTANT NOTES:" in output)
self.assertTrue("High" in output) self.assertTrue("High" in output)
self.assertTrue("Med" in output) self.assertTrue("Med" in output)
@@ -67,7 +67,7 @@ class ReporterTest(unittest.TestCase):
raise ValueError raise ValueError
except ValueError: except ValueError:
self.reporter.print_messages() self.reporter.print_messages()
output = sys.stdout.getvalue() output = sys.stdout.getvalue() # type: ignore
self.assertTrue("IMPORTANT NOTES:" in output) self.assertTrue("IMPORTANT NOTES:" in output)
self.assertTrue("High" in output) self.assertTrue("High" in output)
self.assertTrue("Med" not in output) self.assertTrue("Med" not in output)
+9 -5
View File
@@ -20,6 +20,7 @@ from collections import OrderedDict
import configargparse import configargparse
from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
from certbot import lock from certbot import lock
@@ -218,8 +219,12 @@ def safe_open(path, mode="w", chmod=None, buffering=None):
""" """
# pylint: disable=star-args # pylint: disable=star-args
open_args = () if chmod is None else (chmod,) open_args = () # type: Union[Tuple[()], Tuple[int]]
fdopen_args = () if buffering is None else (buffering,) if chmod is not None:
open_args = (chmod,)
fdopen_args = () # type: Union[Tuple[()], Tuple[int]]
if buffering is not None:
fdopen_args = (buffering,)
return os.fdopen( return os.fdopen(
os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args), os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args),
mode, *fdopen_args) mode, *fdopen_args)
@@ -303,9 +308,8 @@ def get_filtered_names(all_names):
for name in all_names: for name in all_names:
try: try:
filtered_names.add(enforce_le_validity(name)) filtered_names.add(enforce_le_validity(name))
except errors.ConfigurationError as error: except errors.ConfigurationError:
logger.debug('Not suggesting name "%s"', name) logger.debug('Not suggesting name "%s"', name, exc_info=True)
logger.debug(error)
return filtered_names return filtered_names
+6
View File
@@ -5,6 +5,12 @@ ignore_missing_imports = True
[mypy-acme.*] [mypy-acme.*]
check_untyped_defs = True check_untyped_defs = True
[mypy-acme.magic_typing_test]
ignore_errors = True
[mypy-certbot.*]
check_untyped_defs = True
[mypy-certbot_apache.*] [mypy-certbot_apache.*]
check_untyped_defs = True check_untyped_defs = True
+1 -1
View File
@@ -34,7 +34,7 @@ version = meta['version']
# specified here to avoid masking the more specific request requirements in # specified here to avoid masking the more specific request requirements in
# acme. See https://github.com/pypa/pip/issues/988 for more info. # acme. See https://github.com/pypa/pip/issues/988 for more info.
install_requires = [ install_requires = [
'acme>=0.22.1', 'acme>0.24.0',
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but # 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 # 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. # in which we added 2.6 support (see #2243), so we relax the requirement.
+1 -1
View File
@@ -30,7 +30,7 @@ josepy==1.0.1
logger==1.4 logger==1.4
logilab-common==1.4.1 logilab-common==1.4.1
MarkupSafe==1.0 MarkupSafe==1.0
mypy==0.580 mypy==0.600
ndg-httpsclient==0.3.2 ndg-httpsclient==0.3.2
oauth2client==2.0.0 oauth2client==2.0.0
pathlib2==2.3.0 pathlib2==2.3.0
+1 -1
View File
@@ -121,8 +121,8 @@ commands =
[testenv:mypy] [testenv:mypy]
basepython = python3 basepython = python3
commands = commands =
{[base]pip_install} .[dev3]
{[base]install_packages} {[base]install_packages}
{[base]pip_install} .[dev3]
mypy {[base]source_paths} mypy {[base]source_paths}
[testenv:apacheconftest] [testenv:apacheconftest]