diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py index 3ec1f1f7c..8c4d618b8 100644 --- a/letsencrypt/plugins/common.py +++ b/letsencrypt/plugins/common.py @@ -44,15 +44,15 @@ class Plugin(object): """ArgumentParser options namespace (prefix of all options).""" return option_namespace(self.name) + def option_name(self, name): + """Option name (include plugin namespace).""" + return self.option_namespace + name + @property def dest_namespace(self): """ArgumentParser dest namespace (prefix of all destinations).""" return dest_namespace(self.name) - def option_name(self, name): - """Option name (include plugin namespace).""" - return self.option_namespace + name - def dest(self, var): """Find a destination for given variable ``var``.""" # this should do exactly the same what ArgumentParser(arg), diff --git a/letsencrypt/plugins/common_test.py b/letsencrypt/plugins/common_test.py index fa761839c..9c6df8c9e 100644 --- a/letsencrypt/plugins/common_test.py +++ b/letsencrypt/plugins/common_test.py @@ -50,6 +50,9 @@ class PluginTest(unittest.TestCase): def test_option_namespace(self): self.assertEqual("mock-", self.plugin.option_namespace) + def test_option_name(self): + self.assertEqual("mock-foo_bar", self.plugin.option_name("foo_bar")) + def test_dest_namespace(self): self.assertEqual("mock_", self.plugin.dest_namespace) diff --git a/letsencrypt/plugins/simplefs.py b/letsencrypt/plugins/simplefs.py index 67e59983e..ad83c13d7 100644 --- a/letsencrypt/plugins/simplefs.py +++ b/letsencrypt/plugins/simplefs.py @@ -35,32 +35,37 @@ to serve all files under specified web root ({0}).""" def add_parser_arguments(cls, add): add("root", help="public_html / webroot path") - def get_chall_pref(self, domain): + def get_chall_pref(self, domain): # pragma: no cover # pylint: disable=missing-docstring,no-self-use,unused-argument return [challenges.SimpleHTTP] def __init__(self, *args, **kwargs): super(Authenticator, self).__init__(*args, **kwargs) + self.full_root = None + def prepare(self): # pylint: disable=missing-docstring root = self.conf("root") if root is None: - raise errors.Error("--{0} must be set".format( + raise errors.PluginError("--{0} must be set".format( self.option_name("root"))) if not os.path.isdir(root): - raise errors.Error(root + " does not exist or is not a directory") + raise errors.PluginError( + root + " does not exist or is not a directory") self.full_root = os.path.join( root, challenges.SimpleHTTPResponse.URI_ROOT_PATH) - def prepare(self): # pylint: disable=missing-docstring logger.debug("Creating root challenges validation dir at %s", self.full_root) try: os.makedirs(self.full_root) except OSError as exception: if exception.errno != errno.EEXIST: - raise + raise errors.PluginError( + "Couldn't create root for SimpleHTTP " + "challenge responses: {0}", exception) def perform(self, achalls): # pylint: disable=missing-docstring + assert self.full_root is not None return [self._perform_single(achall) for achall in achalls] def _path_for_achall(self, achall): diff --git a/letsencrypt/plugins/simplefs_test.py b/letsencrypt/plugins/simplefs_test.py new file mode 100644 index 000000000..f80e8b29a --- /dev/null +++ b/letsencrypt/plugins/simplefs_test.py @@ -0,0 +1,82 @@ +"""Tests for letsencrypt.plugins.simplefs.""" +import os +import shutil +import tempfile +import unittest + +import mock + +from acme import jose + +from letsencrypt import achallenges +from letsencrypt import errors + +from letsencrypt.tests import acme_util +from letsencrypt.tests import test_util + + +KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + + +class AuthenticatorTest(unittest.TestCase): + """Tests for letsencrypt.plugins.simplefs.Authenticator.""" + + achall = achallenges.SimpleHTTP( + challb=acme_util.SIMPLE_HTTP_P, domain=None, account_key=KEY) + + def setUp(self): + from letsencrypt.plugins.simplefs import Authenticator + self.root = tempfile.mkdtemp() + self.validation_path = os.path.join( + self.root, ".well-known", "acme-challenge", + "ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ") + self.config = mock.MagicMock(simplefs_root=self.root) + self.auth = Authenticator(self.config, "simplefs") + self.auth.prepare() + + def tearDown(self): + shutil.rmtree(self.root) + + def test_more_info(self): + more_info = self.auth.more_info() + self.assertTrue(isinstance(more_info, str)) + self.assertTrue(self.root in more_info) + + def test_add_parser_arguments(self): + add = mock.MagicMock() + self.auth.add_parser_arguments(add) + self.assertEqual(1, add.call_count) + + def test_prepare_bad_root(self): + self.config.simplefs_root = os.path.join(self.root, "null") + self.assertRaises(errors.PluginError, self.auth.prepare) + + def test_prepare_missing_root(self): + self.config.simplefs_root = None + self.assertRaises(errors.PluginError, self.auth.prepare) + + def test_prepare_full_root_exists(self): + # prepare() has already been called once in setUp() + self.auth.prepare() # shouldn't raise any exceptions + + def test_prepare_reraises_other_errors(self): + self.auth.full_root = os.path.join(self.root, "null") + os.chmod(self.root, 0o000) + self.assertRaises(errors.PluginError, self.auth.prepare) + os.chmod(self.root, 0o700) + + def test_perform_cleanup(self): + responses = self.auth.perform([self.achall]) + self.assertEqual(1, len(responses)) + self.assertTrue(os.path.exists(self.validation_path)) + with open(self.validation_path) as validation_f: + validation = jose.JWS.json_loads(validation_f.read()) + self.assertTrue(responses[0].check_validation( + validation, self.achall.chall, KEY.public_key())) + + self.auth.cleanup([self.achall]) + self.assertFalse(os.path.exists(self.validation_path)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover