mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 08:45:22 +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_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)',
|
||||
'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')
|
||||
@@ -220,8 +224,11 @@ class ChallengeBody(ResourceBody):
|
||||
"""
|
||||
__slots__ = ('chall',)
|
||||
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)
|
||||
error = jose.Field('error', decoder=Error.from_json,
|
||||
omitempty=True, default=None)
|
||||
|
||||
def to_partial_json(self):
|
||||
jobj = super(ChallengeBody, self).to_partial_json()
|
||||
|
||||
+14
-4
@@ -198,19 +198,29 @@ class ChallengeBodyTest(unittest.TestCase):
|
||||
self.chall = challenges.DNS(token='foo')
|
||||
|
||||
from acme.messages import ChallengeBody
|
||||
from acme.messages import STATUS_VALID
|
||||
self.status = STATUS_VALID
|
||||
from acme.messages import Error
|
||||
from acme.messages import STATUS_INVALID
|
||||
self.status = STATUS_INVALID
|
||||
error = Error(typ='serverInternal',
|
||||
detail='Unable to communicate with DNS server')
|
||||
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 = {
|
||||
'uri': 'http://challb',
|
||||
'status': self.status,
|
||||
'type': 'dns',
|
||||
'token': 'foo',
|
||||
'error': error,
|
||||
}
|
||||
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):
|
||||
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
|
||||
if 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)
|
||||
if achall.domain in chall_update:
|
||||
chall_update[achall.domain].append(achall)
|
||||
@@ -170,23 +173,27 @@ class AuthHandler(object):
|
||||
while dom_to_check and rounds < max_rounds:
|
||||
# TODO: Use retry-after...
|
||||
time.sleep(min_sleep)
|
||||
all_failed_achalls = set()
|
||||
for domain in dom_to_check:
|
||||
comp_challs, failed_challs = self._handle_check(
|
||||
comp_achalls, failed_achalls = self._handle_check(
|
||||
domain, chall_update[domain])
|
||||
|
||||
if len(comp_challs) == len(chall_update[domain]):
|
||||
if len(comp_achalls) == len(chall_update[domain]):
|
||||
comp_domains.add(domain)
|
||||
elif not failed_challs:
|
||||
for chall in comp_challs:
|
||||
chall_update[domain].remove(chall)
|
||||
elif not failed_achalls:
|
||||
for achall, _ in comp_achalls:
|
||||
chall_update[domain].remove(achall)
|
||||
# We failed some challenges... damage control
|
||||
else:
|
||||
# Right now... just assume a loss and carry on...
|
||||
if best_effort:
|
||||
comp_domains.add(domain)
|
||||
else:
|
||||
raise errors.AuthorizationError(
|
||||
"Failed Authorization procedure for %s" % domain)
|
||||
all_failed_achalls.update(
|
||||
updated for _, updated in failed_achalls)
|
||||
|
||||
if all_failed_achalls:
|
||||
raise errors.FailedChallenges(all_failed_achalls)
|
||||
|
||||
dom_to_check -= comp_domains
|
||||
comp_domains.clear()
|
||||
@@ -204,32 +211,31 @@ class AuthHandler(object):
|
||||
# Note: if the whole authorization is invalid, the individual failed
|
||||
# challenges will be determined here...
|
||||
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.
|
||||
if status == messages.STATUS_VALID:
|
||||
completed.append(achall)
|
||||
elif status == messages.STATUS_INVALID:
|
||||
failed.append(achall)
|
||||
if updated_achall.status == messages.STATUS_VALID:
|
||||
completed.append((achall, updated_achall))
|
||||
elif updated_achall.status == messages.STATUS_INVALID:
|
||||
failed.append((achall, updated_achall))
|
||||
|
||||
return completed, failed
|
||||
|
||||
def _get_chall_status(self, authzr, achall): # pylint: disable=no-self-use
|
||||
"""Get the status of the challenge.
|
||||
def _find_updated_challb(self, authzr, achall): # pylint: disable=no-self-use
|
||||
"""Find updated challenge body within Authorization Resource.
|
||||
|
||||
.. warning:: This assumes only one instance of type of challenge in
|
||||
each challenge resource.
|
||||
|
||||
:param authzr: Authorization Resource
|
||||
:type authzr: :class:`acme.messages.AuthorizationResource`
|
||||
|
||||
:param achall: Annotated challenge for which to get status
|
||||
:type achall: :class:`letsencrypt.achallenges.AnnotatedChallenge`
|
||||
:param .AuthorizationResource authzr: Authorization Resource
|
||||
:param .AnnotatedChallenge achall: Annotated challenge for which
|
||||
to get status
|
||||
|
||||
"""
|
||||
for authzr_challb in authzr.body.challenges:
|
||||
if type(authzr_challb.chall) is type(achall.challb.chall):
|
||||
return authzr_challb.status
|
||||
return authzr_challb
|
||||
raise errors.AuthorizationError(
|
||||
"Target challenge not found in authorization resource")
|
||||
|
||||
|
||||
@@ -15,6 +15,24 @@ class AuthorizationError(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):
|
||||
"""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