From dac0b2c187fd94de9725da623587729ef356b60d Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 25 Jan 2022 00:16:19 +0100 Subject: [PATCH] Typed jose fields (#9073) * Add generic methods to save some casts, and fix lint * Update current and oldest pinning * Fix classes * Remove some todos thanks to josepy 1.11.0 * Cleanup some useless pylint disable * Finish complete typing * Better TypeVar names * Upgrade pinning and fix some typing errors * Use protocol * Fix types in apache Co-authored-by: Brad Warren --- acme/acme/challenges.py | 66 +++--- acme/acme/client.py | 41 ++-- acme/acme/crypto_util.py | 13 +- acme/acme/fields.py | 19 +- acme/acme/jws.py | 18 +- acme/acme/messages.py | 220 ++++++++++-------- acme/acme/standalone.py | 5 +- acme/setup.py | 3 +- acme/tests/fields_test.py | 4 +- .../certbot_apache/_internal/configurator.py | 25 +- .../certbot_apache/_internal/http_01.py | 12 +- .../certbot_integration_tests/utils/misc.py | 3 +- .../certbot_compatibility_test/test_driver.py | 2 +- .../_internal/dns_rfc2136.py | 12 +- .../certbot_nginx/_internal/configurator.py | 7 +- .../certbot_nginx/_internal/http_01.py | 6 +- .../certbot_nginx/_internal/nginxparser.py | 10 +- certbot/certbot/_internal/account.py | 8 +- certbot/certbot/_internal/auth_handler.py | 15 +- certbot/certbot/_internal/client.py | 2 +- certbot/certbot/_internal/plugins/manual.py | 2 +- .../certbot/_internal/plugins/standalone.py | 2 +- certbot/certbot/_internal/plugins/webroot.py | 8 +- certbot/certbot/_internal/reporter.py | 2 +- certbot/certbot/_internal/snap_config.py | 4 +- certbot/certbot/crypto_util.py | 5 +- certbot/certbot/plugins/common.py | 4 +- .../plugins/dns_test_common_lexicon.py | 4 +- certbot/certbot/tests/acme_util.py | 4 +- tools/oldest_constraints.txt | 66 +++--- tools/pinning/current/pyproject.toml | 4 - tools/requirements.txt | 127 +++++----- 32 files changed, 398 insertions(+), 325 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index de842a261..94d121a21 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -12,6 +12,8 @@ from typing import Mapping from typing import Optional from typing import Tuple from typing import Type +from typing import TypeVar +from typing import Union from cryptography.hazmat.primitives import hashes import josepy as jose @@ -27,6 +29,8 @@ from acme.mixins import TypeMixin logger = logging.getLogger(__name__) +GenericChallenge = TypeVar('GenericChallenge', bound='Challenge') + class Challenge(jose.TypedJSONObjectWithFields): # _fields_to_partial_json @@ -34,9 +38,10 @@ class Challenge(jose.TypedJSONObjectWithFields): TYPES: Dict[str, Type['Challenge']] = {} @classmethod - def from_json(cls, jobj: Mapping[str, Any]) -> 'Challenge': + def from_json(cls: Type[GenericChallenge], + jobj: Mapping[str, Any]) -> Union[GenericChallenge, 'UnrecognizedChallenge']: try: - return super().from_json(jobj) + return cast(GenericChallenge, super().from_json(jobj)) except jose.UnrecognizedTypeError as error: logger.debug(error) return UnrecognizedChallenge.from_json(jobj) @@ -47,7 +52,7 @@ class ChallengeResponse(ResourceMixin, TypeMixin, jose.TypedJSONObjectWithFields """ACME challenge response.""" TYPES: Dict[str, Type['ChallengeResponse']] = {} resource_type = 'challenge' - resource = fields.Resource(resource_type) + resource: str = fields.resource(resource_type) class UnrecognizedChallenge(Challenge): @@ -62,6 +67,7 @@ class UnrecognizedChallenge(Challenge): :ivar jobj: Original JSON decoded object. """ + jobj: Dict[str, Any] def __init__(self, jobj: Mapping[str, Any]) -> None: super().__init__() @@ -85,7 +91,7 @@ class _TokenChallenge(Challenge): """Minimum size of the :attr:`token` in bytes.""" # TODO: acme-spec doesn't specify token as base64-encoded value - token: bytes = jose.Field( + token: bytes = jose.field( "token", encoder=jose.encode_b64jose, decoder=functools.partial( jose.decode_b64jose, size=TOKEN_SIZE, minimum=True)) @@ -108,10 +114,10 @@ class _TokenChallenge(Challenge): class KeyAuthorizationChallengeResponse(ChallengeResponse): """Response to Challenges based on Key Authorization. - :param unicode key_authorization: + :param str key_authorization: """ - key_authorization = jose.Field("keyAuthorization") + key_authorization: str = jose.field("keyAuthorization") thumbprint_hash_function = hashes.SHA256 def verify(self, chall: 'KeyAuthorizationChallenge', account_public_key: jose.JWK) -> bool: @@ -126,7 +132,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): :rtype: bool """ - parts = self.key_authorization.split('.') + parts = self.key_authorization.split('.') # pylint: disable=no-member if len(parts) != 2: logger.debug("Key authorization (%r) is not well formed", self.key_authorization) @@ -152,6 +158,9 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): return jobj +# TODO: Make this method a generic of K (bound=KeyAuthorizationChallenge), response_cls of type +# Type[K] and use it in response/response_and_validation return types once Python 3.6 support is +# dropped (do not support generic ABC classes, see https://github.com/python/typing/issues/449). class KeyAuthorizationChallenge(_TokenChallenge, metaclass=abc.ABCMeta): """Challenge based on Key Authorization. @@ -168,7 +177,7 @@ class KeyAuthorizationChallenge(_TokenChallenge, metaclass=abc.ABCMeta): """Generate Key Authorization. :param JWK account_key: - :rtype unicode: + :rtype str: """ return self.encode("token") + "." + jose.b64encode( @@ -229,7 +238,7 @@ class DNS01Response(KeyAuthorizationChallengeResponse): around `KeyAuthorizationChallengeResponse.verify`. :param challenges.DNS01 chall: Corresponding challenge. - :param unicode domain: Domain name being verified. + :param str domain: Domain name being verified. :param JWK account_public_key: Public key for the key pair being authorized. @@ -257,7 +266,7 @@ class DNS01(KeyAuthorizationChallenge): """Generate validation. :param JWK account_key: - :rtype: unicode + :rtype: str """ return jose.b64encode(hashlib.sha256(self.key_authorization( @@ -266,7 +275,8 @@ class DNS01(KeyAuthorizationChallenge): def validation_domain_name(self, name: str) -> str: """Domain name for TXT validation record. - :param unicode name: Domain name being validated. + :param str name: Domain name being validated. + :rtype: str """ return "{0}.{1}".format(self.LABEL, name) @@ -293,7 +303,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse): """Simple verify. :param challenges.SimpleHTTP chall: Corresponding challenge. - :param unicode domain: Domain name being verified. + :param str domain: Domain name being verified. :param JWK account_public_key: Public key for the key pair being authorized. :param int port: Port used in the validation. @@ -357,7 +367,7 @@ class HTTP01(KeyAuthorizationChallenge): def path(self) -> str: """Path (starting with '/') for provisioned resource. - :rtype: string + :rtype: str """ return '/' + self.URI_ROOT_PATH + '/' + self.encode('token') @@ -368,8 +378,8 @@ class HTTP01(KeyAuthorizationChallenge): Forms an URI to the HTTPS server provisioned resource (containing :attr:`~SimpleHTTP.token`). - :param unicode domain: Domain name being verified. - :rtype: string + :param str domain: Domain name being verified. + :rtype: str """ return "http://" + domain + self.path @@ -378,7 +388,7 @@ class HTTP01(KeyAuthorizationChallenge): """Generate validation. :param JWK account_key: - :rtype: unicode + :rtype: str """ return self.key_authorization(account_key) @@ -409,7 +419,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse): ) -> Tuple[crypto.X509, crypto.PKey]: """Generate tls-alpn-01 certificate. - :param unicode domain: Domain verified by the challenge. + :param str domain: Domain verified by the challenge. :param OpenSSL.crypto.PKey key: Optional private key used in certificate generation. If not provided (``None``), then fresh key will be generated. @@ -433,8 +443,8 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse): port: Optional[int] = None) -> crypto.X509: """Probe tls-alpn-01 challenge certificate. - :param unicode domain: domain being validated, required. - :param string host: IP address used to probe the certificate. + :param str domain: domain being validated, required. + :param str host: IP address used to probe the certificate. :param int port: Port used to probe the certificate. """ @@ -450,7 +460,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse): def verify_cert(self, domain: str, cert: crypto.X509) -> bool: """Verify tls-alpn-01 challenge certificate. - :param unicode domain: Domain name being validated. + :param str domain: Domain name being validated. :param OpensSSL.crypto.X509 cert: Challenge certificate. :returns: Whether the certificate was successfully verified. @@ -523,7 +533,7 @@ class TLSALPN01(KeyAuthorizationChallenge): """Generate validation. :param JWK account_key: - :param unicode domain: Domain verified by the challenge. + :param str domain: Domain verified by the challenge. :param OpenSSL.crypto.PKey cert_key: Optional private key used in certificate generation. If not provided (``None``), then fresh key will be generated. @@ -531,9 +541,10 @@ class TLSALPN01(KeyAuthorizationChallenge): :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey` """ - return self.response(account_key).gen_cert( + # TODO: Remove cast when response() is generic. + return cast(TLSALPN01Response, self.response(account_key)).gen_cert( key=kwargs.get('cert_key'), - domain=kwargs.get('domain')) + domain=cast(str, kwargs.get('domain'))) @staticmethod def is_supported() -> bool: @@ -599,13 +610,12 @@ class DNS(_TokenChallenge): :rtype: DNSResponse """ - return DNSResponse(validation=self.gen_validation( - account_key, **kwargs)) + return DNSResponse(validation=self.gen_validation(account_key, **kwargs)) def validation_domain_name(self, name: str) -> str: """Domain name for TXT validation record. - :param unicode name: Domain name being validated. + :param str name: Domain name being validated. """ return "{0}.{1}".format(self.LABEL, name) @@ -620,7 +630,7 @@ class DNSResponse(ChallengeResponse): """ typ = "dns" - validation = jose.Field("validation", decoder=jose.JWS.from_json) + validation: jose.JWS = jose.field("validation", decoder=jose.JWS.from_json) def check_validation(self, chall: 'DNS', account_public_key: jose.JWK) -> bool: """Check validation. @@ -631,4 +641,4 @@ class DNSResponse(ChallengeResponse): :rtype: bool """ - return chall.check_validation(cast(jose.JWS, self.validation), account_public_key) + return chall.check_validation(self.validation, account_public_key) diff --git a/acme/acme/client.py b/acme/acme/client.py index ae2f261aa..7e87e7474 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -19,6 +19,7 @@ from typing import cast from typing import Dict from typing import Iterable from typing import List +from typing import Mapping from typing import Optional from typing import Set from typing import Text @@ -33,6 +34,7 @@ from requests.adapters import HTTPAdapter from requests.utils import parse_header_links from requests_toolbelt.adapters.source import SourceAddressAdapter +from acme import challenges from acme import crypto_util from acme import errors from acme import jws @@ -156,12 +158,12 @@ class ClientBase: authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), uri=response.headers.get('Location', uri)) - if identifier is not None and authzr.body.identifier != identifier: + if identifier is not None and authzr.body.identifier != identifier: # pylint: disable=no-member raise errors.UnexpectedUpdate(authzr) return authzr - def answer_challenge(self, challb: messages.ChallengeBody, response: requests.Response - ) -> messages.ChallengeResource: + def answer_challenge(self, challb: messages.ChallengeBody, + response: challenges.ChallengeResponse) -> messages.ChallengeResource: """Answer challenge. :param challb: Challenge Resource body. @@ -176,15 +178,15 @@ class ClientBase: :raises .UnexpectedUpdate: """ - response = self._post(challb.uri, response) + resp = self._post(challb.uri, response) try: - authzr_uri = response.links['up']['url'] + authzr_uri = resp.links['up']['url'] except KeyError: raise errors.ClientError('"up" Link header missing') challr = messages.ChallengeResource( authzr_uri=authzr_uri, - body=messages.ChallengeBody.from_json(response.json())) - # TODO: check that challr.uri == response.headers['Location']? + body=messages.ChallengeBody.from_json(resp.json())) + # TODO: check that challr.uri == resp.headers['Location']? if challr.uri != challb.uri: raise errors.UnexpectedUpdate(challr.uri) return challr @@ -492,7 +494,7 @@ class Client(ClientBase): updated[authzr] = updated_authzr attempts[authzr] += 1 - if updated_authzr.body.status not in ( + if updated_authzr.body.status not in ( # pylint: disable=no-member messages.STATUS_VALID, messages.STATUS_INVALID): if attempts[authzr] < max_attempts: # push back to the priority queue, with updated retry_after @@ -599,7 +601,7 @@ class Client(ClientBase): :raises .ClientError: If revocation is unsuccessful. """ - self._revoke(cert, rsn, self.directory[cast(str, messages.Revocation)]) + self._revoke(cert, rsn, self.directory[messages.Revocation]) class ClientV2(ClientBase): @@ -756,7 +758,7 @@ class ClientV2(ClientBase): for url in orderr.body.authorizations: while datetime.datetime.now() < deadline: authzr = self._authzr_from_response(self._post_as_get(url), uri=url) - if authzr.body.status != messages.STATUS_PENDING: + if authzr.body.status != messages.STATUS_PENDING: # pylint: disable=no-member responses.append(authzr) break time.sleep(1) @@ -897,11 +899,11 @@ class BackwardsCompatibleClientV2: check_tos_cb(tos) if self.acme_version == 1: client_v1 = cast(Client, self.client) - regr = client_v1.register(regr) - if regr.terms_of_service is not None: - _assess_tos(regr.terms_of_service) - return client_v1.agree_to_tos(regr) - return regr + regr_res = client_v1.register(regr) + if regr_res.terms_of_service is not None: + _assess_tos(regr_res.terms_of_service) + return client_v1.agree_to_tos(regr_res) + return regr_res else: client_v2 = cast(ClientV2, self.client) if "terms_of_service" in client_v2.directory.meta: @@ -970,7 +972,8 @@ class BackwardsCompatibleClientV2: 'certificate, please rerun the command for a new one.') cert = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode() + OpenSSL.crypto.FILETYPE_PEM, + cast(OpenSSL.crypto.X509, cast(jose.ComparableX509, certr.body).wrapped)).decode() chain_str = crypto_util.dump_pyopenssl_chain(chain).decode() return orderr.update(fullchain_pem=(cert + chain_str)) @@ -1056,7 +1059,7 @@ class ClientNetwork: pass def _wrap_in_jws(self, obj: jose.JSONDeSerializable, nonce: str, url: str, - acme_version: int) -> jose.JWS: + acme_version: int) -> str: """Wrap `JSONDeSerializable` object in JWS. .. todo:: Implement ``acmePath``. @@ -1064,7 +1067,7 @@ class ClientNetwork: :param josepy.JSONDeSerializable obj: :param str url: The URL to which this object will be POSTed :param str nonce: - :rtype: `josepy.JWS` + :rtype: str """ if isinstance(obj, VersionedLEACMEMixin): @@ -1082,7 +1085,7 @@ class ClientNetwork: if self.account is not None: kwargs["kid"] = self.account["uri"] kwargs["key"] = self.key - return jws.JWS.sign(jobj, **kwargs).json_dumps(indent=2) + return jws.JWS.sign(jobj, **cast(Mapping[str, Any], kwargs)).json_dumps(indent=2) @classmethod def _check_response(cls, response: requests.Response, diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index b337e7697..eb6672926 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -278,7 +278,7 @@ def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req]) :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. :returns: A list of Subject Alternative Names that is DNS. - :rtype: `list` of `unicode` + :rtype: `list` of `str` """ # This function finds SANs with dns name @@ -300,7 +300,7 @@ def _pyopenssl_cert_or_req_san_ip(cert_or_req: Union[crypto.X509, crypto.X509Req :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. :returns: A list of Subject Alternative Names that are IP Addresses. - :rtype: `list` of `unicode`. note that this returns as string, not IPaddress object + :rtype: `list` of `str`. note that this returns as string, not IPaddress object """ @@ -320,7 +320,7 @@ def _pyopenssl_extract_san_list_raw(cert_or_req: Union[crypto.X509, crypto.X509R :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. :returns: raw san strings, parsed byte as utf-8 - :rtype: `list` of `unicode` + :rtype: `list` of `str` """ # This function finds SANs by dumping the certificate/CSR to text and @@ -352,7 +352,7 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None, ) -> crypto.X509: """Generate new self-signed certificate. - :type domains: `list` of `unicode` + :type domains: `list` of `str` :param OpenSSL.crypto.PKey key: :param bool force_san: :param extensions: List of additional extensions to include in the cert. @@ -410,7 +410,8 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None, return cert -def dump_pyopenssl_chain(chain: List[crypto.X509], filetype: int = crypto.FILETYPE_PEM) -> bytes: +def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X509]], + filetype: int = crypto.FILETYPE_PEM) -> bytes: """Dump certificate chain into a bundle. :param list chain: List of `OpenSSL.crypto.X509` (or wrapped in @@ -425,6 +426,8 @@ def dump_pyopenssl_chain(chain: List[crypto.X509], filetype: int = crypto.FILETY def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes: if isinstance(cert, jose.ComparableX509): + if isinstance(cert.wrapped, crypto.X509Req): + raise errors.Error("Unexpected CSR provided.") # pragma: no cover cert = cert.wrapped return crypto.dump_certificate(filetype, cert) diff --git a/acme/acme/fields.py b/acme/acme/fields.py index b15489020..191231df2 100644 --- a/acme/acme/fields.py +++ b/acme/acme/fields.py @@ -56,8 +56,8 @@ class Resource(jose.Field): def __init__(self, resource_type: str, *args: Any, **kwargs: Any) -> None: self.resource_type = resource_type - super().__init__( - 'resource', default=resource_type, *args, **kwargs) + kwargs['default'] = resource_type + super().__init__('resource', *args, **kwargs) def decode(self, value: Any) -> Any: if value != self.resource_type: @@ -65,3 +65,18 @@ class Resource(jose.Field): 'Wrong resource type: {0} instead of {1}'.format( value, self.resource_type)) return value + + +def fixed(json_name: str, value: Any) -> Any: + """Generates a type-friendly Fixed field.""" + return Fixed(json_name, value) + + +def rfc3339(json_name: str, omitempty: bool = False) -> Any: + """Generates a type-friendly RFC3339 field.""" + return RFC3339Field(json_name, omitempty=omitempty) + + +def resource(resource_type: str) -> Any: + """Generates a type-friendly Resource field.""" + return Resource(resource_type) diff --git a/acme/acme/jws.py b/acme/acme/jws.py index bde1bb590..2a9627c6d 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -12,14 +12,14 @@ import josepy as jose class Header(jose.Header): """ACME-specific JOSE Header. Implements nonce, kid, and url. """ - nonce = jose.Field('nonce', omitempty=True, encoder=jose.encode_b64jose) - kid = jose.Field('kid', omitempty=True) - url = jose.Field('url', omitempty=True) + nonce: Optional[bytes] = jose.field('nonce', omitempty=True, encoder=jose.encode_b64jose) + kid: Optional[str] = jose.field('kid', omitempty=True) + url: Optional[str] = jose.field('url', omitempty=True) # Mypy does not understand the josepy magic happening here, and falsely claims # that nonce is redefined. Let's ignore the type check here. - @nonce.decoder # type: ignore - def nonce(value: str) -> bytes: # pylint: disable=no-self-argument,missing-function-docstring + @nonce.decoder # type: ignore[no-redef,union-attr] + def nonce(value: str) -> bytes: # type: ignore[misc] # pylint: disable=no-self-argument,missing-function-docstring try: return jose.decode_b64jose(value) except jose.DeserializationError as error: @@ -29,12 +29,12 @@ class Header(jose.Header): class Signature(jose.Signature): """ACME-specific Signature. Uses ACME-specific Header for customer fields.""" - __slots__ = jose.Signature._orig_slots # pylint: disable=no-member + __slots__ = jose.Signature._orig_slots # type: ignore[attr-defined] # pylint: disable=protected-access,no-member # TODO: decoder/encoder should accept cls? Otherwise, subclassing # JSONObjectWithFields is tricky... header_cls = Header - header = jose.Field( + header: Header = jose.field( 'header', omitempty=True, default=header_cls(), decoder=header_cls.from_json) @@ -44,10 +44,10 @@ class Signature(jose.Signature): class JWS(jose.JWS): """ACME-specific JWS. Includes none, url, and kid in protected header.""" signature_cls = Signature - __slots__ = jose.JWS._orig_slots + __slots__ = jose.JWS._orig_slots # type: ignore[attr-defined] # pylint: disable=protected-access @classmethod - # pylint: disable=arguments-differ + # type: ignore[override] # pylint: disable=arguments-differ def sign(cls, payload: bytes, key: jose.JWK, alg: jose.JWASignature, nonce: Optional[bytes], url: Optional[str] = None, kid: Optional[str] = None) -> jose.JWS: # Per ACME spec, jwk and kid are mutually exclusive, so only include a diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 80dacb674..c02a600da 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -1,4 +1,5 @@ """ACME protocol messages.""" +import datetime from collections.abc import Hashable import json from typing import Any @@ -7,9 +8,12 @@ from typing import Iterator from typing import List from typing import Mapping from typing import MutableMapping +from typing import Optional from typing import Tuple from typing import Type -from typing import Optional +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union import josepy as jose @@ -20,6 +24,11 @@ from acme import jws from acme import util from acme.mixins import ResourceMixin +if TYPE_CHECKING: + from typing_extensions import Protocol # pragma: no cover +else: + Protocol = object + OLD_ERROR_PREFIX = "urn:acme:error:" ERROR_PREFIX = "urn:ietf:params:acme:error:" @@ -75,20 +84,20 @@ class Error(jose.JSONObjectWithFields, errors.Error): https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00 - :ivar unicode typ: - :ivar unicode title: - :ivar unicode detail: + :ivar str typ: + :ivar str title: + :ivar str detail: """ - typ = jose.Field('type', omitempty=True, default='about:blank') - title = jose.Field('title', omitempty=True) - detail = jose.Field('detail', omitempty=True) + typ: str = jose.field('type', omitempty=True, default='about:blank') + title: str = jose.field('title', omitempty=True) + detail: str = jose.field('detail', omitempty=True) @classmethod def with_code(cls, code: str, **kwargs: Any) -> 'Error': """Create an Error instance with an ACME Error code. - :unicode code: An ACME error code, like 'dnssec'. + :str code: An ACME error code, like 'dnssec'. :kwargs: kwargs to pass to Error. """ @@ -98,14 +107,14 @@ class Error(jose.JSONObjectWithFields, errors.Error): typ = ERROR_PREFIX + code # Mypy will not understand that the Error constructor accepts a named argument # "typ" because of josepy magic. Let's ignore the type check here. - return cls(typ=typ, **kwargs) # type: ignore + return cls(typ=typ, **kwargs) @property def description(self) -> Optional[str]: """Hardcoded error description based on its type. :returns: Description if standard ACME error or ``None``. - :rtype: unicode + :rtype: str """ return ERROR_TYPE_DESCRIPTIONS.get(self.typ) @@ -117,7 +126,7 @@ class Error(jose.JSONObjectWithFields, errors.Error): Basically self.typ without the ERROR_PREFIX. :returns: error code if standard ACME code or ``None``. - :rtype: unicode + :rtype: str """ code = str(self.typ).rsplit(':', maxsplit=1)[-1] @@ -164,7 +173,7 @@ class _Constant(jose.JSONDeSerializable, Hashable): class Status(_Constant): """ACME "status" field.""" - POSSIBLE_NAMES: Dict[str, 'Status'] = {} + POSSIBLE_NAMES: Dict[str, _Constant] = {} STATUS_UNKNOWN = Status('unknown') @@ -179,7 +188,7 @@ STATUS_DEACTIVATED = Status('deactivated') class IdentifierType(_Constant): """ACME identifier type.""" - POSSIBLE_NAMES: Dict[str, 'IdentifierType'] = {} + POSSIBLE_NAMES: Dict[str, _Constant] = {} IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder @@ -190,25 +199,35 @@ class Identifier(jose.JSONObjectWithFields): """ACME identifier. :ivar IdentifierType typ: - :ivar unicode value: + :ivar str value: """ - typ = jose.Field('type', decoder=IdentifierType.from_json) - value = jose.Field('value') + typ: IdentifierType = jose.field('type', decoder=IdentifierType.from_json) + value: str = jose.field('value') + + +class HasResourceType(Protocol): + """ + Represents a class with a resource_type class parameter of type string. + """ + resource_type: str = NotImplemented + + +GenericHasResourceType = TypeVar("GenericHasResourceType", bound=HasResourceType) class Directory(jose.JSONDeSerializable): """Directory.""" - _REGISTERED_TYPES: Dict[str, Type['Directory']] = {} + _REGISTERED_TYPES: Dict[str, Type[HasResourceType]] = {} class Meta(jose.JSONObjectWithFields): """Directory Meta.""" - _terms_of_service = jose.Field('terms-of-service', omitempty=True) - _terms_of_service_v2 = jose.Field('termsOfService', omitempty=True) - website = jose.Field('website', omitempty=True) - caa_identities = jose.Field('caaIdentities', omitempty=True) - external_account_required = jose.Field('externalAccountRequired', omitempty=True) + _terms_of_service: str = jose.field('terms-of-service', omitempty=True) + _terms_of_service_v2: str = jose.field('termsOfService', omitempty=True) + website: str = jose.field('website', omitempty=True) + caa_identities: List[str] = jose.field('caaIdentities', omitempty=True) + external_account_required: bool = jose.field('externalAccountRequired', omitempty=True) def __init__(self, **kwargs: Any) -> None: kwargs = {self._internal_name(k): v for k, v in kwargs.items()} @@ -229,11 +248,14 @@ class Directory(jose.JSONDeSerializable): return '_' + name if name == 'terms_of_service' else name @classmethod - def _canon_key(cls, key: str) -> str: - return getattr(key, 'resource_type', key) + def _canon_key(cls, key: Union[str, HasResourceType, Type[HasResourceType]]) -> str: + if isinstance(key, str): + return key + return key.resource_type @classmethod - def register(cls, resource_body_cls: Type['Directory']) -> Type['Directory']: + def register(cls, + resource_body_cls: Type[GenericHasResourceType]) -> Type[GenericHasResourceType]: """Register resource.""" resource_type = resource_body_cls.resource_type assert resource_type not in cls._REGISTERED_TYPES @@ -252,7 +274,7 @@ class Directory(jose.JSONDeSerializable): except KeyError as error: raise AttributeError(str(error)) - def __getitem__(self, name: str) -> Any: + def __getitem__(self, name: Union[str, HasResourceType, Type[HasResourceType]]) -> Any: try: return self._jobj[self._canon_key(name)] except KeyError: @@ -273,16 +295,16 @@ class Resource(jose.JSONObjectWithFields): :ivar acme.messages.ResourceBody body: Resource body. """ - body = jose.Field('body') + body: "ResourceBody" = jose.field('body') class ResourceWithURI(Resource): """ACME Resource with URI. - :ivar unicode ~.uri: Location of the resource. + :ivar str uri: Location of the resource. """ - uri = jose.Field('uri') # no ChallengeResource.uri + uri: str = jose.field('uri') # no ChallengeResource.uri class ResourceBody(jose.JSONObjectWithFields): @@ -308,35 +330,40 @@ class ExternalAccountBinding: return eab.to_partial_json() +GenericRegistration = TypeVar('GenericRegistration', bound='Registration') + + class Registration(ResourceBody): """Registration Resource Body. - :ivar josepy.jwk.JWK key: Public key. + :ivar jose.JWK key: Public key. :ivar tuple contact: Contact information following ACME spec, - `tuple` of `unicode`. - :ivar unicode agreement: + `tuple` of `str`. + :ivar str agreement: """ # on new-reg key server ignores 'key' and populates it based on # JWS.signature.combined.jwk - key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json) + key: jose.JWK = jose.field('key', omitempty=True, decoder=jose.JWK.from_json) # Contact field implements special behavior to allow messages that clear existing # contacts while not expecting the `contact` field when loading from json. # This is implemented in the constructor and *_json methods. - contact = jose.Field('contact', omitempty=True, default=()) - agreement = jose.Field('agreement', omitempty=True) - status = jose.Field('status', omitempty=True) - terms_of_service_agreed = jose.Field('termsOfServiceAgreed', omitempty=True) - only_return_existing = jose.Field('onlyReturnExisting', omitempty=True) - external_account_binding = jose.Field('externalAccountBinding', omitempty=True) + contact: Tuple[str, ...] = jose.field('contact', omitempty=True, default=()) + agreement: str = jose.field('agreement', omitempty=True) + status: Status = jose.field('status', omitempty=True) + terms_of_service_agreed: bool = jose.field('termsOfServiceAgreed', omitempty=True) + only_return_existing: bool = jose.field('onlyReturnExisting', omitempty=True) + external_account_binding: Dict[str, Any] = jose.field('externalAccountBinding', + omitempty=True) phone_prefix = 'tel:' email_prefix = 'mailto:' @classmethod - def from_data(cls, phone: Optional[str] = None, email: Optional[str] = None, + def from_data(cls: Type[GenericRegistration], phone: Optional[str] = None, + email: Optional[str] = None, external_account_binding: Optional[Dict[str, Any]] = None, - **kwargs: Any) -> 'Registration': + **kwargs: Any) -> GenericRegistration: """ Create registration resource from contact details. @@ -419,26 +446,26 @@ class Registration(ResourceBody): class NewRegistration(ResourceMixin, Registration): """New registration.""" resource_type = 'new-reg' - resource = fields.Resource(resource_type) + resource: str = fields.resource(resource_type) class UpdateRegistration(ResourceMixin, Registration): """Update registration.""" resource_type = 'reg' - resource = fields.Resource(resource_type) + resource: str = fields.resource(resource_type) class RegistrationResource(ResourceWithURI): """Registration Resource. :ivar acme.messages.Registration body: - :ivar unicode new_authzr_uri: Deprecated. Do not use. - :ivar unicode terms_of_service: URL for the CA TOS. + :ivar str new_authzr_uri: Deprecated. Do not use. + :ivar str terms_of_service: URL for the CA TOS. """ - body = jose.Field('body', decoder=Registration.from_json) - new_authzr_uri = jose.Field('new_authzr_uri', omitempty=True) - terms_of_service = jose.Field('terms_of_service', omitempty=True) + body: Registration = jose.field('body', decoder=Registration.from_json) + new_authzr_uri: str = jose.field('new_authzr_uri', omitempty=True) + terms_of_service: str = jose.field('terms_of_service', omitempty=True) class ChallengeBody(ResourceBody): @@ -463,12 +490,12 @@ class ChallengeBody(ResourceBody): # challenge object supports either one, but should be accessed through the # name "uri". In Client.answer_challenge, whichever one is set will be # used. - _uri = jose.Field('uri', omitempty=True, default=None) - _url = jose.Field('url', omitempty=True, default=None) - status = jose.Field('status', decoder=Status.from_json, + _uri: str = jose.field('uri', omitempty=True, default=None) + _url: str = jose.field('url', omitempty=True, default=None) + status: Status = jose.field('status', decoder=Status.from_json, omitempty=True, default=STATUS_PENDING) - validated = fields.RFC3339Field('validated', omitempty=True) - error = jose.Field('error', decoder=Error.from_json, + validated: datetime.datetime = fields.rfc3339('validated', omitempty=True) + error: Error = jose.field('error', decoder=Error.from_json, omitempty=True, default=None) def __init__(self, **kwargs: Any) -> None: @@ -511,16 +538,16 @@ class ChallengeResource(Resource): """Challenge Resource. :ivar acme.messages.ChallengeBody body: - :ivar unicode authzr_uri: URI found in the 'up' ``Link`` header. + :ivar str authzr_uri: URI found in the 'up' ``Link`` header. """ - body = jose.Field('body', decoder=ChallengeBody.from_json) - authzr_uri = jose.Field('authzr_uri') + body: ChallengeBody = jose.field('body', decoder=ChallengeBody.from_json) + authzr_uri: str = jose.field('authzr_uri') @property def uri(self) -> str: """The URL of the challenge body.""" - return self.body.uri + return self.body.uri # pylint: disable=no-member class Authorization(ResourceBody): @@ -534,26 +561,26 @@ class Authorization(ResourceBody): :ivar datetime.datetime expires: """ - identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True) - challenges = jose.Field('challenges', omitempty=True) - combinations = jose.Field('combinations', omitempty=True) + identifier: Identifier = jose.field('identifier', decoder=Identifier.from_json, omitempty=True) + challenges: List[ChallengeBody] = jose.field('challenges', omitempty=True) + combinations: Tuple[Tuple[int, ...], ...] = jose.field('combinations', omitempty=True) - status = jose.Field('status', omitempty=True, decoder=Status.from_json) + status: Status = jose.field('status', omitempty=True, decoder=Status.from_json) # TODO: 'expires' is allowed for Authorization Resources in # general, but for Key Authorization '[t]he "expires" field MUST # be absent'... then acme-spec gives example with 'expires' # present... That's confusing! - expires = fields.RFC3339Field('expires', omitempty=True) - wildcard = jose.Field('wildcard', omitempty=True) + expires: datetime.datetime = fields.rfc3339('expires', omitempty=True) + wildcard: bool = jose.field('wildcard', omitempty=True) # Mypy does not understand the josepy magic happening here, and falsely claims # that challenge is redefined. Let's ignore the type check here. @challenges.decoder # type: ignore - def challenges(value: List[Mapping[str, Any]]) -> Tuple[ChallengeBody, ...]: # pylint: disable=no-self-argument,missing-function-docstring + def challenges(value: List[Dict[str, Any]]) -> Tuple[ChallengeBody, ...]: # type: ignore[misc] # pylint: disable=no-self-argument,missing-function-docstring return tuple(ChallengeBody.from_json(chall) for chall in value) @property - def resolved_combinations(self) -> Tuple[Tuple[Dict[str, Any], ...], ...]: + def resolved_combinations(self) -> Tuple[Tuple[ChallengeBody, ...], ...]: """Combinations with challenges instead of indices.""" return tuple(tuple(self.challenges[idx] for idx in combo) for combo in self.combinations) # pylint: disable=not-an-iterable @@ -563,37 +590,37 @@ class Authorization(ResourceBody): class NewAuthorization(ResourceMixin, Authorization): """New authorization.""" resource_type = 'new-authz' - resource = fields.Resource(resource_type) + resource: str = fields.resource(resource_type) class UpdateAuthorization(ResourceMixin, Authorization): """Update authorization.""" resource_type = 'authz' - resource = fields.Resource(resource_type) + resource: str = fields.resource(resource_type) class AuthorizationResource(ResourceWithURI): """Authorization Resource. :ivar acme.messages.Authorization body: - :ivar unicode new_cert_uri: Deprecated. Do not use. + :ivar str new_cert_uri: Deprecated. Do not use. """ - body = jose.Field('body', decoder=Authorization.from_json) - new_cert_uri = jose.Field('new_cert_uri', omitempty=True) + body: Authorization = jose.field('body', decoder=Authorization.from_json) + new_cert_uri: str = jose.field('new_cert_uri', omitempty=True) @Directory.register class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields): """ACME new-cert request. - :ivar josepy.util.ComparableX509 csr: + :ivar jose.ComparableX509 csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509` """ resource_type = 'new-cert' - resource = fields.Resource(resource_type) - csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr) + resource: str = fields.resource(resource_type) + csr: jose.ComparableX509 = jose.field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr) class CertificateResource(ResourceWithURI): @@ -601,27 +628,27 @@ class CertificateResource(ResourceWithURI): :ivar josepy.util.ComparableX509 body: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` - :ivar unicode cert_chain_uri: URI found in the 'up' ``Link`` header + :ivar str cert_chain_uri: URI found in the 'up' ``Link`` header :ivar tuple authzrs: `tuple` of `AuthorizationResource`. """ - cert_chain_uri = jose.Field('cert_chain_uri') - authzrs = jose.Field('authzrs') + cert_chain_uri: str = jose.field('cert_chain_uri') + authzrs: Tuple[AuthorizationResource, ...] = jose.field('authzrs') @Directory.register class Revocation(ResourceMixin, jose.JSONObjectWithFields): """Revocation message. - :ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in - `.ComparableX509` + :ivar jose.ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in + `jose.ComparableX509` """ resource_type = 'revoke-cert' - resource = fields.Resource(resource_type) - certificate = jose.Field( + resource: str = fields.resource(resource_type) + certificate: jose.ComparableX509 = jose.field( 'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert) - reason = jose.Field('reason') + reason: int = jose.field('reason') class Order(ResourceBody): @@ -638,19 +665,18 @@ class Order(ResourceBody): :ivar datetime.datetime expires: When the order expires. :ivar ~.Error error: Any error that occurred during finalization, if applicable. """ - identifiers = jose.Field('identifiers', omitempty=True) - status = jose.Field('status', decoder=Status.from_json, - omitempty=True) - authorizations = jose.Field('authorizations', omitempty=True) - certificate = jose.Field('certificate', omitempty=True) - finalize = jose.Field('finalize', omitempty=True) - expires = fields.RFC3339Field('expires', omitempty=True) - error = jose.Field('error', omitempty=True, decoder=Error.from_json) + identifiers: List[Identifier] = jose.field('identifiers', omitempty=True) + status: Status = jose.field('status', decoder=Status.from_json, omitempty=True) + authorizations: List[str] = jose.field('authorizations', omitempty=True) + certificate: str = jose.field('certificate', omitempty=True) + finalize: str = jose.field('finalize', omitempty=True) + expires: datetime.datetime = fields.rfc3339('expires', omitempty=True) + error: Error = jose.field('error', omitempty=True, decoder=Error.from_json) # Mypy does not understand the josepy magic happening here, and falsely claims # that identifiers is redefined. Let's ignore the type check here. @identifiers.decoder # type: ignore - def identifiers(value: List[Mapping[str, Any]]) -> Tuple[Identifier, ...]: # pylint: disable=no-self-argument,missing-function-docstring + def identifiers(value: List[Dict[str, Any]]) -> Tuple[Identifier, ...]: # type: ignore[misc] # pylint: disable=no-self-argument,missing-function-docstring return tuple(Identifier.from_json(identifier) for identifier in value) @@ -658,7 +684,7 @@ class OrderResource(ResourceWithURI): """Order Resource. :ivar acme.messages.Order body: - :ivar str csr_pem: The CSR this Order will be finalized with. + :ivar bytes csr_pem: The CSR this Order will be finalized with. :ivar authorizations: Fully-fetched AuthorizationResource objects. :vartype authorizations: `list` of `acme.messages.AuthorizationResource` :ivar str fullchain_pem: The fetched contents of the certificate URL @@ -668,11 +694,13 @@ class OrderResource(ResourceWithURI): finalization. :vartype alternative_fullchains_pem: `list` of `str` """ - body = jose.Field('body', decoder=Order.from_json) - csr_pem = jose.Field('csr_pem', omitempty=True) - authorizations = jose.Field('authorizations') - fullchain_pem = jose.Field('fullchain_pem', omitempty=True) - alternative_fullchains_pem = jose.Field('alternative_fullchains_pem', omitempty=True) + body: Order = jose.field('body', decoder=Order.from_json) + csr_pem: bytes = jose.field('csr_pem', omitempty=True) + authorizations: List[AuthorizationResource] = jose.field('authorizations') + fullchain_pem: str = jose.field('fullchain_pem', omitempty=True) + alternative_fullchains_pem: List[str] = jose.field('alternative_fullchains_pem', + omitempty=True) + @Directory.register class NewOrder(Order): diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 5dfb27136..f2df276a4 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -8,6 +8,7 @@ import socket import socketserver import threading from typing import Any +from typing import cast from typing import List from typing import Mapping from typing import Optional @@ -39,10 +40,10 @@ class TLSServer(socketserver.TCPServer): super().__init__(*args, **kwargs) def _wrap_sock(self) -> None: - self.socket = crypto_util.SSLSocket( + self.socket = cast(socket.socket, crypto_util.SSLSocket( self.socket, cert_selection=self._cert_selection, alpn_selection=getattr(self, '_alpn_selection', None), - method=self.method) + method=self.method)) def _cert_selection(self, connection: SSL.Connection ) -> Tuple[crypto.PKey, crypto.X509]: # pragma: no cover diff --git a/acme/setup.py b/acme/setup.py index e5d822202..fb97eb6ad 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -7,7 +7,7 @@ version = '1.23.0.dev0' install_requires = [ 'cryptography>=2.5.0', - 'josepy>=1.9.0', + 'josepy>=1.10.0', 'PyOpenSSL>=17.3.0', 'pyrfc3339', 'pytz', @@ -24,6 +24,7 @@ docs_extras = [ test_extras = [ 'pytest', 'pytest-xdist', + 'typing-extensions', ] setup( diff --git a/acme/tests/fields_test.py b/acme/tests/fields_test.py index 69dde8b89..4cc167f9c 100644 --- a/acme/tests/fields_test.py +++ b/acme/tests/fields_test.py @@ -10,8 +10,8 @@ class FixedTest(unittest.TestCase): """Tests for acme.fields.Fixed.""" def setUp(self): - from acme.fields import Fixed - self.field = Fixed('name', 'x') + from acme.fields import fixed + self.field = fixed('name', 'x') def test_decode(self): self.assertEqual('x', self.field.decode('x')) diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 73b6a1e1b..9b814f60d 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -22,11 +22,9 @@ from typing import Type from typing import Union from acme import challenges -from acme.challenges import Challenge from certbot import achallenges from certbot import errors from certbot import util -from certbot.achallenges import KeyAuthorizationAnnotatedChallenge from certbot.compat import filesystem from certbot.compat import os from certbot.display import util as display_util @@ -240,7 +238,7 @@ class ApacheConfigurator(common.Configurator): # Add name_server association dict self.assoc: Dict[str, obj.VirtualHost] = {} # Outstanding challenges - self._chall_out: Set[KeyAuthorizationAnnotatedChallenge] = set() + self._chall_out: Set[achallenges.AnnotatedChallenge] = set() # List of vhosts configured per wildcard domain on this run. # used by deploy_cert() and enhance() self._wildcard_vhosts: Dict[str, List[obj.VirtualHost]] = {} @@ -2532,9 +2530,8 @@ class ApacheConfigurator(common.Configurator): """Return list of challenge preferences.""" return [challenges.HTTP01] - def perform( - self, achalls: List[KeyAuthorizationAnnotatedChallenge] - ) -> List[Challenge]: + def perform(self, achalls: List[achallenges.AnnotatedChallenge] + ) -> List[challenges.ChallengeResponse]: """Perform the configuration related challenge. This function currently assumes all challenges will be fulfilled. @@ -2543,10 +2540,13 @@ class ApacheConfigurator(common.Configurator): """ self._chall_out.update(achalls) - responses: List[Optional[Challenge]] = [None] * len(achalls) + responses: List[Optional[challenges.ChallengeResponse]] = [None] * len(achalls) http_doer = http_01.ApacheHttp01(self) for i, achall in enumerate(achalls): + if not isinstance(achall, achallenges.KeyAuthorizationAnnotatedChallenge): + raise errors.Error("Challenge should be an instance " # pragma: no cover + "of KeyAuthorizationAnnotatedChallenge") # Currently also have chall_doer hold associated index of the # challenge. This helps to put all of the responses back together # when they are all complete. @@ -2560,18 +2560,17 @@ class ApacheConfigurator(common.Configurator): self.restart() # TODO: Remove this dirty hack. We need to determine a reliable way - # of identifying when the new configuration is being used. + # of identifying when the new configuration is being used. time.sleep(3) self._update_responses(responses, http_response, http_doer) - # We assume all challenges has been fulfilled as described in the function documentation. - return cast(List[Challenge], responses) + return [response for response in responses if response] def _update_responses( self, - responses: List[Optional[challenges.HTTP01Response]], - chall_response: List[Challenge], + responses: List[Optional[challenges.ChallengeResponse]], + chall_response: List[challenges.KeyAuthorizationChallengeResponse], chall_doer: http_01.ApacheHttp01 ) -> None: # Go through all of the challenges and assign them to the proper @@ -2580,7 +2579,7 @@ class ApacheConfigurator(common.Configurator): for i, resp in enumerate(chall_response): responses[chall_doer.indices[i]] = resp - def cleanup(self, achalls: List[KeyAuthorizationAnnotatedChallenge]) -> None: + def cleanup(self, achalls: List[achallenges.AnnotatedChallenge]) -> None: """Revert all challenges.""" self._chall_out.difference_update(achalls) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index 2fe981720..eed849dbe 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,12 +1,11 @@ """A class that performs HTTP-01 challenges for Apache""" import errno import logging -from typing import Any from typing import List from typing import Set from typing import TYPE_CHECKING -from acme.challenges import HTTP01Response +from acme.challenges import KeyAuthorizationChallengeResponse from certbot import errors from certbot.achallenges import KeyAuthorizationAnnotatedChallenge from certbot.compat import filesystem @@ -67,7 +66,7 @@ class ApacheHttp01(common.ChallengePerformer): "http_challenges") self.moded_vhosts: Set[VirtualHost] = set() - def perform(self) -> List[KeyAuthorizationAnnotatedChallenge]: + def perform(self) -> List[KeyAuthorizationChallengeResponse]: """Perform all HTTP-01 challenges.""" if not self.achalls: return [] @@ -182,7 +181,7 @@ class ApacheHttp01(common.ChallengePerformer): """Return all VirtualHost objects with no ServerName""" return [vh for vh in self.configurator.vhosts if vh.name is None] - def _set_up_challenges(self) -> List[HTTP01Response]: + def _set_up_challenges(self) -> List[KeyAuthorizationChallengeResponse]: if not os.path.isdir(self.challenge_dir): old_umask = filesystem.umask(0o022) try: @@ -200,9 +199,8 @@ class ApacheHttp01(common.ChallengePerformer): return responses - def _set_up_challenge(self, achall: KeyAuthorizationAnnotatedChallenge) -> HTTP01Response: - response: HTTP01Response - validation: Any + def _set_up_challenge(self, achall: KeyAuthorizationAnnotatedChallenge + ) -> KeyAuthorizationChallengeResponse: response, validation = achall.response_and_validation() name: str = os.path.join(self.challenge_dir, achall.chall.encode("token")) diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 9143804f9..a36b348e0 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -48,7 +48,8 @@ def _suppress_x509_verification_warnings() -> None: # Handle old versions of request with vendorized urllib3 # pylint: disable=no-member from requests.packages.urllib3.exceptions import InsecureRequestWarning - requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + requests.packages.urllib3.disable_warnings( # type: ignore[attr-defined] + InsecureRequestWarning) def check_until_timeout(url: str, attempts: int = 30) -> None: diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 1b27a2195..26b7660ab 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -106,7 +106,7 @@ def test_authenticator(plugin: common.Proxy, config: str, temp_dir: str) -> bool def _create_achalls(plugin: common.Proxy) -> List[achallenges.AnnotatedChallenge]: """Returns a list of annotated challenges to test on plugin""" - achalls = [] + achalls: List[achallenges.AnnotatedChallenge] = [] names = plugin.get_testable_domain_names() for domain in names: prefs = plugin.get_chall_pref(domain) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index d3fdd5e8c..8cf6d9966 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -138,7 +138,7 @@ class _RFC2136Client: except Exception as e: raise errors.PluginError('Encountered error adding TXT record: {0}' .format(e)) - rcode = response.rcode() + rcode = response.rcode() # type: ignore[attr-defined] if rcode == dns.rcode.NOERROR: logger.debug('Successfully added TXT record %s', record_name) @@ -173,7 +173,7 @@ class _RFC2136Client: except Exception as e: raise errors.PluginError('Encountered error deleting TXT record: {0}' .format(e)) - rcode = response.rcode() + rcode = response.rcode() # type: ignore[attr-defined] if rcode == dns.rcode.NOERROR: logger.debug('Successfully deleted TXT record %s', record_name) @@ -223,11 +223,13 @@ class _RFC2136Client: except (OSError, dns.exception.Timeout) as e: logger.debug('TCP query failed, fallback to UDP: %s', e) response = dns.query.udp(request, self.server, self._default_timeout, self.port) - rcode = response.rcode() + rcode = response.rcode() # type: ignore[attr-defined] # Authoritative Answer bit should be set - if (rcode == dns.rcode.NOERROR and response.get_rrset(response.answer, - domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA): + if (rcode == dns.rcode.NOERROR + and response.get_rrset(response.answer, # type: ignore[attr-defined] + domain, dns.rdataclass.IN, dns.rdatatype.SOA) + and response.flags & dns.flags.AA): logger.debug('Received authoritative SOA response for %s', domain_name) return True diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index fb819f194..39784110c 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1180,7 +1180,7 @@ class NginxConfigurator(common.Configurator): # Entry point in main.py for performing challenges def perform(self, achalls: List[achallenges.AnnotatedChallenge] - ) -> List[challenges.HTTP01Response]: + ) -> List[challenges.ChallengeResponse]: """Perform the configuration related challenge. This function currently assumes all challenges will be fulfilled. @@ -1189,13 +1189,16 @@ class NginxConfigurator(common.Configurator): """ self._chall_out += len(achalls) - responses: List[Optional[challenges.HTTP01Response]] = [None] * len(achalls) + responses: List[Optional[challenges.ChallengeResponse]] = [None] * len(achalls) http_doer = http_01.NginxHttp01(self) for i, achall in enumerate(achalls): # Currently also have chall_doer hold associated index of the # challenge. This helps to put all of the responses back together # when they are all complete. + if not isinstance(achall, achallenges.KeyAuthorizationAnnotatedChallenge): + raise errors.Error("Challenge should be an instance " + "of KeyAuthorizationAnnotatedChallenge") http_doer.add_chall(achall, i) http_response = http_doer.perform() diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index 6f61bfb6f..f9988007e 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -11,7 +11,7 @@ from certbot_nginx._internal import nginxparser from certbot_nginx._internal.obj import Addr from acme import challenges -from acme.challenges import HTTP01Response +from acme.challenges import KeyAuthorizationChallengeResponse from certbot import errors from certbot.achallenges import KeyAuthorizationAnnotatedChallenge from certbot.compat import os @@ -49,10 +49,10 @@ class NginxHttp01(common.ChallengePerformer): self.challenge_conf = os.path.join( configurator.config.config_dir, "le_http_01_cert_challenge.conf") - def perform(self) -> List[HTTP01Response]: + def perform(self) -> List[KeyAuthorizationChallengeResponse]: """Perform a challenge on Nginx. - :returns: list of :class:`certbot.acme.challenges.HTTP01Response` + :returns: list of :class:`acme.challenges.KeyAuthorizationChallengeResponse` :rtype: list """ diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index 70a55be3a..0fdc71dfa 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -2,6 +2,7 @@ # Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed) import copy import logging +import operator import typing from typing import Any from typing import IO @@ -167,13 +168,14 @@ class UnspacedList(List[Any]): inbound = UnspacedList(inbound) return inbound, inbound.spaced - def insert(self, i: int, x: Any) -> None: + def insert(self, i: "SupportsIndex", x: Any) -> None: """Insert object before index.""" + idx = operator.index(i) item, spaced_item = self._coerce(x) - slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced) + slicepos = self._spaced_position(idx) if idx < len(self) else len(self.spaced) self.spaced.insert(slicepos, spaced_item) if not spacey(item): - super().insert(i, item) + super().insert(idx, item) self.dirty = True def append(self, x: Any) -> None: @@ -246,7 +248,7 @@ class UnspacedList(List[Any]): def _spaced_position(self, idx: "SupportsIndex") -> int: """Convert from indexes in the unspaced list to positions in the spaced one""" - int_idx = idx.__index__() + int_idx = operator.index(idx) pos = spaces = 0 # Normalize indexes like list[-1] etc, and save the result if int_idx < 0: diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index 041fe9cf5..8c45efe21 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -54,9 +54,9 @@ class Account: cross-machine migration scenarios. """ - creation_dt = acme_fields.RFC3339Field("creation_dt") - creation_host = jose.Field("creation_host") - register_to_eff = jose.Field("register_to_eff", omitempty=True) + creation_dt: datetime.datetime = acme_fields.rfc3339("creation_dt") + creation_host: str = jose.field("creation_host") + register_to_eff: str = jose.field("register_to_eff", omitempty=True) def __init__(self, regr: messages.RegistrationResource, key: jose.JWK, meta: Optional['Meta'] = None) -> None: @@ -135,7 +135,7 @@ class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): continue to write out this field for some time so older clients don't crash in that scenario. """ - new_authzr_uri = jose.Field('new_authzr_uri') + new_authzr_uri: str = jose.field('new_authzr_uri') class AccountFileStorage(interfaces.AccountStorage): diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py index 0fa1daf8e..08dc8be35 100644 --- a/certbot/certbot/_internal/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -6,6 +6,7 @@ from typing import Dict from typing import Iterable from typing import List from typing import Optional +from typing import Sequence from typing import Tuple from typing import Type @@ -272,7 +273,7 @@ class AuthHandler: self.auth.cleanup(achalls) def _challenge_factory(self, authzr: messages.AuthorizationResource, - path: List[int]) -> List[achallenges.AnnotatedChallenge]: + path: Sequence[int]) -> List[achallenges.AnnotatedChallenge]: """Construct Namedtuple Challenges :param messages.AuthorizationResource authzr: authorization @@ -350,7 +351,7 @@ def challb_to_achall(challb: messages.ChallengeBody, account_key: josepy.JWK, def gen_challenge_path(challbs: List[messages.ChallengeBody], preferences: List[Type[challenges.Challenge]], - combinations: Tuple[List[int], ...]) -> List[int]: + combinations: Tuple[Tuple[int, ...], ...]) -> Tuple[int, ...]: """Generate a plan to get authority over the identity. .. todo:: This can be possibly be rewritten to use resolved_combinations. @@ -383,8 +384,8 @@ def gen_challenge_path(challbs: List[messages.ChallengeBody], def _find_smart_path(challbs: List[messages.ChallengeBody], preferences: List[Type[challenges.Challenge]], - combinations: Tuple[List[int], ...] - ) -> List[int]: + combinations: Tuple[Tuple[int, ...], ...] + ) -> Tuple[int, ...]: """Find challenge path with server hints. Can be called if combinations is included. Function uses a simple @@ -399,7 +400,7 @@ def _find_smart_path(challbs: List[messages.ChallengeBody], # max_cost is now equal to sum(indices) + 1 - best_combo: Optional[List[int]] = None + best_combo: Optional[Tuple[int, ...]] = None # Set above completing all of the available challenges best_combo_cost = max_cost @@ -422,7 +423,7 @@ def _find_smart_path(challbs: List[messages.ChallengeBody], def _find_dumb_path(challbs: List[messages.ChallengeBody], - preferences: List[Type[challenges.Challenge]]) -> List[int]: + preferences: List[Type[challenges.Challenge]]) -> Tuple[int, ...]: """Find challenge path without server hints. Should be called if the combinations hint is not included by the @@ -440,7 +441,7 @@ def _find_dumb_path(challbs: List[messages.ChallengeBody], else: raise _report_no_chall_path(challbs) - return path + return tuple(path) def _report_no_chall_path(challbs: List[messages.ChallengeBody]) -> errors.AuthorizationError: diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index fec016ec7..ad5d99b61 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -251,7 +251,7 @@ def perform_registration(acme: acme_client.ClientV2, config: configuration.Names raise errors.Error("The ACME client must be an instance of " "acme.client.BackwardsCompatibleClientV2") except messages.Error as e: - if e.code in ('invalidEmail', 'invalidContact'): + if e.code in ("invalidEmail", "invalidContact"): if config.noninteractive_mode: msg = ("The ACME server believes %s is an invalid email address. " "Please ensure it is a valid email and attempt " diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index dc45ae271..6c69b7e5b 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -98,7 +98,7 @@ permitted by DNS standards.) super().__init__(*args, **kwargs) self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() - self.env: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] = {} + self.env: Dict[achallenges.AnnotatedChallenge, Dict[str, str]] = {} self.subsequent_dns_challenge = False self.subsequent_any_challenge = False diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index f70d2ed9e..826acb2b1 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: ServedType = DefaultDict[ acme_standalone.BaseDualNetworkedServers, - Set[achallenges.KeyAuthorizationAnnotatedChallenge] + Set[achallenges.AnnotatedChallenge] ] diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index bb61d8220..4a84197b0 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -20,7 +20,7 @@ from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot._internal import cli -from certbot.achallenges import KeyAuthorizationAnnotatedChallenge as AnnotatedChallenge +from certbot.achallenges import AnnotatedChallenge from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops @@ -85,7 +85,7 @@ to serve all files under specified web root ({0}).""" "file, it needs to be on a single line, like: webroot-map = " '{"example.com":"/var/www"}.') - def auth_hint(self, failed_achalls: Iterable[AnnotatedChallenge]) -> str: # pragma: no cover + def auth_hint(self, failed_achalls: List[AnnotatedChallenge]) -> str: # pragma: no cover return ("The Certificate Authority failed to download the temporary challenge files " "created by Certbot. Ensure that the listed domains serve their content from " "the provided --webroot-path/-w and that files created there can be downloaded " @@ -105,7 +105,7 @@ to serve all files under specified web root ({0}).""" def prepare(self) -> None: # pylint: disable=missing-function-docstring pass - def perform(self, achalls: Iterable[AnnotatedChallenge]) -> List[challenges.ChallengeResponse]: # pylint: disable=missing-function-docstring + def perform(self, achalls: List[AnnotatedChallenge]) -> List[challenges.ChallengeResponse]: # pylint: disable=missing-function-docstring self._set_webroots(achalls) self._create_challenge_dirs() @@ -257,7 +257,7 @@ to serve all files under specified web root ({0}).""" self.performed[root_path].add(achall) return response - def cleanup(self, achalls: Iterable[AnnotatedChallenge]) -> None: # pylint: disable=missing-function-docstring + def cleanup(self, achalls: List[AnnotatedChallenge]) -> None: # pylint: disable=missing-function-docstring for achall in achalls: root_path = self.full_roots.get(achall.domain, None) if root_path is not None: diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py index 333fcca48..fb09b1f27 100644 --- a/certbot/certbot/_internal/reporter.py +++ b/certbot/certbot/_internal/reporter.py @@ -29,7 +29,7 @@ class Reporter: _msg_type = collections.namedtuple('_msg_type', 'priority text on_crash') def __init__(self, config: configuration.NamespaceConfig) -> None: - self.messages: queue.PriorityQueue[Reporter._msg_type] = queue.PriorityQueue() + self.messages: "queue.PriorityQueue[Reporter._msg_type]" = queue.PriorityQueue() self.config = config def add_message(self, msg: str, priority: int, on_crash: bool = True) -> None: diff --git a/certbot/certbot/_internal/snap_config.py b/certbot/certbot/_internal/snap_config.py index 3aad79912..f006c8be1 100644 --- a/certbot/certbot/_internal/snap_config.py +++ b/certbot/certbot/_internal/snap_config.py @@ -18,8 +18,8 @@ try: from urllib3.connectionpool import HTTPConnectionPool except ImportError: # Stub imports for oldest requirements, that will never be used in snaps. - HTTPConnection = object - HTTPConnectionPool = object + HTTPConnection = object # type: ignore[misc,assignment] + HTTPConnectionPool = object # type: ignore[misc,assignment] _ARCH_TRIPLET_MAP = { diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index e3a62cc9e..ef86c5e46 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -290,8 +290,11 @@ def make_key(bits: int = 1024, key_type: str = "rsa", try: name = elliptic_curve.upper() if name in ('SECP256R1', 'SECP384R1', 'SECP521R1'): + curve = getattr(ec, elliptic_curve.upper()) + if not curve: + raise errors.Error(f"Invalid curve type: {elliptic_curve}") _key = ec.generate_private_key( - curve=getattr(ec, elliptic_curve.upper(), None)(), + curve=curve(), backend=default_backend() ) else: diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 3af0ee6ec..fbecc7372 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -18,6 +18,8 @@ from typing import Tuple import pkg_resources +from acme import challenges + from certbot import achallenges from certbot import configuration from certbot import crypto_util @@ -377,7 +379,7 @@ class ChallengePerformer: if idx is not None: self.indices.append(idx) - def perform(self) -> List[achallenges.KeyAuthorizationAnnotatedChallenge]: + def perform(self) -> List[challenges.KeyAuthorizationChallengeResponse]: """Perform all added challenges. :returns: challenge responses diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py index df91dac1f..7d844d133 100644 --- a/certbot/certbot/plugins/dns_test_common_lexicon.py +++ b/certbot/certbot/plugins/dns_test_common_lexicon.py @@ -7,8 +7,8 @@ import josepy as jose from requests.exceptions import HTTPError from requests.exceptions import RequestException -from acme.challenges import Challenge from certbot import errors +from certbot.achallenges import AnnotatedChallenge from certbot.plugins import dns_test_common from certbot.plugins.dns_common_lexicon import LexiconClient from certbot.plugins.dns_test_common import _AuthenticatorCallableTestCase @@ -33,7 +33,7 @@ class _AuthenticatorCallableLexiconTestCase(_AuthenticatorCallableTestCase, Prot a mocked LexiconClient instance. """ mock_client: MagicMock - achall: Challenge + achall: AnnotatedChallenge class _LexiconAwareTestCase(Protocol): diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py index 0c164ecb4..d8ee7f9a8 100644 --- a/certbot/certbot/tests/acme_util.py +++ b/certbot/certbot/tests/acme_util.py @@ -1,5 +1,7 @@ """ACME utilities for testing.""" import datetime +from typing import Any +from typing import Dict from typing import Iterable from typing import Tuple @@ -74,7 +76,7 @@ def gen_authzr(authz_status: messages.Status, domain: str, challs: Iterable[chal chall_to_challb(chall, status) for chall, status in zip(challs, statuses) ) - authz_kwargs = { + authz_kwargs: Dict[str, Any] = { "identifier": messages.Identifier( typ=messages.IDENTIFIER_FQDN, value=domain), "challenges": challbs, diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index 819cbfda6..a3ffc16ab 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -2,10 +2,9 @@ # that script. apacheconfig==0.3.2 asn1crypto==0.24.0 -astroid==2.8.6; python_version >= "3.6" and python_version < "4.0" +astroid==2.9.0; python_version >= "3.6" and python_version < "4.0" atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" -attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -backports.entry-points-selectable==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" bcrypt==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" boto3==1.4.7 botocore==1.7.41 @@ -16,10 +15,10 @@ cloudflare==1.5.1 colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_full_version >= "3.5.0" and python_version >= "3.6" and sys_platform == "win32" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" and python_full_version >= "3.5.0" configargparse==0.10.0 configobj==5.0.6 -coverage==6.1.2; python_version >= "3.6" or python_version >= "3.6" +coverage==6.2; python_version >= "3.6" or python_version >= "3.6" cryptography==3.2.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -cython==0.29.24; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") -distlib==0.3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +cython==0.29.26; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") +distlib==0.3.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" distro==1.0.1 dns-lexicon==3.2.1 dnspython==1.15.0 @@ -30,30 +29,30 @@ dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or p docopt==0.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" docutils==0.18.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -filelock==3.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" +filelock==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" funcsigs==0.4 future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" google-api-python-client==1.5.5 httplib2==0.9.2 idna==2.6 -importlib-metadata==4.8.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.8" or python_version < "3.8" and python_version >= "3.6" +importlib-metadata==4.8.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.8" or python_version < "3.8" and python_version >= "3.6" importlib-resources==5.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.7" iniconfig==1.1.1; python_version >= "3.6" ipaddress==1.0.16 isort==5.8.0; python_version >= "3.6" and python_version < "4.0" jmespath==0.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -josepy==1.11.0; python_version >= "3.6" +josepy==1.12.0; python_version >= "3.6" jsonschema==2.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" +lazy-object-proxy==1.7.1; python_version >= "3.6" and python_version < "4.0" logger==1.4; python_version >= "3.6" mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0" mock==1.0.1 mypy-extensions==0.4.3; python_version >= "3.6" -mypy==0.910; python_version >= "3.6" +mypy==0.931; python_version >= "3.6" ndg-httpsclient==0.3.2 oauth2client==4.0.0 packaging==21.3; python_version >= "3.6" -paramiko==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +paramiko==2.9.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" parsedatetime==2.4 pbr==1.8.0 pip==21.3.1; python_version >= "3.6" @@ -64,21 +63,21 @@ py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_ pyasn1-modules==0.0.10; python_version >= "3.6" pyasn1==0.1.9 pycparser==2.14 -pylint==2.11.1; python_version >= "3.6" and python_version < "4.0" -pynacl==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +pylint==2.12.0; python_version >= "3.6" and python_version < "4.0" +pynacl==1.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pyopenssl==17.3.0 pyparsing==2.2.0 pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") pyrfc3339==1.0 pytest-cov==3.0.0; python_version >= "3.6" or python_version >= "3.6" -pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pytest-xdist==2.4.0; python_version >= "3.6" or python_version >= "3.6" -pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pytest-forked==1.4.0; python_version >= "3.6" +pytest-xdist==2.5.0; python_version >= "3.6" or python_version >= "3.6" +pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" python-augeas==0.5.0 python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" python-digitalocean==1.11 pytz==2012c -pywin32==302; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") +pywin32==303; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") pyyaml==3.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" requests-file==1.5.1; python_version >= "3.6" requests-toolbelt==0.9.1; python_version >= "3.6" @@ -90,24 +89,25 @@ six==1.11.0 texttable==0.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" tldextract==3.1.2; python_version >= "3.6" toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" -tomli==1.2.2; python_version >= "3.6" +tomli==1.2.3; python_version >= "3.6" tox==1.9.2; python_version >= "3.6" -typed-ast==1.4.3; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" -types-cryptography==3.3.9; python_version >= "3.6" -types-enum34==1.1.1; python_version >= "3.6" -types-ipaddress==1.0.1; python_version >= "3.6" -types-mock==4.0.3; python_version >= "3.6" -types-pyopenssl==21.0.1; python_version >= "3.6" -types-pyrfc3339==1.1.0; python_version >= "3.6" -types-python-dateutil==2.8.3; python_version >= "3.6" -types-pytz==2021.3.1; python_version >= "3.6" -types-requests==2.26.0; python_version >= "3.6" -types-setuptools==57.4.3; python_version >= "3.6" -types-six==1.16.2; python_version >= "3.6" -typing-extensions==4.0.0; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6" +typed-ast==1.5.1; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" +types-cryptography==3.3.14; python_version >= "3.6" +types-enum34==1.1.7; python_version >= "3.6" +types-ipaddress==1.0.7; python_version >= "3.6" +types-mock==4.0.8; python_version >= "3.6" +types-pyopenssl==21.0.3; python_version >= "3.6" +types-pyrfc3339==1.1.1; python_version >= "3.6" +types-python-dateutil==2.8.7; python_version >= "3.6" +types-pytz==2021.3.4; python_version >= "3.6" +types-requests==2.27.7; python_version >= "3.6" +types-setuptools==57.4.7; python_version >= "3.6" +types-six==1.16.10; python_version >= "3.6" +types-urllib3==1.26.7; python_version >= "3.6" +typing-extensions==4.0.1; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6" uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" urllib3==1.10.2 -virtualenv==20.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +virtualenv==20.13.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" wheel==0.33.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") wrapt==1.13.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" diff --git a/tools/pinning/current/pyproject.toml b/tools/pinning/current/pyproject.toml index c04180b06..ca04a0ada 100644 --- a/tools/pinning/current/pyproject.toml +++ b/tools/pinning/current/pyproject.toml @@ -51,10 +51,6 @@ awscli = ">=1.19.62" # as a dependency here to ensure a version of cython is pinned for extra # stability. cython = "*" -# Due to better type annotations in new versions, mypy tests currently fail -# with new versions of josepy so we pin it back for now. Fixing this is being -# tracked by https://github.com/certbot/certbot/issues/9113. -josepy = "1.9.0" # We install mock in our "external-mock" tox environment to test that we didn't # break Certbot's test API which used to always use mock objects from the 3rd # party mock library. We list the mock dependency here so that is pinned, but diff --git a/tools/requirements.txt b/tools/requirements.txt index 38bf7c0cc..c91bf2117 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -9,41 +9,41 @@ alabaster==0.7.12; python_version >= "3.6" apacheconfig==0.3.2; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" appnope==0.1.2; python_version == "3.6" and sys_platform == "darwin" or python_version >= "3.7" and sys_platform == "darwin" -astroid==2.8.6; python_version >= "3.6" and python_version < "4.0" +astroid==2.9.0; python_version >= "3.6" and python_version < "4.0" atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" -attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -awscli==1.22.11; python_version >= "3.6" +attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +awscli==1.22.39; python_version >= "3.6" azure-devops==6.0.0b4; python_version >= "3.6" babel==2.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" backcall==0.2.0; python_version == "3.6" or python_version >= "3.7" bcrypt==3.2.0; python_version >= "3.6" beautifulsoup4==4.10.0; python_full_version > "3.0.0" and python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" and python_full_version > "3.0.0" bleach==4.1.0; python_version >= "3.6" -boto3==1.20.11; python_version >= "3.6" -botocore==1.23.11; python_version >= "3.6" +boto3==1.20.39; python_version >= "3.6" +botocore==1.23.39; python_version >= "3.6" cachecontrol==0.12.10; python_version >= "3.6" and python_version < "4.0" cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" cachetools==4.2.4; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") cachy==0.3.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" certifi==2021.10.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" -cffi==1.15.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6" -charset-normalizer==2.0.7; python_full_version >= "3.6.0" and python_version >= "3.6" +cffi==1.15.0; python_version >= "3.6" or python_version >= "3.6" +charset-normalizer==2.0.10; python_full_version >= "3.6.0" and python_version >= "3.6" cleo==1.0.0a4; python_version >= "3.6" and python_version < "4.0" cloudflare==2.8.15; python_version >= "3.6" colorama==0.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_full_version >= "3.5.0" and python_version >= "3.6" and sys_platform == "win32" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and platform_system == "Windows" or python_version >= "3.6" and python_full_version >= "3.5.0" and platform_system == "Windows" or python_version == "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version == "3.6" and sys_platform == "win32" and python_full_version >= "3.5.0" or python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or python_version >= "3.7" and sys_platform == "win32" and python_full_version >= "3.5.0" configargparse==1.5.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" configobj==5.0.6; python_version >= "3.6" -coverage==6.1.2; python_version >= "3.6" or python_version >= "3.6" +coverage==6.2; python_version >= "3.6" or python_version >= "3.6" crashtest==0.3.1; python_version >= "3.6" and python_version < "4.0" -cryptography==36.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux" -cython==0.29.24; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") +cryptography==36.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux" +cython==0.29.26; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" -decorator==5.1.0; python_version == "3.6" or python_version > "3.6" or python_version >= "3.5" or python_version >= "3.7" +decorator==5.1.1; python_version == "3.6" or python_version > "3.6" or python_version >= "3.5" or python_version >= "3.7" deprecated==1.2.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" -distlib==0.3.3; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" +distlib==0.3.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" distro==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" -dns-lexicon==3.8.3; python_version >= "3.6" and python_version < "4.0" -dnspython==2.1.0; python_version >= "3.6" +dns-lexicon==3.8.5; python_version >= "3.6" and python_version < "4.0" +dnspython==2.2.0; python_version >= "3.6" and python_version < "4.0" docker-compose==1.26.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" docker==4.2.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" @@ -52,12 +52,12 @@ docutils==0.15.2; python_version >= "3.6" and python_full_version < "3.0.0" or p entrypoints==0.3; python_version >= "3.6" and python_version < "4.0" execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" fabric==2.6.0; python_version >= "3.6" -filelock==3.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0" -google-api-core==2.2.2; python_version >= "3.6" -google-api-python-client==2.31.0; python_version >= "3.6" +filelock==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0" +google-api-core==2.4.0; python_version >= "3.6" +google-api-python-client==2.36.0; python_version >= "3.6" google-auth-httplib2==0.1.0; python_version >= "3.6" google-auth==2.3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -googleapis-common-protos==1.53.0; python_version >= "3.6" +googleapis-common-protos==1.54.0; python_version >= "3.6" html5lib==1.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" httplib2==0.20.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" idna==3.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_version < "4.0" @@ -68,58 +68,60 @@ iniconfig==1.1.1; python_version >= "3.6" invoke==1.6.0; python_version >= "3.6" ipdb==0.13.9; python_version >= "3.6" ipython-genutils==0.2.0 -ipython==7.16.1; python_version == "3.6" -ipython==7.29.0; python_version >= "3.7" -isodate==0.6.0; python_version >= "3.6" +ipython==7.16.3; python_version == "3.6" +ipython==7.31.1; python_version >= "3.7" +isodate==0.6.1; python_version >= "3.6" isort==5.8.0; python_version >= "3.6" and python_version < "4.0" -jedi==0.18.1; python_version == "3.6" or python_version >= "3.7" +jedi==0.17.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.5.0" +jedi==0.18.1; python_version >= "3.7" jeepney==0.7.1; python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux" jinja2==3.0.3; python_version >= "3.6" or python_version >= "3.6" jmespath==0.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -josepy==1.9.0; python_version >= "3.6" -jsonlines==2.0.0; python_version >= "3.6" -jsonpickle==2.0.0; python_version >= "3.6" +josepy==1.12.0; python_version >= "3.6" +jsonlines==3.0.0; python_version >= "3.6" +jsonpickle==2.1.0; python_version >= "3.6" jsonschema==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" keyring==22.3.0; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" -lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" +lazy-object-proxy==1.7.1; python_version >= "3.6" and python_version < "4.0" lockfile==0.12.2 markupsafe==2.0.1; python_version >= "3.6" matplotlib-inline==0.1.3; python_version >= "3.7" mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0" mock==4.0.3; python_version >= "3.6" -msgpack==1.0.2; python_version >= "3.6" and python_version < "4.0" +msgpack==1.0.3; python_version >= "3.6" and python_version < "4.0" msrest==0.6.21; python_version >= "3.6" mypy-extensions==0.4.3; python_version >= "3.6" -mypy==0.910; python_version >= "3.6" +mypy==0.931; python_version >= "3.6" oauth2client==4.1.3; python_version >= "3.6" oauthlib==3.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version >= "3.5.0" -paramiko==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" +paramiko==2.9.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" parsedatetime==2.6; python_version >= "3.6" -parso==0.8.2; python_version == "3.6" +parso==0.7.1; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.5.0" +parso==0.8.3; python_version >= "3.7" pathlib2==2.3.6; python_version >= "3.6" pexpect==4.8.0; python_version >= "3.6" and python_version < "4.0" or python_version == "3.6" and sys_platform != "win32" or python_version >= "3.7" and sys_platform != "win32" pickleshare==0.7.5; python_version == "3.6" or python_version >= "3.7" pip==21.3.1; python_version >= "3.6" -pkginfo==1.8.1; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" +pkginfo==1.8.2; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" platformdirs==2.4.0; python_version >= "3.6" and python_version < "4.0" pluggy==1.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" ply==3.11; python_version >= "3.6" poetry-core==1.1.0a6; python_version >= "3.6" and python_version < "4.0" poetry==1.2.0a2; python_version >= "3.6" and python_version < "4.0" -prompt-toolkit==3.0.22; python_version == "3.6" and python_full_version >= "3.6.2" or python_version >= "3.7" and python_full_version >= "3.6.2" -protobuf==3.19.1; python_version >= "3.6" +prompt-toolkit==3.0.24; python_version == "3.6" and python_full_version >= "3.6.2" or python_version >= "3.7" and python_full_version >= "3.6.2" +protobuf==3.19.3; python_version >= "3.6" ptyprocess==0.7.0; python_version >= "3.6" and python_version < "4.0" py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" pyasn1==0.4.8; python_version >= "3.6" and python_version < "4" or python_version >= "3.6" pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pygithub==1.55; python_version >= "3.6" -pygments==2.10.0; python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" +pygments==2.11.2; python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" pyjwt==2.3.0; python_version >= "3.6" pylev==1.4.0; python_version >= "3.6" and python_version < "4.0" -pylint==2.11.1; python_version >= "3.6" and python_version < "4.0" -pynacl==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" +pylint==2.12.0; python_version >= "3.6" and python_version < "4.0" +pynacl==1.5.0; python_version >= "3.6" or python_version >= "3.6" pynsist==2.7; python_version >= "3.6" pyopenssl==21.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" pyparsing==3.0.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" @@ -127,36 +129,36 @@ pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_ pyrfc3339==1.1; python_version >= "3.6" pyrsistent==0.18.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pytest-cov==3.0.0; python_version >= "3.6" or python_version >= "3.6" -pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pytest-xdist==2.4.0; python_version >= "3.6" or python_version >= "3.6" -pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pytest-forked==1.4.0; python_version >= "3.6" +pytest-xdist==2.5.0; python_version >= "3.6" or python_version >= "3.6" +pytest==6.2.5; python_version >= "3.6" or python_version >= "3.6" python-augeas==1.1.0; python_version >= "3.6" python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" python-digitalocean==1.17.0; python_version >= "3.6" python-dotenv==0.19.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pytz==2021.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6" pywin32-ctypes==0.2.0; python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" -pywin32==302; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") +pywin32==303; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") pyyaml==5.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" -readme-renderer==30.0; python_version >= "3.6" +readme-renderer==32.0; python_version >= "3.6" requests-download==0.1.2; python_version >= "3.6" requests-file==1.5.1; python_version >= "3.6" and python_version < "4.0" requests-oauthlib==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" requests-toolbelt==0.9.1; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" or python_version >= "3.6" -requests==2.26.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" +requests==2.27.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" rfc3986==1.5.0; python_version >= "3.6" rsa==4.7.2; python_version >= "3.6" and python_version < "4" or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") s3transfer==0.5.0; python_version >= "3.6" secretstorage==3.3.1; python_version >= "3.6" and python_version < "4.0" and sys_platform == "linux" semantic-version==2.8.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" setuptools-rust==0.12.1; python_version >= "3.6" -setuptools==59.2.0; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" or python_version >= "3.6" and python_version < "4.0" +setuptools==59.6.0; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version == "3.6" or python_version >= "3.7" or python_version >= "3.6" and python_version < "4.0" shellingham==1.4.0; python_version >= "3.6" and python_version < "4.0" six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" or python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.3.0" snowballstemmer==2.2.0; python_version >= "3.6" soupsieve==2.3.1; python_full_version > "3.0.0" and python_version >= "3.6" sphinx-rtd-theme==1.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" -sphinx==4.3.0; python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" +sphinx==4.3.2; python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" sphinxcontrib-applehelp==1.0.2; python_version >= "3.6" sphinxcontrib-devhelp==1.0.2; python_version >= "3.6" sphinxcontrib-htmlhelp==2.0.0; python_version >= "3.6" @@ -166,32 +168,33 @@ sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.6" texttable==1.6.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" tldextract==3.1.2; python_version >= "3.6" and python_version < "4.0" toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_version == "3.6" and python_full_version < "3.0.0" or python_version > "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.3.0" or python_version > "3.6" and python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -tomli==1.2.2; python_version >= "3.6" -tomlkit==0.7.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" -tox==3.24.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +tomli==1.2.3; python_version >= "3.6" +tomlkit==0.8.0; python_version >= "3.6" and python_version < "4.0" +tox==3.24.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" tqdm==4.62.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" traitlets==4.3.3 twine==3.3.0; python_version >= "3.6" -typed-ast==1.4.3; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" -types-cryptography==3.3.8; python_version >= "3.6" -types-enum34==1.1.1; python_version >= "3.6" -types-ipaddress==1.0.1; python_version >= "3.6" -types-mock==4.0.3; python_version >= "3.6" -types-pyopenssl==21.0.0; python_version >= "3.6" -types-pyrfc3339==1.1.0; python_version >= "3.6" -types-python-dateutil==2.8.2; python_version >= "3.6" -types-pytz==2021.3.0; python_version >= "3.6" -types-requests==2.26.0; python_version >= "3.6" -types-setuptools==57.4.2; python_version >= "3.6" -types-six==1.16.2; python_version >= "3.6" -typing-extensions==4.0.0; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" +typed-ast==1.5.1; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" +types-cryptography==3.3.14; python_version >= "3.6" +types-enum34==1.1.7; python_version >= "3.6" +types-ipaddress==1.0.7; python_version >= "3.6" +types-mock==4.0.8; python_version >= "3.6" +types-pyopenssl==21.0.3; python_version >= "3.6" +types-pyrfc3339==1.1.1; python_version >= "3.6" +types-python-dateutil==2.8.7; python_version >= "3.6" +types-pytz==2021.3.4; python_version >= "3.6" +types-requests==2.27.7; python_version >= "3.6" +types-setuptools==57.4.7; python_version >= "3.6" +types-six==1.16.10; python_version >= "3.6" +types-urllib3==1.26.7; python_version >= "3.6" +typing-extensions==4.0.1; python_version >= "3.6" or python_version >= "3.6" and python_version < "3.10" or python_version < "3.8" and python_version >= "3.6" uritemplate==4.1.1; python_version >= "3.6" -urllib3==1.26.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6" +urllib3==1.26.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6" virtualenv==20.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" wcwidth==0.2.5; python_version == "3.6" and python_full_version >= "3.6.2" webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" or python_version >= "3.6" websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version >= "3.6" -wheel==0.37.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" +wheel==0.37.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" wrapt==1.13.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" yarg==0.1.9; python_version >= "3.6" zipp==3.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_version >= "3.6" and python_version < "3.8" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_version < "3.7" and python_full_version >= "3.4.0"