mirror of
https://github.com/certbot/certbot.git
synced 2026-01-21 19:01:07 +03:00
Merge pull request #1395 from sagi/hsts
apache: add general http-header enhacement [needs revision]
This commit is contained in:
@@ -122,7 +122,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
self.parser = None
|
||||
self.version = version
|
||||
self.vhosts = None
|
||||
self._enhance_func = {"redirect": self._enable_redirect}
|
||||
self._enhance_func = {"redirect": self._enable_redirect,
|
||||
"ensure-http-header": self._set_http_header}
|
||||
|
||||
@property
|
||||
def mod_ssl_conf(self):
|
||||
@@ -739,7 +740,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
############################################################################
|
||||
def supported_enhancements(self): # pylint: disable=no-self-use
|
||||
"""Returns currently supported enhancements."""
|
||||
return ["redirect"]
|
||||
return ["redirect", "ensure-http-header"]
|
||||
|
||||
def enhance(self, domain, enhancement, options=None):
|
||||
"""Enhance configuration.
|
||||
@@ -766,6 +767,73 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
logger.warn("Failed %s for %s", enhancement, domain)
|
||||
raise
|
||||
|
||||
def _set_http_header(self, ssl_vhost, header_substring):
|
||||
"""Enables header that is identified by header_substring on ssl_vhost.
|
||||
|
||||
If the header identified by header_substring is not already set,
|
||||
a new Header directive is placed in ssl_vhost's configuration with
|
||||
arguments from: constants.HTTP_HEADER[header_substring]
|
||||
|
||||
.. note:: This function saves the configuration
|
||||
|
||||
:param ssl_vhost: Destination of traffic, an ssl enabled vhost
|
||||
:type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost`
|
||||
|
||||
:param header_substring: string that uniquely identifies a header.
|
||||
e.g: Strict-Transport-Security, Upgrade-Insecure-Requests.
|
||||
:type str
|
||||
|
||||
:returns: Success, general_vhost (HTTP vhost)
|
||||
:rtype: (bool, :class:`~letsencrypt_apache.obj.VirtualHost`)
|
||||
|
||||
:raises .errors.PluginError: If no viable HTTP host can be created or
|
||||
set with header header_substring.
|
||||
|
||||
"""
|
||||
if "headers_module" not in self.parser.modules:
|
||||
self.enable_mod("headers")
|
||||
|
||||
# Check if selected header is already set
|
||||
self._verify_no_matching_http_header(ssl_vhost, header_substring)
|
||||
|
||||
# Add directives to server
|
||||
self.parser.add_dir(ssl_vhost.path, "Header",
|
||||
constants.HEADER_ARGS[header_substring])
|
||||
|
||||
self.save_notes += ("Adding %s header to ssl vhost in %s\n" %
|
||||
(header_substring, ssl_vhost.filep))
|
||||
|
||||
self.save()
|
||||
logger.info("Adding %s header to ssl vhost in %s", header_substring,
|
||||
ssl_vhost.filep)
|
||||
|
||||
def _verify_no_matching_http_header(self, ssl_vhost, header_substring):
|
||||
"""Checks to see if an there is an existing Header directive that
|
||||
contains the string header_substring.
|
||||
|
||||
:param ssl_vhost: vhost to check
|
||||
:type vhost: :class:`~letsencrypt_apache.obj.VirtualHost`
|
||||
|
||||
:param header_substring: string that uniquely identifies a header.
|
||||
e.g: Strict-Transport-Security, Upgrade-Insecure-Requests.
|
||||
:type str
|
||||
|
||||
:returns: boolean
|
||||
:rtype: (bool)
|
||||
|
||||
:raises errors.PluginEnhancementAlreadyPresent When header
|
||||
header_substring exists
|
||||
|
||||
"""
|
||||
header_path = self.parser.find_dir("Header", None, start=ssl_vhost.path)
|
||||
if header_path:
|
||||
# "Existing Header directive for virtualhost"
|
||||
pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower())
|
||||
for match in header_path:
|
||||
if re.search(pat, self.aug.get(match).lower()):
|
||||
raise errors.PluginEnhancementAlreadyPresent(
|
||||
"Existing %s header" % (header_substring))
|
||||
|
||||
def _enable_redirect(self, ssl_vhost, unused_options):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
||||
@@ -835,8 +903,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
:param vhost: vhost to check
|
||||
:type vhost: :class:`~letsencrypt_apache.obj.VirtualHost`
|
||||
|
||||
:raises errors.PluginError: When another redirection exists
|
||||
:raises errors.PluginEnhancementAlreadyPresent: When the exact
|
||||
letsencrypt redirection WriteRule exists in virtual host.
|
||||
|
||||
errors.PluginError: When there exists directives that may hint
|
||||
other redirection. (TODO: We should not throw a PluginError,
|
||||
but that's for an other PR.)
|
||||
"""
|
||||
rewrite_path = self.parser.find_dir(
|
||||
"RewriteRule", None, start=vhost.path)
|
||||
@@ -853,7 +925,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
rewrite_path, constants.REWRITE_HTTPS_ARGS):
|
||||
if self.aug.get(match) != arg:
|
||||
raise errors.PluginError("Unknown Existing RewriteRule")
|
||||
raise errors.PluginError(
|
||||
|
||||
raise errors.PluginEnhancementAlreadyPresent(
|
||||
"Let's Encrypt has already enabled redirection")
|
||||
|
||||
def _create_redirect_vhost(self, ssl_vhost):
|
||||
|
||||
@@ -27,3 +27,15 @@ AUGEAS_LENS_DIR = pkg_resources.resource_filename(
|
||||
REWRITE_HTTPS_ARGS = [
|
||||
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"]
|
||||
"""Apache rewrite rule arguments used for redirections to https vhost"""
|
||||
|
||||
|
||||
HSTS_ARGS = ["always", "set", "Strict-Transport-Security",
|
||||
"\"max-age=31536000; includeSubDomains\""]
|
||||
"""Apache header arguments for HSTS"""
|
||||
|
||||
UIR_ARGS = ["always", "set", "Content-Security-Policy",
|
||||
"upgrade-insecure-requests"]
|
||||
|
||||
HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
|
||||
"Upgrade-Insecure-Requests": UIR_ARGS}
|
||||
|
||||
|
||||
@@ -630,6 +630,84 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
errors.PluginError,
|
||||
self.config.enhance, "letsencrypt.demo", "unknown_enhancement")
|
||||
|
||||
@mock.patch("letsencrypt.le_util.run_script")
|
||||
@mock.patch("letsencrypt.le_util.exe_exists")
|
||||
def test_http_header_hsts(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for letsencrypt.demo
|
||||
self.config.enhance("letsencrypt.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
self.assertTrue("headers_module" in self.config.parser.modules)
|
||||
|
||||
# Get the ssl vhost for letsencrypt.demo
|
||||
ssl_vhost = self.config.assoc["letsencrypt.demo"]
|
||||
|
||||
# These are not immediately available in find_dir even with save() and
|
||||
# load(). They must be found in sites-available
|
||||
hsts_header = self.config.parser.find_dir(
|
||||
"Header", None, ssl_vhost.path)
|
||||
|
||||
# four args to HSTS header
|
||||
self.assertEqual(len(hsts_header), 4)
|
||||
|
||||
def test_http_header_hsts_twice(self):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
|
||||
# This will create an ssl vhost for letsencrypt.demo
|
||||
self.config.enhance("encryption-example.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginEnhancementAlreadyPresent,
|
||||
self.config.enhance, "encryption-example.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
@mock.patch("letsencrypt.le_util.run_script")
|
||||
@mock.patch("letsencrypt.le_util.exe_exists")
|
||||
def test_http_header_uir(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for letsencrypt.demo
|
||||
self.config.enhance("letsencrypt.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
self.assertTrue("headers_module" in self.config.parser.modules)
|
||||
|
||||
# Get the ssl vhost for letsencrypt.demo
|
||||
ssl_vhost = self.config.assoc["letsencrypt.demo"]
|
||||
|
||||
# These are not immediately available in find_dir even with save() and
|
||||
# load(). They must be found in sites-available
|
||||
uir_header = self.config.parser.find_dir(
|
||||
"Header", None, ssl_vhost.path)
|
||||
|
||||
# four args to HSTS header
|
||||
self.assertEqual(len(uir_header), 4)
|
||||
|
||||
def test_http_header_uir_twice(self):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
|
||||
# This will create an ssl vhost for letsencrypt.demo
|
||||
self.config.enhance("encryption-example.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginEnhancementAlreadyPresent,
|
||||
self.config.enhance, "encryption-example.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
|
||||
|
||||
@mock.patch("letsencrypt.le_util.run_script")
|
||||
@mock.patch("letsencrypt.le_util.exe_exists")
|
||||
def test_redirect_well_formed_http(self, mock_exe, _):
|
||||
@@ -670,7 +748,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.enhance("encryption-example.demo", "redirect")
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
errors.PluginEnhancementAlreadyPresent,
|
||||
self.config.enhance, "encryption-example.demo", "redirect")
|
||||
|
||||
def test_unknown_rewrite(self):
|
||||
|
||||
@@ -463,7 +463,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo
|
||||
domains, lineage.privkey, lineage.cert,
|
||||
lineage.chain, lineage.fullchain)
|
||||
|
||||
le_client.enhance_config(domains, args.redirect)
|
||||
le_client.enhance_config(domains, config)
|
||||
|
||||
if len(lineage.available_versions("cert")) == 1:
|
||||
display_ops.success_installation(domains)
|
||||
@@ -517,7 +517,7 @@ def install(args, config, plugins):
|
||||
le_client.deploy_certificate(
|
||||
domains, args.key_path, args.cert_path, args.chain_path,
|
||||
args.fullchain_path)
|
||||
le_client.enhance_config(domains, args.redirect)
|
||||
le_client.enhance_config(domains, config)
|
||||
|
||||
|
||||
def revoke(args, config, unused_plugins): # TODO: coop with renewal config
|
||||
@@ -923,6 +923,25 @@ def prepare_and_parse_args(plugins, args):
|
||||
"security", "--no-redirect", action="store_false",
|
||||
help="Do not automatically redirect all HTTP traffic to HTTPS for the newly "
|
||||
"authenticated vhost.", dest="redirect", default=None)
|
||||
helpful.add(
|
||||
"security", "--hsts", action="store_true",
|
||||
help="Add the Strict-Transport-Security header to every HTTP response."
|
||||
" Forcing browser to use always use SSL for the domain."
|
||||
" Defends against SSL Stripping.", dest="hsts", default=False)
|
||||
helpful.add(
|
||||
"security", "--no-hsts", action="store_false",
|
||||
help="Do not automatically add the Strict-Transport-Security header"
|
||||
" to every HTTP response.", dest="hsts", default=False)
|
||||
helpful.add(
|
||||
"security", "--uir", action="store_true",
|
||||
help="Add the \"Content-Security-Policy: upgrade-insecure-requests\""
|
||||
" header to every HTTP response. Forcing the browser to use"
|
||||
" https:// for every http:// resource.", dest="uir", default=None)
|
||||
helpful.add(
|
||||
"security", "--no-uir", action="store_false",
|
||||
help=" Do not automatically set the \"Content-Security-Policy:"
|
||||
" upgrade-insecure-requests\" header to every HTTP response.",
|
||||
dest="uir", default=None)
|
||||
helpful.add(
|
||||
"security", "--strict-permissions", action="store_true",
|
||||
help="Require that all configuration files are owned by the current "
|
||||
|
||||
@@ -383,57 +383,86 @@ class Client(object):
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
|
||||
# sites may have been enabled / final cleanup
|
||||
self.installer.restart()
|
||||
|
||||
def enhance_config(self, domains, redirect=None):
|
||||
def enhance_config(self, domains, config):
|
||||
"""Enhance the configuration.
|
||||
|
||||
.. todo:: This needs to handle the specific enhancements offered by the
|
||||
installer. We will also have to find a method to pass in the chosen
|
||||
values efficiently.
|
||||
|
||||
:param list domains: list of domains to configure
|
||||
|
||||
:param redirect: If traffic should be forwarded from HTTP to HTTPS.
|
||||
:type redirect: bool or None
|
||||
:ivar config: Namespace typically produced by
|
||||
:meth:`argparse.ArgumentParser.parse_args`.
|
||||
it must have the redirect, hsts and uir attributes.
|
||||
:type namespace: :class:`argparse.Namespace`
|
||||
|
||||
:raises .errors.Error: if no installer is specified in the
|
||||
client.
|
||||
|
||||
"""
|
||||
|
||||
if self.installer is None:
|
||||
logger.warning("No installer is specified, there isn't any "
|
||||
"configuration to enhance.")
|
||||
raise errors.Error("No installer available")
|
||||
|
||||
if config is None:
|
||||
logger.warning("No config is specified.")
|
||||
raise errors.Error("No config available")
|
||||
|
||||
redirect = config.redirect
|
||||
hsts = config.hsts
|
||||
uir = config.uir # Upgrade Insecure Requests
|
||||
|
||||
if redirect is None:
|
||||
redirect = enhancements.ask("redirect")
|
||||
|
||||
# When support for more enhancements are added, the call to the
|
||||
# plugin's `enhance` function should be wrapped by an ErrorHandler
|
||||
if redirect:
|
||||
self.redirect_to_ssl(domains)
|
||||
self.apply_enhancement(domains, "redirect")
|
||||
|
||||
def redirect_to_ssl(self, domains):
|
||||
"""Redirect all traffic from HTTP to HTTPS
|
||||
if hsts:
|
||||
self.apply_enhancement(domains, "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
if uir:
|
||||
self.apply_enhancement(domains, "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
msg = ("We were unable to restart web server")
|
||||
if redirect or hsts or uir:
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
|
||||
self.installer.restart()
|
||||
|
||||
def apply_enhancement(self, domains, enhancement, options=None):
|
||||
"""Applies an enhacement on all domains.
|
||||
|
||||
:param domains: list of ssl_vhosts
|
||||
:type list of str
|
||||
|
||||
:param enhancement: name of enhancement, e.g. ensure-http-header
|
||||
:type str
|
||||
|
||||
.. note:: when more options are need make options a list.
|
||||
:param options: options to enhancement, e.g. Strict-Transport-Security
|
||||
:type str
|
||||
|
||||
:raises .errors.PluginError: If Enhancement is not supported, or if
|
||||
there is any other problem with the enhancement.
|
||||
|
||||
:param vhost: list of ssl_vhosts
|
||||
:type vhost: :class:`letsencrypt.interfaces.IInstaller`
|
||||
|
||||
"""
|
||||
msg = ("We were unable to set up a redirect for your server, "
|
||||
"however, we successfully installed your certificate.")
|
||||
msg = ("We were unable to set up enhancement %s for your server, "
|
||||
"however, we successfully installed your certificate."
|
||||
% (enhancement))
|
||||
with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
|
||||
for dom in domains:
|
||||
try:
|
||||
self.installer.enhance(dom, "redirect")
|
||||
self.installer.enhance(dom, enhancement, options)
|
||||
except errors.PluginEnhancementAlreadyPresent:
|
||||
logger.warn("Enhancement %s was already set.",
|
||||
enhancement)
|
||||
except errors.PluginError:
|
||||
logger.warn("Unable to perform redirect for %s", dom)
|
||||
logger.warn("Unable to set enhancement %s for %s",
|
||||
enhancement, dom)
|
||||
raise
|
||||
|
||||
self.installer.save("Add Redirects")
|
||||
|
||||
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
|
||||
self.installer.restart()
|
||||
self.installer.save("Add enhancement %s" % (enhancement))
|
||||
|
||||
def _recovery_routine_with_msg(self, success_msg):
|
||||
"""Calls the installer's recovery routine and prints success_msg
|
||||
|
||||
@@ -66,6 +66,10 @@ class PluginError(Error):
|
||||
"""Let's Encrypt Plugin error."""
|
||||
|
||||
|
||||
class PluginEnhancementAlreadyPresent(Error):
|
||||
""" Enhancement was already set """
|
||||
|
||||
|
||||
class PluginSelectionError(Error):
|
||||
"""A problem with plugin/configurator selection or setup"""
|
||||
|
||||
|
||||
@@ -20,6 +20,15 @@ KEY = test_util.load_vector("rsa512_key.pem")
|
||||
CSR_SAN = test_util.load_vector("csr-san.der")
|
||||
|
||||
|
||||
class ConfigHelper(object):
|
||||
"""Creates a dummy object to imitate a namespace object
|
||||
|
||||
Example: cfg = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
will result in: cfg.redirect=True, cfg.hsts=False, etc.
|
||||
"""
|
||||
def __init__(self, **kwds):
|
||||
self.__dict__.update(kwds)
|
||||
|
||||
class RegisterTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.client.register."""
|
||||
|
||||
@@ -224,21 +233,50 @@ class ClientTest(unittest.TestCase):
|
||||
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config(self, mock_enhancements):
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
self.assertRaises(errors.Error,
|
||||
self.client.enhance_config, ["foo.bar"])
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
|
||||
self.client.enhance_config(["foo.bar"])
|
||||
installer.enhance.assert_called_once_with("foo.bar", "redirect")
|
||||
self.client.enhance_config(["foo.bar"], config)
|
||||
installer.enhance.assert_called_once_with("foo.bar", "redirect", None)
|
||||
self.assertEqual(installer.save.call_count, 1)
|
||||
installer.restart.assert_called_once_with()
|
||||
|
||||
def test_enhance_config_no_installer(self):
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
def test_enhance_config_no_ask(self, mock_enhancements):
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
self.assertRaises(errors.Error,
|
||||
self.client.enhance_config, ["foo.bar"])
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
|
||||
mock_enhancements.ask.return_value = True
|
||||
installer = mock.MagicMock()
|
||||
self.client.installer = installer
|
||||
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
self.client.enhance_config(["foo.bar"], config)
|
||||
installer.enhance.assert_called_with("foo.bar", "redirect", None)
|
||||
|
||||
config = ConfigHelper(redirect=False, hsts=True, uir=False)
|
||||
self.client.enhance_config(["foo.bar"], config)
|
||||
installer.enhance.assert_called_with("foo.bar", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
config = ConfigHelper(redirect=False, hsts=False, uir=True)
|
||||
self.client.enhance_config(["foo.bar"], config)
|
||||
installer.enhance.assert_called_with("foo.bar", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
self.assertEqual(installer.save.call_count, 3)
|
||||
self.assertEqual(installer.restart.call_count, 3)
|
||||
|
||||
def test_enhance_config_no_installer(self):
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
self.assertRaises(errors.Error,
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
@mock.patch("letsencrypt.client.enhancements")
|
||||
@@ -249,8 +287,10 @@ class ClientTest(unittest.TestCase):
|
||||
self.client.installer = installer
|
||||
installer.enhance.side_effect = errors.PluginError
|
||||
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
|
||||
@@ -263,8 +303,10 @@ class ClientTest(unittest.TestCase):
|
||||
self.client.installer = installer
|
||||
installer.save.side_effect = errors.PluginError
|
||||
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
installer.recovery_routine.assert_called_once_with()
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
|
||||
@@ -277,8 +319,11 @@ class ClientTest(unittest.TestCase):
|
||||
self.client.installer = installer
|
||||
installer.restart.side_effect = [errors.PluginError, None]
|
||||
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 2)
|
||||
@@ -293,8 +338,10 @@ class ClientTest(unittest.TestCase):
|
||||
installer.restart.side_effect = errors.PluginError
|
||||
installer.rollback_checkpoints.side_effect = errors.ReverterError
|
||||
|
||||
config = ConfigHelper(redirect=True, hsts=False, uir=False)
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.client.enhance_config, ["foo.bar"], True)
|
||||
self.client.enhance_config, ["foo.bar"], config)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
installer.rollback_checkpoints.assert_called_once_with()
|
||||
self.assertEqual(installer.restart.call_count, 1)
|
||||
|
||||
Reference in New Issue
Block a user