mirror of
https://github.com/certbot/certbot.git
synced 2026-06-07 15:52:08 -04:00
commit
7d2023c64e
3 changed files with 143 additions and 5 deletions
|
|
@ -3,12 +3,15 @@ import itertools
|
|||
import logging
|
||||
import time
|
||||
|
||||
import zope.component
|
||||
|
||||
from acme import challenges
|
||||
from acme import messages
|
||||
|
||||
from letsencrypt import achallenges
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import interfaces
|
||||
|
||||
|
||||
class AuthHandler(object):
|
||||
|
|
@ -193,6 +196,7 @@ class AuthHandler(object):
|
|||
updated for _, updated in failed_achalls)
|
||||
|
||||
if all_failed_achalls:
|
||||
_report_failed_challs(all_failed_achalls)
|
||||
raise errors.FailedChallenges(all_failed_achalls)
|
||||
|
||||
dom_to_check -= comp_domains
|
||||
|
|
@ -480,3 +484,80 @@ def is_preferred(offered_challb, satisfied,
|
|||
different=True):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
_ERROR_HELP_COMMON = (
|
||||
"To fix these errors, please make sure that your domain name was entered "
|
||||
"correctly and the DNS A/AAAA record(s) for that domain contains the "
|
||||
"right IP address.")
|
||||
|
||||
|
||||
_ERROR_HELP = {
|
||||
"connection" :
|
||||
_ERROR_HELP_COMMON + " Additionally, please check that your computer "
|
||||
"has publicly routable IP address and no firewalls are preventing the "
|
||||
"server from communicating with the client.",
|
||||
"dnssec" :
|
||||
_ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for "
|
||||
"your domain, please ensure the signature is valid.",
|
||||
"malformed" :
|
||||
"To fix these errors, please make sure that you did not provide any "
|
||||
"invalid information to the client and try running Let's Encrypt "
|
||||
"again.",
|
||||
"serverInternal" :
|
||||
"Unfortunately, an error on the ACME server prevented you from completing "
|
||||
"authorization. Please try again later.",
|
||||
"tls" :
|
||||
_ERROR_HELP_COMMON + " Additionally, please check that you have an up "
|
||||
"to date TLS configuration that allows the server to communicate with "
|
||||
"the Let's Encrypt client.",
|
||||
"unauthorized" : _ERROR_HELP_COMMON,
|
||||
"unknownHost" : _ERROR_HELP_COMMON,}
|
||||
|
||||
|
||||
def _report_failed_challs(failed_achalls):
|
||||
"""Notifies the user about failed challenges.
|
||||
|
||||
:param set failed_achalls: A set of failed
|
||||
:class:`letsencrypt.achallenges.AnnotatedChallenge`.
|
||||
|
||||
"""
|
||||
problems = dict()
|
||||
for achall in failed_achalls:
|
||||
if achall.error:
|
||||
problems.setdefault(achall.error.typ, []).append(achall)
|
||||
|
||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||
for achalls in problems.itervalues():
|
||||
reporter.add_message(
|
||||
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY, True)
|
||||
|
||||
|
||||
def _generate_failed_chall_msg(failed_achalls):
|
||||
"""Creates a user friendly error message about failed challenges.
|
||||
|
||||
:param list failed_achalls: A list of failed
|
||||
:class:`letsencrypt.achallenges.AnnotatedChallenge` with the same error
|
||||
type.
|
||||
|
||||
:returns: A formatted error message for the client.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
typ = failed_achalls[0].error.typ
|
||||
msg = [
|
||||
"The following '{0}' errors were reported by the server:".format(typ)]
|
||||
|
||||
problems = dict()
|
||||
for achall in failed_achalls:
|
||||
problems.setdefault(achall.error.description, set()).add(achall.domain)
|
||||
for problem in problems:
|
||||
msg.append("\n\nDomains: ")
|
||||
msg.append(", ".join(sorted(problems[problem])))
|
||||
msg.append("\nError: {0}".format(problem))
|
||||
|
||||
if typ in _ERROR_HELP:
|
||||
msg.append("\n\n")
|
||||
msg.append(_ERROR_HELP[typ])
|
||||
|
||||
return "".join(msg)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ class Reporter(object):
|
|||
|
||||
If there is an unhandled exception, only messages for which
|
||||
``on_crash`` is ``True`` are printed.
|
||||
"""
|
||||
|
||||
"""
|
||||
bold_on = False
|
||||
if not self.messages.empty():
|
||||
no_exception = sys.exc_info()[0] is None
|
||||
|
|
@ -74,14 +75,21 @@ class Reporter(object):
|
|||
if bold_on:
|
||||
print self._BOLD
|
||||
print 'IMPORTANT NOTES:'
|
||||
wrapper = textwrap.TextWrapper(initial_indent=' - ',
|
||||
subsequent_indent=(' ' * 3))
|
||||
first_wrapper = textwrap.TextWrapper(
|
||||
initial_indent=' - ', subsequent_indent=(' ' * 3))
|
||||
next_wrapper = textwrap.TextWrapper(
|
||||
initial_indent=first_wrapper.subsequent_indent,
|
||||
subsequent_indent=first_wrapper.subsequent_indent)
|
||||
while not self.messages.empty():
|
||||
msg = self.messages.get()
|
||||
if no_exception or msg.on_crash:
|
||||
if bold_on and msg.priority > self.HIGH_PRIORITY:
|
||||
sys.stdout.write(self._RESET)
|
||||
bold_on = False
|
||||
print wrapper.fill(msg.text)
|
||||
lines = msg.text.splitlines()
|
||||
print first_wrapper.fill(lines[0])
|
||||
if len(lines) > 1:
|
||||
print "\n".join(
|
||||
next_wrapper.fill(line) for line in lines[1:])
|
||||
if bold_on:
|
||||
sys.stdout.write(self._RESET)
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ class PollChallengesTest(unittest.TestCase):
|
|||
self.assertEqual(authzr.body.status, messages.STATUS_PENDING)
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.time")
|
||||
def test_poll_challenges_failure(self, unused_mock_time):
|
||||
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
|
||||
def test_poll_challenges_failure(self, unused_mock_time, unused_mock_zope):
|
||||
self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid
|
||||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler._poll_challenges,
|
||||
|
|
@ -420,6 +421,54 @@ class IsPreferredTest(unittest.TestCase):
|
|||
self._call(acme_util.DVSNI_P, frozenset([acme_util.DVSNI_P])))
|
||||
|
||||
|
||||
class ReportFailedChallsTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.auth_handler._report_failed_challs."""
|
||||
# pylint: disable=protected-access
|
||||
|
||||
def setUp(self):
|
||||
from letsencrypt import achallenges
|
||||
|
||||
kwargs = {
|
||||
"chall" : acme_util.SIMPLE_HTTP,
|
||||
"uri": "uri",
|
||||
"status": messages.STATUS_INVALID,
|
||||
"error": messages.Error(typ="tls", detail="detail"),
|
||||
}
|
||||
|
||||
self.simple_http = achallenges.SimpleHTTP(
|
||||
challb=messages.ChallengeBody(**kwargs),# pylint: disable=star-args
|
||||
domain="example.com",
|
||||
key=acme_util.KEY)
|
||||
|
||||
kwargs["chall"] = acme_util.DVSNI
|
||||
self.dvsni_same = achallenges.DVSNI(
|
||||
challb=messages.ChallengeBody(**kwargs),# pylint: disable=star-args
|
||||
domain="example.com",
|
||||
key=acme_util.KEY)
|
||||
|
||||
kwargs["error"] = messages.Error(typ="dnssec", detail="detail")
|
||||
self.dvsni_diff = achallenges.DVSNI(
|
||||
challb=messages.ChallengeBody(**kwargs),# pylint: disable=star-args
|
||||
domain="foo.bar",
|
||||
key=acme_util.KEY)
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
|
||||
def test_same_error_and_domain(self, mock_zope):
|
||||
from letsencrypt import auth_handler
|
||||
|
||||
auth_handler._report_failed_challs([self.simple_http, self.dvsni_same])
|
||||
call_list = mock_zope().add_message.call_args_list
|
||||
self.assertTrue(len(call_list) == 1)
|
||||
self.assertTrue("Domains: example.com\n" in call_list[0][0][0])
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.zope.component.getUtility")
|
||||
def test_different_errors_and_domains(self, mock_zope):
|
||||
from letsencrypt import auth_handler
|
||||
|
||||
auth_handler._report_failed_challs([self.simple_http, self.dvsni_diff])
|
||||
self.assertTrue(mock_zope().add_message.call_count == 2)
|
||||
|
||||
|
||||
def gen_auth_resp(chall_list):
|
||||
"""Generate a dummy authorization response."""
|
||||
return ["%s%s" % (chall.__class__.__name__, chall.domain)
|
||||
|
|
|
|||
Loading…
Reference in a new issue