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:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
|
||||
if TYPE_CHECKING:
|
||||
ServedType = DefaultDict[
|
||||
acme_standalone.BaseDualNetworkedServers,
|
||||
Set[achallenges.KeyAuthorizationAnnotatedChallenge]
|
||||
Set[achallenges.AnnotatedChallenge]
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user