Resolved conflict in errors.py (LetsEncryptContAuthError renamed
to ContAuthError)

Conflicts:
	letsencrypt/errors.py
This commit is contained in:
Seth Schoen
2015-06-23 15:55:13 -07:00
5 changed files with 95 additions and 28 deletions
+11 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+18
View File
@@ -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."""
+26
View File
@@ -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