1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-26 07:41:33 +03:00

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 <bmw@users.noreply.github.com>
This commit is contained in:
Adrien Ferrand
2022-01-25 00:16:19 +01:00
committed by GitHub
parent 7198f43008
commit dac0b2c187
32 changed files with 398 additions and 325 deletions

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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(

View File

@@ -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'))

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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
"""

View File

@@ -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:

View File

@@ -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):

View File

@@ -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:

View File

@@ -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 "

View File

@@ -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

View File

@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
ServedType = DefaultDict[
acme_standalone.BaseDualNetworkedServers,
Set[achallenges.KeyAuthorizationAnnotatedChallenge]
Set[achallenges.AnnotatedChallenge]
]

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 = {

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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,

View File

@@ -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"

View File

@@ -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

View File

@@ -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"