From cbfdae88fcde764b0a60190d12881c9945fe2437 Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 27 Sep 2015 14:44:00 -0400 Subject: [PATCH 01/22] Add Mac compatibility to boulder-start The version of sort that ships with OS X does not support the -V version flag. Emulate that functionality with some sed-fu --- tests/boulder-start.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/boulder-start.sh b/tests/boulder-start.sh index 7ce7dcba4..e8c50633f 100755 --- a/tests/boulder-start.sh +++ b/tests/boulder-start.sh @@ -4,11 +4,16 @@ # ugh, go version output is like: # go version go1.4.2 linux/amd64 -GOVER=`go version | cut -d" " -f3 | cut -do -f2` +GOVER=`go version | cut -d" " -f3 | cut -do -f2` # version comparison function verlte { + if [ `uname` == 'Darwin' ]; then + [ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \ + | sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ] + else [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] + fi } if ! verlte 1.5 "$GOVER" ; then From 5d8e9a3d68b362634c9fb752e5a0bcb4fb12d021 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 27 Sep 2015 21:07:40 +0000 Subject: [PATCH 02/22] Fix various doc generation issues --- acme/acme/challenges.py | 2 +- docs/api/display.rst | 6 ------ docs/api/recovery_token.rst | 5 ----- docs/api/revoker.rst | 5 ----- letsencrypt-apache/letsencrypt_apache/configurator.py | 2 +- 5 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 docs/api/recovery_token.rst delete mode 100644 docs/api/revoker.rst diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 13186cc4f..81711e605 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -542,7 +542,7 @@ class DNS(DVChallenge): def check_validation(self, validation, account_public_key): """Check validation. - :param validation + :param JWS validation: :type account_public_key: `~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` or diff --git a/docs/api/display.rst b/docs/api/display.rst index b79ef25d7..117a91708 100644 --- a/docs/api/display.rst +++ b/docs/api/display.rst @@ -21,9 +21,3 @@ .. automodule:: letsencrypt.display.enhancements :members: - -:mod:`letsencrypt.display.revocation` -===================================== - -.. automodule:: letsencrypt.display.revocation - :members: diff --git a/docs/api/recovery_token.rst b/docs/api/recovery_token.rst deleted file mode 100644 index 774aa4b3c..000000000 --- a/docs/api/recovery_token.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.recovery_token` --------------------------------------------------- - -.. automodule:: letsencrypt.recovery_token - :members: diff --git a/docs/api/revoker.rst b/docs/api/revoker.rst deleted file mode 100644 index a482a138e..000000000 --- a/docs/api/revoker.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.revoker` --------------------------- - -.. automodule:: letsencrypt.revoker - :members: diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index f301de8b9..ad3c62d2c 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -1162,7 +1162,7 @@ def _get_mod_deps(mod_name): changes. .. warning:: If all deps are not included, it may cause incorrect parsing behavior, due to enable_mod's shortcut for updating the parser's - currently defined modules (:method:`.ApacheConfigurator._add_parser_mod`) + currently defined modules (`.ApacheConfigurator._add_parser_mod`) This would only present a major problem in extremely atypical configs that use ifmod for the missing deps. From 96a737bbbaf9aa76accdbd9421b19e38a0703e72 Mon Sep 17 00:00:00 2001 From: David Xia Date: Sun, 27 Sep 2015 16:51:20 -0400 Subject: [PATCH 03/22] Fix CLI --help for OS X OS X's signal module doesn't have SIGPWR. Don't try to use it. Fixes #841 --- letsencrypt/error_handler.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/letsencrypt/error_handler.py b/letsencrypt/error_handler.py index fedb66c0e..99f502ac2 100644 --- a/letsencrypt/error_handler.py +++ b/letsencrypt/error_handler.py @@ -2,6 +2,7 @@ import logging import os import signal +import sys import traceback @@ -13,9 +14,14 @@ logger = logging.getLogger(__name__) # potentially occur from inside Python. Signals such as SIGILL were not # included as they could be a sign of something devious and we should terminate # immediately. -_SIGNALS = ([signal.SIGTERM] if os.name == "nt" else - [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, - signal.SIGXCPU, signal.SIGXFSZ, signal.SIGPWR]) +if os.name == "nt": + _SIGNALS = [signal.SIGTERM] +elif sys.platform == "darwin": + _SIGNALS = [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, + signal.SIGXFSZ] +else: + _SIGNALS = [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, + signal.SIGXFSZ, signal.SIGPWR] class ErrorHandler(object): From a7375eb5494df494d2604ee1e903467b093af30b Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sun, 27 Sep 2015 17:44:31 -0700 Subject: [PATCH 04/22] Emit error when simple_verify fails. When running the manual authenticator, if simple_verify fails, there is no output to indicate what went wrong, just "Incomplete authorizations." --- letsencrypt/plugins/manual.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index 2014c8c0e..2fad4ac53 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -182,6 +182,8 @@ binary for temporary key/certificate generation.""".replace("\n", "") achall.account_key.public_key(), self.config.simple_http_port): return response else: + logger.error( + "Self-verify of challenge failed, authorization abandoned.\n") if self.conf("test-mode") and self._httpd.poll() is not None: # simply verify cause command failure... return False From 913a0a9e98b2559ab960b58dd533a932cdde8150 Mon Sep 17 00:00:00 2001 From: Jadaw1n Date: Mon, 28 Sep 2015 17:34:43 +0200 Subject: [PATCH 05/22] Dockerfile: option --text doesn't exist --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 789e26af9..b9ea168de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,5 +62,5 @@ RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \ # bash" and investigate, apply patches, etc. ENV PATH /opt/letsencrypt/venv/bin:$PATH -# TODO: is --text really necessary? -ENTRYPOINT [ "letsencrypt", "--text" ] + +ENTRYPOINT [ "letsencrypt" ] From 27268afdcc82a34e0d37d39bd6a14af5431ddb8c Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Mon, 28 Sep 2015 11:58:12 -0700 Subject: [PATCH 06/22] Remove extra newline. --- letsencrypt/plugins/manual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index 2fad4ac53..3f7276725 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -183,7 +183,7 @@ binary for temporary key/certificate generation.""".replace("\n", "") return response else: logger.error( - "Self-verify of challenge failed, authorization abandoned.\n") + "Self-verify of challenge failed, authorization abandoned.") if self.conf("test-mode") and self._httpd.poll() is not None: # simply verify cause command failure... return False From 315b3577811fba3d3a540c22cc2f6bf772fb98af Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 27 Sep 2015 21:27:36 +0000 Subject: [PATCH 07/22] Hide null installer (fixes #789). --- letsencrypt/cli.py | 2 +- letsencrypt/display/ops.py | 2 +- letsencrypt/plugins/disco.py | 9 +++++++++ letsencrypt/plugins/null.py | 1 + letsencrypt/tests/display/ops_test.py | 12 +++++++----- setup.py | 1 - 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 3317ae549..8bcbd8f02 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -420,7 +420,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print logger.debug("Expected interfaces: %s", args.ifaces) ifaces = [] if args.ifaces is None else args.ifaces - filtered = plugins.ifaces(ifaces) + filtered = plugins.visible().ifaces(ifaces) logger.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: diff --git a/letsencrypt/display/ops.py b/letsencrypt/display/ops.py index 4ab3ec579..43705e309 100644 --- a/letsencrypt/display/ops.py +++ b/letsencrypt/display/ops.py @@ -65,7 +65,7 @@ def pick_plugin(config, default, plugins, question, ifaces): # throw more UX-friendly error if default not in plugins filtered = plugins.filter(lambda p_ep: p_ep.name == default) else: - filtered = plugins.ifaces(ifaces) + filtered = plugins.visible().ifaces(ifaces) filtered.init(config) verified = filtered.verify(ifaces) diff --git a/letsencrypt/plugins/disco.py b/letsencrypt/plugins/disco.py index b6cdb1f99..5a41fda88 100644 --- a/letsencrypt/plugins/disco.py +++ b/letsencrypt/plugins/disco.py @@ -50,6 +50,11 @@ class PluginEntryPoint(object): """Description with name. Handy for UI.""" return "{0} ({1})".format(self.description, self.name) + @property + def hidden(self): + """Should this plugin be hidden from UI?""" + return getattr(self.plugin_cls, "hidden", False) + def ifaces(self, *ifaces_groups): """Does plugin implements specified interface groups?""" return not ifaces_groups or any( @@ -183,6 +188,10 @@ class PluginsRegistry(collections.Mapping): return type(self)(dict((name, plugin_ep) for name, plugin_ep in self._plugins.iteritems() if pred(plugin_ep))) + def visible(self): + """Filter plugins based on visibility.""" + return self.filter(lambda plugin_ep: not plugin_ep.hidden) + def ifaces(self, *ifaces_groups): """Filter plugins based on interfaces.""" # pylint: disable=star-args diff --git a/letsencrypt/plugins/null.py b/letsencrypt/plugins/null.py index efe041cac..4ba6c9d64 100644 --- a/letsencrypt/plugins/null.py +++ b/letsencrypt/plugins/null.py @@ -17,6 +17,7 @@ class Installer(common.Plugin): zope.interface.classProvides(interfaces.IPluginFactory) description = "Null Installer" + hidden = True # pylint: disable=missing-docstring,no-self-use diff --git a/letsencrypt/tests/display/ops_test.py b/letsencrypt/tests/display/ops_test.py index 7420a62f0..9d4a3a933 100644 --- a/letsencrypt/tests/display/ops_test.py +++ b/letsencrypt/tests/display/ops_test.py @@ -84,7 +84,7 @@ class PickPluginTest(unittest.TestCase): def test_no_default(self): self._call() - self.assertEqual(1, self.reg.ifaces.call_count) + self.assertEqual(1, self.reg.visible().ifaces.call_count) def test_no_candidate(self): self.assertTrue(self._call() is None) @@ -94,7 +94,8 @@ class PickPluginTest(unittest.TestCase): plugin_ep.init.return_value = "foo" plugin_ep.misconfigured = False - self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep} + self.reg.visible().ifaces().verify().available.return_value = { + "bar": plugin_ep} self.assertEqual("foo", self._call()) def test_single_misconfigured(self): @@ -102,13 +103,14 @@ class PickPluginTest(unittest.TestCase): plugin_ep.init.return_value = "foo" plugin_ep.misconfigured = True - self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep} + self.reg.visible().ifaces().verify().available.return_value = { + "bar": plugin_ep} self.assertTrue(self._call() is None) def test_multiple(self): plugin_ep = mock.MagicMock() plugin_ep.init.return_value = "foo" - self.reg.ifaces().verify().available.return_value = { + self.reg.visible().ifaces().verify().available.return_value = { "bar": plugin_ep, "baz": plugin_ep, } @@ -119,7 +121,7 @@ class PickPluginTest(unittest.TestCase): [plugin_ep, plugin_ep], self.question) def test_choose_plugin_none(self): - self.reg.ifaces().verify().available.return_value = { + self.reg.visible().ifaces().verify().available.return_value = { "bar": None, "baz": None, } diff --git a/setup.py b/setup.py index c568d2872..8f75aff03 100644 --- a/setup.py +++ b/setup.py @@ -118,7 +118,6 @@ setup( ], 'letsencrypt.plugins': [ 'manual = letsencrypt.plugins.manual:Authenticator', - # TODO: null should probably not be presented to the user 'null = letsencrypt.plugins.null:Installer', 'standalone = letsencrypt.plugins.standalone.authenticator' ':StandaloneAuthenticator', From c1012f5f0082dd99d22fb5a49695dfbdfd433f19 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 12:25:37 -0700 Subject: [PATCH 08/22] Removed SIGPWR entirely --- letsencrypt/error_handler.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/letsencrypt/error_handler.py b/letsencrypt/error_handler.py index 99f502ac2..1f979a6de 100644 --- a/letsencrypt/error_handler.py +++ b/letsencrypt/error_handler.py @@ -2,7 +2,6 @@ import logging import os import signal -import sys import traceback @@ -14,14 +13,9 @@ logger = logging.getLogger(__name__) # potentially occur from inside Python. Signals such as SIGILL were not # included as they could be a sign of something devious and we should terminate # immediately. -if os.name == "nt": - _SIGNALS = [signal.SIGTERM] -elif sys.platform == "darwin": - _SIGNALS = [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, - signal.SIGXFSZ] -else: - _SIGNALS = [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, - signal.SIGXFSZ, signal.SIGPWR] +_SIGNALS = ([signal.SIGTERM] if os.name == "nt" else + [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, + signal.SIGXCPU, signal.SIGXFSZ]) class ErrorHandler(object): From ab98d5c39fc19cc90785a87f10cc4b53390e8b20 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 28 Sep 2015 17:14:33 -0400 Subject: [PATCH 09/22] Ignore unknown challenge types --- acme/acme/messages.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 02ae24c8f..002c08767 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -373,7 +373,17 @@ class Authorization(ResourceBody): @challenges.decoder def challenges(value): # pylint: disable=missing-docstring,no-self-argument - return tuple(ChallengeBody.from_json(chall) for chall in value) + # The from_json method raises errors.UnrecognizedTypeError when a + # challenge of unknown type is encountered. We want to ignore this + # case. This forces us to do an explicit iteration, since list + # comprehensions can't handle exceptions. + challenges = [] + for chall in value: + try: + challenges.append(ChallengeBody.from_json(chall)) + except errors.UnknownTypeError: + continue + return tuple(challenges) @property def resolved_combinations(self): From b6bbc9e0a29a7b64ecc03b1ffbbccf67cac37238 Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Mon, 28 Sep 2015 17:39:01 -0400 Subject: [PATCH 10/22] Add inline Mac comment --- tests/boulder-start.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/boulder-start.sh b/tests/boulder-start.sh index e8c50633f..530f9c598 100755 --- a/tests/boulder-start.sh +++ b/tests/boulder-start.sh @@ -8,6 +8,7 @@ GOVER=`go version | cut -d" " -f3 | cut -do -f2` # version comparison function verlte { + #OS X doesn't support version sorting; emulate with sed if [ `uname` == 'Darwin' ]; then [ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \ | sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ] From 3279aefefbd409aae2f1bb954cd67d266240e973 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 15:15:44 -0700 Subject: [PATCH 11/22] Made PEP8 happy --- letsencrypt/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 8bcbd8f02..dccfb9289 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -518,7 +518,7 @@ class HelpfulArgumentParser(object): help2 = self.prescan_for_flag("--help", self.help_topics) assert max(True, "a") == "a", "Gravity changed direction" help_arg = max(help1, help2) - if help_arg == True: + if help_arg is True: # just --help with no topic; avoid argparse altogether print USAGE sys.exit(0) From fa992faf52be93309506ae728eb64340fd388706 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Mon, 28 Sep 2015 15:24:51 -0700 Subject: [PATCH 12/22] Fix pylint and add test --- acme/acme/messages.py | 11 ++++++----- acme/acme/messages_test.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 002c08767..594b3d5c7 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -2,6 +2,7 @@ import collections from acme import challenges +from acme import errors from acme import fields from acme import jose from acme import util @@ -373,17 +374,17 @@ class Authorization(ResourceBody): @challenges.decoder def challenges(value): # pylint: disable=missing-docstring,no-self-argument - # The from_json method raises errors.UnrecognizedTypeError when a + # The from_json method raises errors.UnrecognizedTypeError when a # challenge of unknown type is encountered. We want to ignore this # case. This forces us to do an explicit iteration, since list # comprehensions can't handle exceptions. - challenges = [] + challs = [] for chall in value: try: - challenges.append(ChallengeBody.from_json(chall)) - except errors.UnknownTypeError: + challs.append(ChallengeBody.from_json(chall)) + except jose.UnrecognizedTypeError: continue - return tuple(challenges) + return tuple(challs) @property def resolved_combinations(self): diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index 25f07018c..ac722909c 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -274,6 +274,9 @@ class AuthorizationTest(unittest.TestCase): def setUp(self): from acme.messages import ChallengeBody from acme.messages import STATUS_VALID + + unknown_chall = mock.MagicMock() + unknown_chall.to_json.side_effect = side_effect=jose.UnrecognizedTypeError self.challbs = ( ChallengeBody( uri='http://challb1', status=STATUS_VALID, @@ -300,6 +303,19 @@ class AuthorizationTest(unittest.TestCase): 'combinations': combinations, } + # For unknown challenge types + self.jmsg_unknown_chall = { + 'resource': 'challenge', + 'uri': 'random_uri', + 'type': 'unknown', + 'tls': True, + } + + self.jobj_from_unknown = { + 'identifier': identifier.to_json(), + 'challenges': [self.jmsg_unknown_chall], + } + def test_from_json(self): from acme.messages import Authorization Authorization.from_json(self.jobj_from) @@ -314,6 +330,11 @@ class AuthorizationTest(unittest.TestCase): (self.challbs[1], self.challbs[2]), )) + def test_unknown_chall_type(self): + """Just make sure an error isn't thrown.""" + from acme.messages import Authorization + Authorization.from_json(self.jobj_from_unknown) + class AuthorizationResourceTest(unittest.TestCase): """Tests for acme.messages.AuthorizationResource.""" From 4da0e17255a15d0e9589795410b25c05a6b87cc2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 15:45:31 -0700 Subject: [PATCH 13/22] Added message and changed reporter interface --- letsencrypt/account.py | 4 ++-- letsencrypt/auth_handler.py | 2 +- letsencrypt/cli.py | 11 +++++++++++ letsencrypt/client.py | 2 +- letsencrypt/interfaces.py | 2 +- letsencrypt/reporter.py | 2 +- letsencrypt/tests/reporter_test.py | 6 +++--- 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/letsencrypt/account.py b/letsencrypt/account.py index 8bee22102..c97e4f6fe 100644 --- a/letsencrypt/account.py +++ b/letsencrypt/account.py @@ -92,13 +92,13 @@ def report_new_account(acc, config): "contain certificates and private keys obtained by Let's Encrypt " "so making regular backups of this folder is ideal.".format( config.config_dir), - reporter.MEDIUM_PRIORITY, True) + reporter.MEDIUM_PRIORITY) if acc.regr.body.emails: recovery_msg = ("If you lose your account credentials, you can " "recover through e-mails sent to {0}.".format( ", ".join(acc.regr.body.emails))) - reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY, True) + reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY) class AccountMemoryStorage(interfaces.AccountStorage): diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index 68aed510a..b27a569f6 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -531,7 +531,7 @@ def _report_failed_challs(failed_achalls): reporter = zope.component.getUtility(interfaces.IReporter) for achalls in problems.itervalues(): reporter.add_message( - _generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY, True) + _generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) def _generate_failed_chall_msg(failed_achalls): diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index dccfb9289..bd49d110b 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -267,6 +267,14 @@ def _treat_as_renewal(config, domains): return None +def _report_new_cert(cert_path): + """Reports the creation of a new certificate to the user.""" + reporter_util = zope.component.getUtility(interfaces.IReporter) + reporter_util.add_message("Congratulations! Your certificate has been " + "saved at {0}.".format(cert_path), + reporter.MEDIUM_PRIORITY) + + def _auth_from_domains(le_client, config, domains, plugins): """Authenticate and enroll certificate.""" # Note: This can raise errors... caught above us though. @@ -292,6 +300,8 @@ def _auth_from_domains(le_client, config, domains, plugins): if not lineage: raise errors.Error("Certificate could not be obtained") + _report_new_cert(lineage.cert) + return lineage @@ -365,6 +375,7 @@ def auth(args, config, plugins): file=args.csr[0], data=args.csr[1], form="der")) le_client.save_certificate( certr, chain, args.cert_path, args.chain_path) + _report_new_cert(args.cert_path) else: domains = _find_domains(args, installer) _auth_from_domains(le_client, config, domains, plugins) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index e9decae47..c82131af3 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -286,7 +286,7 @@ class Client(object): "configured in the directories under {0}.").format( cert.cli_config.renewal_configs_dir) reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message(msg, reporter.LOW_PRIORITY, True) + reporter.add_message(msg, reporter.LOW_PRIORITY) def save_certificate(self, certr, chain_cert, cert_path, chain_path): # pylint: disable=no-self-use diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 1ba8afe45..1f51645ab 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -478,7 +478,7 @@ class IReporter(zope.interface.Interface): LOW_PRIORITY = zope.interface.Attribute( "Used to denote low priority messages") - def add_message(self, msg, priority, on_crash=False): + def add_message(self, msg, priority, on_crash=True): """Adds msg to the list of messages to be printed. :param str msg: Message to be displayed to the user. diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 482305838..0905dfa54 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -36,7 +36,7 @@ class Reporter(object): def __init__(self): self.messages = Queue.PriorityQueue() - def add_message(self, msg, priority, on_crash=False): + def add_message(self, msg, priority, on_crash=True): """Adds msg to the list of messages to be printed. :param str msg: Message to be displayed to the user. diff --git a/letsencrypt/tests/reporter_test.py b/letsencrypt/tests/reporter_test.py index c43511208..89bd9dfc7 100644 --- a/letsencrypt/tests/reporter_test.py +++ b/letsencrypt/tests/reporter_test.py @@ -78,13 +78,13 @@ class ReporterTest(unittest.TestCase): output = sys.stdout.getvalue() self.assertTrue("IMPORTANT NOTES:" in output) self.assertTrue("High" in output) - self.assertTrue("Med" not in output) + self.assertTrue("Med" in output) self.assertTrue("Low" not in output) def _add_messages(self): - self.reporter.add_message("High", self.reporter.HIGH_PRIORITY, True) + self.reporter.add_message("High", self.reporter.HIGH_PRIORITY) self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY) - self.reporter.add_message("Low", self.reporter.LOW_PRIORITY) + self.reporter.add_message("Low", self.reporter.LOW_PRIORITY, False) if __name__ == "__main__": From 243c9e9021cd1183742a516aed0a432a9cc65b73 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 15:52:09 -0700 Subject: [PATCH 14/22] Made cover and lint happy --- letsencrypt/cli.py | 2 +- letsencrypt/tests/reporter_test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index bd49d110b..0b7d17909 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -272,7 +272,7 @@ def _report_new_cert(cert_path): reporter_util = zope.component.getUtility(interfaces.IReporter) reporter_util.add_message("Congratulations! Your certificate has been " "saved at {0}.".format(cert_path), - reporter.MEDIUM_PRIORITY) + reporter_util.MEDIUM_PRIORITY) def _auth_from_domains(le_client, config, domains, plugins): diff --git a/letsencrypt/tests/reporter_test.py b/letsencrypt/tests/reporter_test.py index 89bd9dfc7..ddf345c4c 100644 --- a/letsencrypt/tests/reporter_test.py +++ b/letsencrypt/tests/reporter_test.py @@ -78,12 +78,12 @@ class ReporterTest(unittest.TestCase): output = sys.stdout.getvalue() self.assertTrue("IMPORTANT NOTES:" in output) self.assertTrue("High" in output) - self.assertTrue("Med" in output) + self.assertTrue("Med" not in output) self.assertTrue("Low" not in output) def _add_messages(self): self.reporter.add_message("High", self.reporter.HIGH_PRIORITY) - self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY) + self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY, False) self.reporter.add_message("Low", self.reporter.LOW_PRIORITY, False) From 67ec4d09eef289b979f18b869c760cc997ef2f44 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Mon, 28 Sep 2015 15:53:42 -0700 Subject: [PATCH 15/22] Put in dummy challenge --- acme/acme/challenges.py | 5 +++++ acme/acme/messages.py | 5 +++-- acme/acme/messages_test.py | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 81711e605..1ffc6cc99 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -34,6 +34,11 @@ class DVChallenge(Challenge): # pylint: disable=abstract-method """Domain validation challenges.""" +class UnrecognizedChallenge(DVChallenge): + """Unrecognized challenge.""" + typ = "unknown" + + class ChallengeResponse(jose.TypedJSONObjectWithFields): # _fields_to_partial_json | pylint: disable=abstract-method """ACME challenge response.""" diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 594b3d5c7..d6e9952c3 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -2,7 +2,6 @@ import collections from acme import challenges -from acme import errors from acme import fields from acme import jose from acme import util @@ -383,7 +382,9 @@ class Authorization(ResourceBody): try: challs.append(ChallengeBody.from_json(chall)) except jose.UnrecognizedTypeError: - continue + challs.append(ChallengeBody( + uri="UNKNOWN", chall=challenges.UnrecognizedChallenge, + status=STATUS_UNKNOWN)) return tuple(challs) @property diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index ac722909c..d7bbdb0e4 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -275,8 +275,6 @@ class AuthorizationTest(unittest.TestCase): from acme.messages import ChallengeBody from acme.messages import STATUS_VALID - unknown_chall = mock.MagicMock() - unknown_chall.to_json.side_effect = side_effect=jose.UnrecognizedTypeError self.challbs = ( ChallengeBody( uri='http://challb1', status=STATUS_VALID, From 5238f530924de2bf335b958a102b31306cf4a79d Mon Sep 17 00:00:00 2001 From: James Kasten Date: Mon, 28 Sep 2015 16:03:03 -0700 Subject: [PATCH 16/22] DVChallenge -> Challenge --- acme/acme/challenges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 1ffc6cc99..fbb2e7418 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -34,7 +34,7 @@ class DVChallenge(Challenge): # pylint: disable=abstract-method """Domain validation challenges.""" -class UnrecognizedChallenge(DVChallenge): +class UnrecognizedChallenge(Challenge): """Unrecognized challenge.""" typ = "unknown" From ed7977fb039d74455d088a4bb11cbf2eaf91373b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 18:45:12 -0700 Subject: [PATCH 17/22] Added cli tests --- letsencrypt/tests/cli_test.py | 140 ++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 33 deletions(-) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 2e9f3330c..31cef584b 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -16,6 +16,9 @@ from letsencrypt.tests import renewer_test from letsencrypt.tests import test_util +CSR = test_util.vector_path('csr.der') + + class CLITest(unittest.TestCase): """Tests for different commands.""" @@ -65,40 +68,111 @@ class CLITest(unittest.TestCase): for r in xrange(len(flags)))): self._call(['plugins'] + list(args)) - @mock.patch("letsencrypt.cli.sys") + def test_auth_bad_args(self): + ret, _, _, _ = self._call(['-d', 'foo.bar', 'auth', '--csr', CSR]) + self.assertEqual(ret, '--domains and --csr are mutually exclusive') + + ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth']) + self.assertEqual(ret, 'Authenticator could not be determined') + + @mock.patch('letsencrypt.cli.zope.component.getUtility') + def test_auth_new_request_success(self, mock_get_utility): + cert_path = '/etc/letsencrypt/live/foo.bar' + mock_lineage = mock.MagicMock(cert=cert_path) + mock_client = mock.MagicMock() + mock_client.obtain_and_enroll_certificate.return_value = mock_lineage + self._auth_new_request_common(mock_client) + self.assertEqual( + mock_client.obtain_and_enroll_certificate.call_count, 1) + self.assertTrue( + cert_path in mock_get_utility().add_message.call_args[0][0]) + + def test_auth_new_request_failure(self): + mock_client = mock.MagicMock() + mock_client.obtain_and_enroll_certificate.return_value = False + self.assertRaises(errors.Error, + self._auth_new_request_common, mock_client) + + def _auth_new_request_common(self, mock_client): + with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal: + mock_renewal.return_value = None + with mock.patch('letsencrypt.cli._init_le_client') as mock_init: + mock_init.return_value = mock_client + self._call(['-d', 'foo.bar', '-a', + 'standalone', '-i', 'bad', 'auth']) + + @mock.patch('letsencrypt.cli.zope.component.getUtility') + @mock.patch('letsencrypt.cli._treat_as_renewal') + @mock.patch('letsencrypt.cli._init_le_client') + def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility): + cert_path = '/etc/letsencrypt/live/foo.bar' + mock_lineage = mock.MagicMock(cert=cert_path) + mock_cert = mock.MagicMock(body='body') + mock_key = mock.MagicMock(pem='pem_key') + mock_renewal.return_value = mock_lineage + mock_client = mock.MagicMock() + mock_client.obtain_certificate.return_value = (mock_cert, 'chain', + mock_key, 'csr') + mock_init.return_value = mock_client + with mock.patch('letsencrypt.cli.OpenSSL'): + with mock.patch('letsencrypt.cli.crypto_util'): + self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth']) + mock_client.obtain_certificate.assert_called_once_with(['foo.bar']) + self.assertEqual(mock_lineage.save_successor.call_count, 1) + mock_lineage.update_all_links_to.assert_called_once_with( + mock_lineage.latest_common_version()) + self.assertTrue( + cert_path in mock_get_utility().add_message.call_args[0][0]) + + @mock.patch('letsencrypt.cli.zope.component.getUtility') + @mock.patch('letsencrypt.cli._init_le_client') + def test_auth_csr(self, mock_init, mock_get_utility): + cert_path = '/etc/letsencrypt/live/foo.bar' + mock_client = mock.MagicMock() + mock_client.obtain_certificate_from_csr.return_value = ('certr', + 'chain') + mock_init.return_value = mock_client + self._call(['-a', 'standalone', 'auth', '--csr', CSR, + '--cert-path', cert_path, '--chain-path', '/']) + mock_client.save_certificate.assert_called_once_with( + 'certr', 'chain', cert_path, '/') + self.assertTrue( + cert_path in mock_get_utility().add_message.call_args[0][0]) + + @mock.patch('letsencrypt.cli.sys') def test_handle_exception(self, mock_sys): # pylint: disable=protected-access from letsencrypt import cli mock_open = mock.mock_open() - with mock.patch("letsencrypt.cli.open", mock_open, create=True): - exception = Exception("detail") + with mock.patch('letsencrypt.cli.open', mock_open, create=True): + exception = Exception('detail') cli._handle_exception( Exception, exc_value=exception, trace=None, args=None) - mock_open().write.assert_called_once_with("".join( + mock_open().write.assert_called_once_with(''.join( traceback.format_exception_only(Exception, exception))) error_msg = mock_sys.exit.call_args_list[0][0][0] - self.assertTrue("unexpected error" in error_msg) + self.assertTrue('unexpected error' in error_msg) - with mock.patch("letsencrypt.cli.open", mock_open, create=True): + with mock.patch('letsencrypt.cli.open', mock_open, create=True): mock_open.side_effect = [KeyboardInterrupt] - error = errors.Error("detail") + error = errors.Error('detail') cli._handle_exception( errors.Error, exc_value=error, trace=None, args=None) # assert_any_call used because sys.exit doesn't exit in cli.py - mock_sys.exit.assert_any_call("".join( + mock_sys.exit.assert_any_call(''.join( traceback.format_exception_only(errors.Error, error))) args = mock.MagicMock(debug=False) cli._handle_exception( - Exception, exc_value=Exception("detail"), trace=None, args=args) + Exception, exc_value=Exception('detail'), trace=None, args=args) error_msg = mock_sys.exit.call_args_list[-1][0][0] - self.assertTrue("unexpected error" in error_msg) + self.assertTrue('unexpected error' in error_msg) - interrupt = KeyboardInterrupt("detail") + interrupt = KeyboardInterrupt('detail') cli._handle_exception( KeyboardInterrupt, exc_value=interrupt, trace=None, args=None) - mock_sys.exit.assert_called_with("".join( + mock_sys.exit.assert_called_with(''.join( traceback.format_exception_only(KeyboardInterrupt, interrupt))) @@ -108,13 +182,13 @@ class DetermineAccountTest(unittest.TestCase): def setUp(self): self.args = mock.MagicMock(account=None, email=None) self.config = configuration.NamespaceConfig(self.args) - self.accs = [mock.MagicMock(id="x"), mock.MagicMock(id="y")] + self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')] self.account_storage = account.AccountMemoryStorage() def _call(self): # pylint: disable=protected-access from letsencrypt.cli import _determine_account - with mock.patch("letsencrypt.cli.account.AccountFileStorage") as mock_storage: + with mock.patch('letsencrypt.cli.account.AccountFileStorage') as mock_storage: mock_storage.return_value = self.account_storage return _determine_account(self.args, self.config) @@ -131,7 +205,7 @@ class DetermineAccountTest(unittest.TestCase): self.assertEqual(self.accs[0].id, self.args.account) self.assertTrue(self.args.email is None) - @mock.patch("letsencrypt.client.display_ops.choose_account") + @mock.patch('letsencrypt.client.display_ops.choose_account') def test_multiple_accounts(self, mock_choose_accounts): for acc in self.accs: self.account_storage.save(acc) @@ -142,11 +216,11 @@ class DetermineAccountTest(unittest.TestCase): self.assertEqual(self.accs[1].id, self.args.account) self.assertTrue(self.args.email is None) - @mock.patch("letsencrypt.client.display_ops.get_email") + @mock.patch('letsencrypt.client.display_ops.get_email') def test_no_accounts_no_email(self, mock_get_email): - mock_get_email.return_value = "foo@bar.baz" + mock_get_email.return_value = 'foo@bar.baz' - with mock.patch("letsencrypt.cli.client") as client: + with mock.patch('letsencrypt.cli.client') as client: client.register.return_value = ( self.accs[0], mock.sentinel.acme) self.assertEqual((self.accs[0], mock.sentinel.acme), self._call()) @@ -154,15 +228,15 @@ class DetermineAccountTest(unittest.TestCase): self.config, self.account_storage, tos_cb=mock.ANY) self.assertEqual(self.accs[0].id, self.args.account) - self.assertEqual("foo@bar.baz", self.args.email) + self.assertEqual('foo@bar.baz', self.args.email) def test_no_accounts_email(self): - self.args.email = "other email" - with mock.patch("letsencrypt.cli.client") as client: + self.args.email = 'other email' + with mock.patch('letsencrypt.cli.client') as client: client.register.return_value = (self.accs[1], mock.sentinel.acme) self._call() self.assertEqual(self.accs[1].id, self.args.account) - self.assertEqual("other email", self.args.email) + self.assertEqual('other email', self.args.email) class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest): @@ -176,36 +250,36 @@ class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest): def tearDown(self): shutil.rmtree(self.tempdir) - @mock.patch("letsencrypt.le_util.make_or_verify_dir") + @mock.patch('letsencrypt.le_util.make_or_verify_dir') def test_find_duplicative_names(self, unused_makedir): from letsencrypt.cli import _find_duplicative_certs - test_cert = test_util.load_vector("cert-san.pem") - with open(self.test_rc.cert, "w") as f: + test_cert = test_util.load_vector('cert-san.pem') + with open(self.test_rc.cert, 'w') as f: f.write(test_cert) # No overlap at all - result = _find_duplicative_certs(["wow.net", "hooray.org"], + result = _find_duplicative_certs(['wow.net', 'hooray.org'], self.config, self.cli_config) self.assertEqual(result, (None, None)) # Totally identical - result = _find_duplicative_certs(["example.com", "www.example.com"], + result = _find_duplicative_certs(['example.com', 'www.example.com'], self.config, self.cli_config) - self.assertTrue(result[0].configfile.filename.endswith("example.org.conf")) + self.assertTrue(result[0].configfile.filename.endswith('example.org.conf')) self.assertEqual(result[1], None) # Superset - result = _find_duplicative_certs(["example.com", "www.example.com", - "something.new"], self.config, + result = _find_duplicative_certs(['example.com', 'www.example.com', + 'something.new'], self.config, self.cli_config) self.assertEqual(result[0], None) - self.assertTrue(result[1].configfile.filename.endswith("example.org.conf")) + self.assertTrue(result[1].configfile.filename.endswith('example.org.conf')) # Partial overlap doesn't count - result = _find_duplicative_certs(["example.com", "something.new"], + result = _find_duplicative_certs(['example.com', 'something.new'], self.config, self.cli_config) self.assertEqual(result, (None, None)) -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() # pragma: no cover From dc0b26c2781132a1c3f0622c40c93f4e64bf1f53 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 28 Sep 2015 18:47:15 -0700 Subject: [PATCH 18/22] Raised cover percentage --- tox.cover.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.cover.sh b/tox.cover.sh index aa5e3ed88..edfd9b81a 100755 --- a/tox.cover.sh +++ b/tox.cover.sh @@ -16,7 +16,7 @@ fi cover () { if [ "$1" = "letsencrypt" ]; then - min=96 + min=97 elif [ "$1" = "acme" ]; then min=100 elif [ "$1" = "letsencrypt_apache" ]; then From ad1fce03f77feddcbf0ef96d1ff63ed40e44576f Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 29 Sep 2015 06:47:15 +0000 Subject: [PATCH 19/22] UnrecognizedChallenge (fixes #855). Overrides quick fix from #856. --- acme/acme/challenges.py | 37 ++++++++++++++++++++++++++++++++----- acme/acme/messages.py | 14 +------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index fbb2e7418..4731c043f 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -25,6 +25,14 @@ class Challenge(jose.TypedJSONObjectWithFields): """ACME challenge.""" TYPES = {} + @classmethod + def from_json(cls, jobj): + try: + return super(Challenge, cls).from_json(jobj) + except jose.UnrecognizedTypeError as error: + logger.debug(error) + return UnrecognizedChallenge.from_json(jobj) + class ContinuityChallenge(Challenge): # pylint: disable=abstract-method """Client validation challenges.""" @@ -34,11 +42,6 @@ class DVChallenge(Challenge): # pylint: disable=abstract-method """Domain validation challenges.""" -class UnrecognizedChallenge(Challenge): - """Unrecognized challenge.""" - typ = "unknown" - - class ChallengeResponse(jose.TypedJSONObjectWithFields): # _fields_to_partial_json | pylint: disable=abstract-method """ACME challenge response.""" @@ -47,6 +50,30 @@ class ChallengeResponse(jose.TypedJSONObjectWithFields): resource = fields.Resource(resource_type) +class UnrecognizedChallenge(Challenge): + """Unrecognized challenge. + + ACME specification defines a generic framework for challenges and + defines some standard challenges that are implemented in this + module. However, other implementations (including peers) might + define additional challenge types, which should be ignored if + unrecognized. + + :ivar jobj: Original JSON decoded object. + + """ + + def __init__(self, jobj): + object.__setattr__(self, "jobj", jobj) + + def to_partial_json(self): + return self.jobj + + @classmethod + def from_json(cls, jobj): + return cls(jobj) + + @Challenge.register class SimpleHTTP(DVChallenge): """ACME "simpleHttp" challenge. diff --git a/acme/acme/messages.py b/acme/acme/messages.py index d6e9952c3..02ae24c8f 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -373,19 +373,7 @@ class Authorization(ResourceBody): @challenges.decoder def challenges(value): # pylint: disable=missing-docstring,no-self-argument - # The from_json method raises errors.UnrecognizedTypeError when a - # challenge of unknown type is encountered. We want to ignore this - # case. This forces us to do an explicit iteration, since list - # comprehensions can't handle exceptions. - challs = [] - for chall in value: - try: - challs.append(ChallengeBody.from_json(chall)) - except jose.UnrecognizedTypeError: - challs.append(ChallengeBody( - uri="UNKNOWN", chall=challenges.UnrecognizedChallenge, - status=STATUS_UNKNOWN)) - return tuple(challs) + return tuple(ChallengeBody.from_json(chall) for chall in value) @property def resolved_combinations(self): From 0ffef20a20522cf060c8c75f84ad6ab9a77470d2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 29 Sep 2015 07:02:33 +0000 Subject: [PATCH 20/22] UnrecognizedChallenge: fix tests and lint. --- acme/acme/challenges.py | 2 ++ acme/acme/challenges_test.py | 26 ++++++++++++++++++++++++++ acme/acme/messages_test.py | 18 ------------------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 4731c043f..d81e77f83 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -64,9 +64,11 @@ class UnrecognizedChallenge(Challenge): """ def __init__(self, jobj): + super(UnrecognizedChallenge, self).__init__() object.__setattr__(self, "jobj", jobj) def to_partial_json(self): + # pylint: disable=no-member return self.jobj @classmethod diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index c82d95e19..ed44d4c45 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -17,6 +17,32 @@ CERT = test_util.load_cert('cert.pem') KEY = test_util.load_rsa_private_key('rsa512_key.pem') +class ChallengeTest(unittest.TestCase): + + def test_from_json_unrecognized(self): + from acme.challenges import Challenge + from acme.challenges import UnrecognizedChallenge + chall = UnrecognizedChallenge({"type": "foo"}) + # pylint: disable=no-member + self.assertEqual(chall, Challenge.from_json(chall.jobj)) + + +class UnrecognizedChallengeTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import UnrecognizedChallenge + self.jobj = {"type": "foo"} + self.chall = UnrecognizedChallenge(self.jobj) + + def test_to_partial_json(self): + self.assertEqual(self.jobj, self.chall.to_partial_json()) + + def test_from_json(self): + from acme.challenges import UnrecognizedChallenge + self.assertEqual( + self.chall, UnrecognizedChallenge.from_json(self.jobj)) + + class SimpleHTTPTest(unittest.TestCase): def setUp(self): diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index d7bbdb0e4..d2d859bc5 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -301,19 +301,6 @@ class AuthorizationTest(unittest.TestCase): 'combinations': combinations, } - # For unknown challenge types - self.jmsg_unknown_chall = { - 'resource': 'challenge', - 'uri': 'random_uri', - 'type': 'unknown', - 'tls': True, - } - - self.jobj_from_unknown = { - 'identifier': identifier.to_json(), - 'challenges': [self.jmsg_unknown_chall], - } - def test_from_json(self): from acme.messages import Authorization Authorization.from_json(self.jobj_from) @@ -328,11 +315,6 @@ class AuthorizationTest(unittest.TestCase): (self.challbs[1], self.challbs[2]), )) - def test_unknown_chall_type(self): - """Just make sure an error isn't thrown.""" - from acme.messages import Authorization - Authorization.from_json(self.jobj_from_unknown) - class AuthorizationResourceTest(unittest.TestCase): """Tests for acme.messages.AuthorizationResource.""" From dcd274ed93182caaf225e33d8efbb50666bb49fa Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 29 Sep 2015 11:06:02 -0700 Subject: [PATCH 21/22] Marked Nginx as Alpha --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 2899e1f76..3f6d6f327 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -56,7 +56,7 @@ class NginxConfigurator(common.Plugin): zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller) zope.interface.classProvides(interfaces.IPluginFactory) - description = "Nginx Web Server" + description = "Nginx Web Server - Alpha" @classmethod def add_parser_arguments(cls, add): From 312057b1b817254914256972dc326af3dbdece48 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 29 Sep 2015 12:54:52 -0700 Subject: [PATCH 22/22] changes += kuba_feedback --- letsencrypt/tests/cli_test.py | 13 ++++++++----- letsencrypt/tests/reporter_test.py | 6 ++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 31cef584b..a59bc414e 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -98,8 +98,7 @@ class CLITest(unittest.TestCase): mock_renewal.return_value = None with mock.patch('letsencrypt.cli._init_le_client') as mock_init: mock_init.return_value = mock_client - self._call(['-d', 'foo.bar', '-a', - 'standalone', '-i', 'bad', 'auth']) + self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth']) @mock.patch('letsencrypt.cli.zope.component.getUtility') @mock.patch('letsencrypt.cli._treat_as_renewal') @@ -124,16 +123,20 @@ class CLITest(unittest.TestCase): self.assertTrue( cert_path in mock_get_utility().add_message.call_args[0][0]) + @mock.patch('letsencrypt.cli.display_ops.pick_installer') @mock.patch('letsencrypt.cli.zope.component.getUtility') @mock.patch('letsencrypt.cli._init_le_client') - def test_auth_csr(self, mock_init, mock_get_utility): + def test_auth_csr(self, mock_init, mock_get_utility, mock_pick_installer): cert_path = '/etc/letsencrypt/live/foo.bar' mock_client = mock.MagicMock() mock_client.obtain_certificate_from_csr.return_value = ('certr', 'chain') mock_init.return_value = mock_client - self._call(['-a', 'standalone', 'auth', '--csr', CSR, - '--cert-path', cert_path, '--chain-path', '/']) + installer = 'installer' + self._call( + ['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR, + '--cert-path', cert_path, '--chain-path', '/']) + self.assertEqual(mock_pick_installer.call_args[0][1], installer) mock_client.save_certificate.assert_called_once_with( 'certr', 'chain', cert_path, '/') self.assertTrue( diff --git a/letsencrypt/tests/reporter_test.py b/letsencrypt/tests/reporter_test.py index ddf345c4c..c848b1cab 100644 --- a/letsencrypt/tests/reporter_test.py +++ b/letsencrypt/tests/reporter_test.py @@ -83,8 +83,10 @@ class ReporterTest(unittest.TestCase): def _add_messages(self): self.reporter.add_message("High", self.reporter.HIGH_PRIORITY) - self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY, False) - self.reporter.add_message("Low", self.reporter.LOW_PRIORITY, False) + self.reporter.add_message( + "Med", self.reporter.MEDIUM_PRIORITY, on_crash=False) + self.reporter.add_message( + "Low", self.reporter.LOW_PRIORITY, on_crash=False) if __name__ == "__main__":