mirror of
https://github.com/certbot/certbot.git
synced 2026-07-31 18:45:40 +02:00
Merge branch 'simplefs'
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
:mod:`letsencrypt.plugins.webroot`
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: letsencrypt.plugins.webroot
|
||||
:members:
|
||||
@@ -46,6 +46,10 @@ 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)."""
|
||||
|
||||
@@ -51,6 +51,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)
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Webroot plugin."""
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
||||
import zope.interface
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import interfaces
|
||||
from letsencrypt.plugins import common
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Authenticator(common.Plugin):
|
||||
"""Webroot Authenticator."""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Webroot Authenticator"
|
||||
|
||||
MORE_INFO = """\
|
||||
Authenticator plugin that performs SimpleHTTP challenge by saving
|
||||
necessary validation resources to appropriate paths on the file
|
||||
system. It expects that there is some other HTTP server configured
|
||||
to serve all files under specified web root ({0})."""
|
||||
|
||||
def more_info(self): # pylint: disable=missing-docstring,no-self-use
|
||||
return self.MORE_INFO.format(self.conf("path"))
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
add("path", help="public_html / webroot path")
|
||||
|
||||
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
|
||||
path = self.conf("path")
|
||||
if path is None:
|
||||
raise errors.PluginError("--{0} must be set".format(
|
||||
self.option_name("path")))
|
||||
if not os.path.isdir(path):
|
||||
raise errors.PluginError(
|
||||
path + " does not exist or is not a directory")
|
||||
self.full_root = os.path.join(
|
||||
path, challenges.SimpleHTTPResponse.URI_ROOT_PATH)
|
||||
|
||||
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 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):
|
||||
return os.path.join(self.full_root, achall.chall.encode("token"))
|
||||
|
||||
def _perform_single(self, achall):
|
||||
response, validation = achall.gen_response_and_validation(
|
||||
tls=(not self.config.no_simple_http_tls))
|
||||
path = self._path_for_achall(achall)
|
||||
logger.debug("Attempting to save validation to %s", path)
|
||||
with open(path, "w") as validation_file:
|
||||
validation_file.write(validation.json_dumps())
|
||||
return response
|
||||
|
||||
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
||||
for achall in achalls:
|
||||
path = self._path_for_achall(achall)
|
||||
logger.debug("Removing %s", path)
|
||||
os.remove(path)
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Tests for letsencrypt.plugins.webroot."""
|
||||
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.webroot.Authenticator."""
|
||||
|
||||
achall = achallenges.SimpleHTTP(
|
||||
challb=acme_util.SIMPLE_HTTP_P, domain=None, account_key=KEY)
|
||||
|
||||
def setUp(self):
|
||||
from letsencrypt.plugins.webroot import Authenticator
|
||||
self.path = tempfile.mkdtemp()
|
||||
self.validation_path = os.path.join(
|
||||
self.path, ".well-known", "acme-challenge",
|
||||
"ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ")
|
||||
self.config = mock.MagicMock(webroot_path=self.path)
|
||||
self.auth = Authenticator(self.config, "webroot")
|
||||
self.auth.prepare()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.path)
|
||||
|
||||
def test_more_info(self):
|
||||
more_info = self.auth.more_info()
|
||||
self.assertTrue(isinstance(more_info, str))
|
||||
self.assertTrue(self.path 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.webroot_path = os.path.join(self.path, "null")
|
||||
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||
|
||||
def test_prepare_missing_root(self):
|
||||
self.config.webroot_path = 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_path = os.path.join(self.path, "null")
|
||||
os.chmod(self.path, 0o000)
|
||||
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||
os.chmod(self.path, 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
|
||||
Reference in New Issue
Block a user