mirror of
https://github.com/certbot/certbot.git
synced 2026-08-02 19:22:16 +02:00
Merge branch 'acme-spec-158' of https://github.com/kuba/lets-encrypt-preview
Resolved conflict in errors.py (LetsEncryptContAuthError renamed to ContAuthError) Conflicts: letsencrypt/errors.py
This commit is contained in:
+11
-4
@@ -14,11 +14,15 @@ class Error(jose.JSONObjectWithFields, Exception):
|
|||||||
"""
|
"""
|
||||||
ERROR_TYPE_NAMESPACE = 'urn:acme:error:'
|
ERROR_TYPE_NAMESPACE = 'urn:acme:error:'
|
||||||
ERROR_TYPE_DESCRIPTIONS = {
|
ERROR_TYPE_DESCRIPTIONS = {
|
||||||
'malformed': 'The request message was malformed',
|
|
||||||
'unauthorized': 'The client lacks sufficient authorization',
|
|
||||||
'serverInternal': 'The server experienced an internal error',
|
|
||||||
'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',
|
'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',
|
||||||
'badNonce': 'The client sent an unacceptable anti-replay nonce',
|
'badNonce': 'The client sent an unacceptable anti-replay nonce',
|
||||||
|
'connection': 'The server could not connect to the client for DV',
|
||||||
|
'dnssec': 'The server could not validate a DNSSEC signed domain',
|
||||||
|
'malformed': 'The request message was malformed',
|
||||||
|
'serverInternal': 'The server experienced an internal error',
|
||||||
|
'tls': 'The server experienced a TLS error during DV',
|
||||||
|
'unauthorized': 'The client lacks sufficient authorization',
|
||||||
|
'unknownHost': 'The server could not resolve a domain name',
|
||||||
}
|
}
|
||||||
|
|
||||||
typ = jose.Field('type')
|
typ = jose.Field('type')
|
||||||
@@ -220,8 +224,11 @@ class ChallengeBody(ResourceBody):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('chall',)
|
__slots__ = ('chall',)
|
||||||
uri = jose.Field('uri')
|
uri = jose.Field('uri')
|
||||||
status = jose.Field('status', decoder=Status.from_json)
|
status = jose.Field('status', decoder=Status.from_json,
|
||||||
|
omitempty=True, default=STATUS_PENDING)
|
||||||
validated = fields.RFC3339Field('validated', omitempty=True)
|
validated = fields.RFC3339Field('validated', omitempty=True)
|
||||||
|
error = jose.Field('error', decoder=Error.from_json,
|
||||||
|
omitempty=True, default=None)
|
||||||
|
|
||||||
def to_partial_json(self):
|
def to_partial_json(self):
|
||||||
jobj = super(ChallengeBody, self).to_partial_json()
|
jobj = super(ChallengeBody, self).to_partial_json()
|
||||||
|
|||||||
+14
-4
@@ -198,19 +198,29 @@ class ChallengeBodyTest(unittest.TestCase):
|
|||||||
self.chall = challenges.DNS(token='foo')
|
self.chall = challenges.DNS(token='foo')
|
||||||
|
|
||||||
from acme.messages import ChallengeBody
|
from acme.messages import ChallengeBody
|
||||||
from acme.messages import STATUS_VALID
|
from acme.messages import Error
|
||||||
self.status = STATUS_VALID
|
from acme.messages import STATUS_INVALID
|
||||||
|
self.status = STATUS_INVALID
|
||||||
|
error = Error(typ='serverInternal',
|
||||||
|
detail='Unable to communicate with DNS server')
|
||||||
self.challb = ChallengeBody(
|
self.challb = ChallengeBody(
|
||||||
uri='http://challb', chall=self.chall, status=self.status)
|
uri='http://challb', chall=self.chall, status=self.status,
|
||||||
|
error=error)
|
||||||
|
|
||||||
self.jobj_to = {
|
self.jobj_to = {
|
||||||
'uri': 'http://challb',
|
'uri': 'http://challb',
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'type': 'dns',
|
'type': 'dns',
|
||||||
'token': 'foo',
|
'token': 'foo',
|
||||||
|
'error': error,
|
||||||
}
|
}
|
||||||
self.jobj_from = self.jobj_to.copy()
|
self.jobj_from = self.jobj_to.copy()
|
||||||
self.jobj_from['status'] = 'valid'
|
self.jobj_from['status'] = 'invalid'
|
||||||
|
self.jobj_from['error'] = {
|
||||||
|
'type': 'urn:acme:error:serverInternal',
|
||||||
|
'detail': 'Unable to communicate with DNS server',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
def test_to_partial_json(self):
|
||||||
self.assertEqual(self.jobj_to, self.challb.to_partial_json())
|
self.assertEqual(self.jobj_to, self.challb.to_partial_json())
|
||||||
|
|||||||
+26
-20
@@ -152,6 +152,9 @@ class AuthHandler(object):
|
|||||||
# Don't send challenges for None and False authenticator responses
|
# Don't send challenges for None and False authenticator responses
|
||||||
if resp:
|
if resp:
|
||||||
self.network.answer_challenge(achall.challb, resp)
|
self.network.answer_challenge(achall.challb, resp)
|
||||||
|
# TODO: answer_challenge returns challr, with URI,
|
||||||
|
# that can be used in _find_updated_challr
|
||||||
|
# comparisons...
|
||||||
active_achalls.append(achall)
|
active_achalls.append(achall)
|
||||||
if achall.domain in chall_update:
|
if achall.domain in chall_update:
|
||||||
chall_update[achall.domain].append(achall)
|
chall_update[achall.domain].append(achall)
|
||||||
@@ -170,23 +173,27 @@ class AuthHandler(object):
|
|||||||
while dom_to_check and rounds < max_rounds:
|
while dom_to_check and rounds < max_rounds:
|
||||||
# TODO: Use retry-after...
|
# TODO: Use retry-after...
|
||||||
time.sleep(min_sleep)
|
time.sleep(min_sleep)
|
||||||
|
all_failed_achalls = set()
|
||||||
for domain in dom_to_check:
|
for domain in dom_to_check:
|
||||||
comp_challs, failed_challs = self._handle_check(
|
comp_achalls, failed_achalls = self._handle_check(
|
||||||
domain, chall_update[domain])
|
domain, chall_update[domain])
|
||||||
|
|
||||||
if len(comp_challs) == len(chall_update[domain]):
|
if len(comp_achalls) == len(chall_update[domain]):
|
||||||
comp_domains.add(domain)
|
comp_domains.add(domain)
|
||||||
elif not failed_challs:
|
elif not failed_achalls:
|
||||||
for chall in comp_challs:
|
for achall, _ in comp_achalls:
|
||||||
chall_update[domain].remove(chall)
|
chall_update[domain].remove(achall)
|
||||||
# We failed some challenges... damage control
|
# We failed some challenges... damage control
|
||||||
else:
|
else:
|
||||||
# Right now... just assume a loss and carry on...
|
# Right now... just assume a loss and carry on...
|
||||||
if best_effort:
|
if best_effort:
|
||||||
comp_domains.add(domain)
|
comp_domains.add(domain)
|
||||||
else:
|
else:
|
||||||
raise errors.AuthorizationError(
|
all_failed_achalls.update(
|
||||||
"Failed Authorization procedure for %s" % domain)
|
updated for _, updated in failed_achalls)
|
||||||
|
|
||||||
|
if all_failed_achalls:
|
||||||
|
raise errors.FailedChallenges(all_failed_achalls)
|
||||||
|
|
||||||
dom_to_check -= comp_domains
|
dom_to_check -= comp_domains
|
||||||
comp_domains.clear()
|
comp_domains.clear()
|
||||||
@@ -204,32 +211,31 @@ class AuthHandler(object):
|
|||||||
# Note: if the whole authorization is invalid, the individual failed
|
# Note: if the whole authorization is invalid, the individual failed
|
||||||
# challenges will be determined here...
|
# challenges will be determined here...
|
||||||
for achall in achalls:
|
for achall in achalls:
|
||||||
status = self._get_chall_status(self.authzr[domain], achall)
|
updated_achall = achall.update(challb=self._find_updated_challb(
|
||||||
|
self.authzr[domain], achall))
|
||||||
|
|
||||||
# This does nothing for challenges that have yet to be decided yet.
|
# This does nothing for challenges that have yet to be decided yet.
|
||||||
if status == messages.STATUS_VALID:
|
if updated_achall.status == messages.STATUS_VALID:
|
||||||
completed.append(achall)
|
completed.append((achall, updated_achall))
|
||||||
elif status == messages.STATUS_INVALID:
|
elif updated_achall.status == messages.STATUS_INVALID:
|
||||||
failed.append(achall)
|
failed.append((achall, updated_achall))
|
||||||
|
|
||||||
return completed, failed
|
return completed, failed
|
||||||
|
|
||||||
def _get_chall_status(self, authzr, achall): # pylint: disable=no-self-use
|
def _find_updated_challb(self, authzr, achall): # pylint: disable=no-self-use
|
||||||
"""Get the status of the challenge.
|
"""Find updated challenge body within Authorization Resource.
|
||||||
|
|
||||||
.. warning:: This assumes only one instance of type of challenge in
|
.. warning:: This assumes only one instance of type of challenge in
|
||||||
each challenge resource.
|
each challenge resource.
|
||||||
|
|
||||||
:param authzr: Authorization Resource
|
:param .AuthorizationResource authzr: Authorization Resource
|
||||||
:type authzr: :class:`acme.messages.AuthorizationResource`
|
:param .AnnotatedChallenge achall: Annotated challenge for which
|
||||||
|
to get status
|
||||||
:param achall: Annotated challenge for which to get status
|
|
||||||
:type achall: :class:`letsencrypt.achallenges.AnnotatedChallenge`
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for authzr_challb in authzr.body.challenges:
|
for authzr_challb in authzr.body.challenges:
|
||||||
if type(authzr_challb.chall) is type(achall.challb.chall):
|
if type(authzr_challb.chall) is type(achall.challb.chall):
|
||||||
return authzr_challb.status
|
return authzr_challb
|
||||||
raise errors.AuthorizationError(
|
raise errors.AuthorizationError(
|
||||||
"Target challenge not found in authorization resource")
|
"Target challenge not found in authorization resource")
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,24 @@ class AuthorizationError(Error):
|
|||||||
"""Authorization error."""
|
"""Authorization error."""
|
||||||
|
|
||||||
|
|
||||||
|
class FailedChallenges(AuthorizationError):
|
||||||
|
"""Failed challenges error.
|
||||||
|
|
||||||
|
:ivar set failed_achalls: Failed `.AnnotatedChallenge` instances.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, failed_achalls):
|
||||||
|
assert failed_achalls
|
||||||
|
self.failed_achalls = failed_achalls
|
||||||
|
super(FailedChallenges, self).__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Failed authorization procedure. {0}".format(
|
||||||
|
", ".join(
|
||||||
|
"{0} ({1}): {2}".format(achall.domain, achall.typ, achall.error)
|
||||||
|
for achall in self.failed_achalls if achall.error is not None))
|
||||||
|
|
||||||
|
|
||||||
class ContAuthError(AuthorizationError):
|
class ContAuthError(AuthorizationError):
|
||||||
"""Let's Encrypt Continuity Authenticator error."""
|
"""Let's Encrypt Continuity Authenticator error."""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""Tests for letsencrypt.errors."""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from acme import messages
|
||||||
|
|
||||||
|
from letsencrypt import achallenges
|
||||||
|
from letsencrypt.tests import acme_util
|
||||||
|
|
||||||
|
|
||||||
|
class FaiiledChallengesTest(unittest.TestCase):
|
||||||
|
"""Tests for letsencrypt.errors.FailedChallenges."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from letsencrypt.errors import FailedChallenges
|
||||||
|
self.error = FailedChallenges(set([achallenges.DNS(
|
||||||
|
domain="example.com", challb=messages.ChallengeBody(
|
||||||
|
chall=acme_util.DNS, uri=None,
|
||||||
|
error=messages.Error(typ="tls", detail="detail")))]))
|
||||||
|
|
||||||
|
def test_str(self):
|
||||||
|
self.assertTrue(str(self.error).startswith(
|
||||||
|
"Failed authorization procedure. example.com (dns): tls"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main() # pragma: no cover
|
||||||
Reference in New Issue
Block a user