1# pylint: disable=too-many-lines
2"""Test for certbot_apache._internal.configurator."""
3import copy
4import shutil
5import socket
6import tempfile
7import unittest
8
9try:
10    import mock
11except ImportError: # pragma: no cover
12    from unittest import mock # type: ignore
13
14from acme import challenges
15from certbot import achallenges
16from certbot import crypto_util
17from certbot import errors
18from certbot.compat import filesystem
19from certbot.compat import os
20from certbot.tests import acme_util
21from certbot.tests import util as certbot_util
22from certbot_apache._internal import apache_util
23from certbot_apache._internal import constants
24from certbot_apache._internal import obj
25from certbot_apache._internal import parser
26import util
27
28
29class MultipleVhostsTest(util.ApacheTest):
30    """Test two standard well-configured HTTP vhosts."""
31
32    def setUp(self):  # pylint: disable=arguments-differ
33        super().setUp()
34
35        self.config = util.get_apache_configurator(
36            self.config_path, self.vhost_path, self.config_dir, self.work_dir)
37        self.config = self.mock_deploy_cert(self.config)
38        self.vh_truth = util.get_vh_truth(
39            self.temp_dir, "debian_apache_2_4/multiple_vhosts")
40
41    def mock_deploy_cert(self, config):
42        """A test for a mock deploy cert"""
43        config.real_deploy_cert = self.config.deploy_cert
44
45        def mocked_deploy_cert(*args, **kwargs):
46            """a helper to mock a deployed cert"""
47            g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod"
48            with mock.patch(g_mod):
49                config.real_deploy_cert(*args, **kwargs)
50        self.config.deploy_cert = mocked_deploy_cert
51        return self.config
52
53    @mock.patch("certbot_apache._internal.configurator.path_surgery")
54    def test_prepare_no_install(self, mock_surgery):
55        silly_path = {"PATH": "/tmp/nothingness2342"}
56        mock_surgery.return_value = False
57        with mock.patch.dict('os.environ', silly_path):
58            self.assertRaises(errors.NoInstallationError, self.config.prepare)
59            self.assertEqual(mock_surgery.call_count, 1)
60
61    @mock.patch("certbot_apache._internal.parser.ApacheParser")
62    @mock.patch("certbot_apache._internal.configurator.util.exe_exists")
63    def test_prepare_version(self, mock_exe_exists, _):
64        mock_exe_exists.return_value = True
65        self.config.version = None
66        self.config.config_test = mock.Mock()
67        self.config.get_version = mock.Mock(return_value=(1, 1))
68
69        self.assertRaises(
70            errors.NotSupportedError, self.config.prepare)
71
72    def test_prepare_locked(self):
73        server_root = self.config.conf("server-root")
74        self.config.config_test = mock.Mock()
75        os.remove(os.path.join(server_root, ".certbot.lock"))
76        certbot_util.lock_and_call(self._test_prepare_locked, server_root)
77
78    @mock.patch("certbot_apache._internal.parser.ApacheParser")
79    @mock.patch("certbot_apache._internal.configurator.util.exe_exists")
80    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root")
81    def _test_prepare_locked(self, _node, _exists, _parser):
82        try:
83            self.config.prepare()
84        except errors.PluginError as err:
85            err_msg = str(err)
86            self.assertTrue("lock" in err_msg)
87            self.assertTrue(self.config.conf("server-root") in err_msg)
88        else:  # pragma: no cover
89            self.fail("Exception wasn't raised!")
90
91    def test_add_parser_arguments(self):  # pylint: disable=no-self-use
92        from certbot_apache._internal.configurator import ApacheConfigurator
93        # Weak test..
94        ApacheConfigurator.add_parser_arguments(mock.MagicMock())
95
96    def test_docs_parser_arguments(self):
97        os.environ["CERTBOT_DOCS"] = "1"
98        from certbot_apache._internal.configurator import ApacheConfigurator
99        mock_add = mock.MagicMock()
100        ApacheConfigurator.add_parser_arguments(mock_add)
101        parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext",
102                      "vhost_root", "logs_root", "challenge_location",
103                      "handle_modules", "handle_sites", "ctl"]
104        exp = {}
105
106        for k in ApacheConfigurator.OS_DEFAULTS.__dict__.keys():
107            if k in parserargs:
108                exp[k.replace("_", "-")] = getattr(ApacheConfigurator.OS_DEFAULTS, k)
109        # Special cases
110        exp["vhost-root"] = None
111
112        found = set()
113        for call in mock_add.call_args_list:
114            found.add(call[0][0])
115
116        # Make sure that all (and only) the expected values exist
117        self.assertEqual(len(mock_add.call_args_list), len(found))
118        for e in exp:
119            self.assertTrue(e in found)
120
121        del os.environ["CERTBOT_DOCS"]
122
123    def test_add_parser_arguments_all_configurators(self):  # pylint: disable=no-self-use
124        from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES
125        for cls in OVERRIDE_CLASSES.values():
126            cls.add_parser_arguments(mock.MagicMock())
127
128    def test_all_configurators_defaults_defined(self):
129        from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES
130        from certbot_apache._internal.configurator import ApacheConfigurator
131        parameters = set(ApacheConfigurator.OS_DEFAULTS.__dict__.keys())
132        for cls in OVERRIDE_CLASSES.values():
133            self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys())))
134
135    def test_constant(self):
136        self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in
137                        self.config.options.server_root)
138
139    @certbot_util.patch_display_util()
140    def test_get_all_names(self, mock_getutility):
141        mock_utility = mock_getutility()
142        mock_utility.notification = mock.MagicMock(return_value=True)
143        names = self.config.get_all_names()
144        self.assertEqual(names, {"certbot.demo", "ocspvhost.com", "encryption-example.demo",
145             "nonsym.link", "vhost.in.rootconf", "www.certbot.demo",
146             "duplicate.example.com"})
147
148    @certbot_util.patch_display_util()
149    @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr")
150    def test_get_all_names_addrs(self, mock_gethost, mock_getutility):
151        mock_gethost.side_effect = [("google.com", "", ""), socket.error]
152        mock_utility = mock_getutility()
153        mock_utility.notification.return_value = True
154        vhost = obj.VirtualHost(
155            "fp", "ap",
156            {obj.Addr(("8.8.8.8", "443")),
157                 obj.Addr(("zombo.com",)),
158                 obj.Addr(("192.168.1.2"))},
159            True, False)
160
161        self.config.vhosts.append(vhost)
162
163        names = self.config.get_all_names()
164        self.assertEqual(len(names), 9)
165        self.assertTrue("zombo.com" in names)
166        self.assertTrue("google.com" in names)
167        self.assertTrue("certbot.demo" in names)
168
169    def test_get_bad_path(self):
170        self.assertEqual(apache_util.get_file_path(None), None)
171        self.assertEqual(apache_util.get_file_path("nonexistent"), None)
172        self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access
173
174    def test_get_aug_internal_path(self):
175        from certbot_apache._internal.apache_util import get_internal_aug_path
176        internal_paths = [
177            "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost",
178            "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost",
179            "IfModule/VirtualHost"]
180
181        for i, internal_path in enumerate(internal_paths):
182            self.assertEqual(
183                get_internal_aug_path(self.vh_truth[i].path), internal_path)
184
185    def test_bad_servername_alias(self):
186        ssl_vh1 = obj.VirtualHost(
187            "fp1", "ap1", {obj.Addr(("*", "443"))},
188            True, False)
189        # pylint: disable=protected-access
190        self.config._add_servernames(ssl_vh1)
191        self.assertTrue(
192                self.config._add_servername_alias("oy_vey", ssl_vh1) is None)
193
194    def test_add_servernames_alias(self):
195        self.config.parser.add_dir(
196            self.vh_truth[2].path, "ServerAlias", ["*.le.co"])
197        # pylint: disable=protected-access
198        self.config._add_servernames(self.vh_truth[2])
199        self.assertEqual(
200            self.vh_truth[2].get_names(), {"*.le.co", "ip-172-30-0-17"})
201
202    def test_get_virtual_hosts(self):
203        """Make sure all vhosts are being properly found."""
204        vhs = self.config.get_virtual_hosts()
205        self.assertEqual(len(vhs), 12)
206        found = 0
207
208        for vhost in vhs:
209            for truth in self.vh_truth:
210                if vhost == truth:
211                    found += 1
212                    break
213            else:
214                raise Exception("Missed: %s" % vhost)  # pragma: no cover
215
216        self.assertEqual(found, 12)
217
218        # Handle case of non-debian layout get_virtual_hosts
219        with mock.patch(
220                "certbot_apache._internal.configurator.ApacheConfigurator.conf"
221        ) as mock_conf:
222            mock_conf.return_value = False
223            vhs = self.config.get_virtual_hosts()
224            self.assertEqual(len(vhs), 12)
225
226    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
227    def test_choose_vhost_none_avail(self, mock_select):
228        mock_select.return_value = None
229        self.assertRaises(
230            errors.PluginError, self.config.choose_vhost, "none.com")
231
232    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
233    def test_choose_vhost_select_vhost_ssl(self, mock_select):
234        mock_select.return_value = self.vh_truth[1]
235        self.assertEqual(
236            self.vh_truth[1], self.config.choose_vhost("none.com"))
237
238    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
239    @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts")
240    def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select):
241        mock_select.return_value = self.vh_truth[0]
242        mock_conf.return_value = False
243        chosen_vhost = self.config.choose_vhost("none.com")
244        self.vh_truth[0].aliases.add("none.com")
245        self.assertEqual(
246            self.vh_truth[0].get_names(), chosen_vhost.get_names())
247
248        # Make sure we go from HTTP -> HTTPS
249        self.assertFalse(self.vh_truth[0].ssl)
250        self.assertTrue(chosen_vhost.ssl)
251
252    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost")
253    @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir")
254    def test_choose_vhost_and_servername_addition(self, mock_add, mock_find):
255        ret_vh = self.vh_truth[8]
256        ret_vh.enabled = False
257        mock_find.return_value = self.vh_truth[8]
258        self.config.choose_vhost("whatever.com")
259        self.assertTrue(mock_add.called)
260
261    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
262    def test_choose_vhost_select_vhost_with_temp(self, mock_select):
263        mock_select.return_value = self.vh_truth[0]
264        chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False)
265        self.assertEqual(self.vh_truth[0], chosen_vhost)
266
267    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
268    def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select):
269        mock_select.return_value = self.vh_truth[3]
270        conflicting_vhost = obj.VirtualHost(
271            "path", "aug_path", {obj.Addr.fromstring("*:443")},
272            True, True)
273        self.config.vhosts.append(conflicting_vhost)
274
275        self.assertRaises(
276            errors.PluginError, self.config.choose_vhost, "none.com")
277
278    def test_find_best_http_vhost_default(self):
279        vh = obj.VirtualHost(
280            "fp", "ap", {obj.Addr.fromstring("_default_:80")}, False, True)
281        self.config.vhosts = [vh]
282        self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh)
283
284    def test_find_best_http_vhost_port(self):
285        port = "8080"
286        vh = obj.VirtualHost(
287            "fp", "ap", {obj.Addr.fromstring("*:" + port)},
288            False, True, "encryption-example.demo")
289        self.config.vhosts.append(vh)
290        self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh)
291
292    def test_findbest_continues_on_short_domain(self):
293        # pylint: disable=protected-access
294        chosen_vhost = self.config._find_best_vhost("purple.com")
295        self.assertEqual(None, chosen_vhost)
296
297    def test_findbest_continues_on_long_domain(self):
298        # pylint: disable=protected-access
299        chosen_vhost = self.config._find_best_vhost("green.red.purple.com")
300        self.assertEqual(None, chosen_vhost)
301
302    def test_find_best_vhost(self):
303        # pylint: disable=protected-access
304        self.assertEqual(
305            self.vh_truth[3], self.config._find_best_vhost("certbot.demo"))
306        self.assertEqual(
307            self.vh_truth[0],
308            self.config._find_best_vhost("encryption-example.demo"))
309        self.assertEqual(
310            self.config._find_best_vhost("does-not-exist.com"), None)
311
312    def test_find_best_vhost_variety(self):
313        # pylint: disable=protected-access
314        ssl_vh = obj.VirtualHost(
315            "fp", "ap", {obj.Addr(("*", "443")),
316                             obj.Addr(("zombo.com",))},
317            True, False)
318        self.config.vhosts.append(ssl_vh)
319        self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh)
320
321    def test_find_best_vhost_default(self):
322        # pylint: disable=protected-access
323        # Assume only the two default vhosts.
324        self.config.vhosts = [
325            vh for vh in self.config.vhosts
326            if vh.name not in ["certbot.demo", "nonsym.link",
327                "encryption-example.demo", "duplicate.example.com",
328                "ocspvhost.com", "vhost.in.rootconf"]
329            and "*.blue.purple.com" not in vh.aliases
330        ]
331        self.assertEqual(
332            self.config._find_best_vhost("encryption-example.demo"),
333            self.vh_truth[2])
334
335    def test_non_default_vhosts(self):
336        # pylint: disable=protected-access
337        vhosts = self.config._non_default_vhosts(self.config.vhosts)
338        self.assertEqual(len(vhosts), 10)
339
340    @mock.patch('certbot_apache._internal.configurator.display_util.notify')
341    def test_deploy_cert_enable_new_vhost(self, unused_mock_notify):
342        # Create
343        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
344        self.config.parser.modules["ssl_module"] = None
345        self.config.parser.modules["mod_ssl.c"] = None
346        self.config.parser.modules["socache_shmcb_module"] = None
347
348        self.assertFalse(ssl_vhost.enabled)
349        self.config.deploy_cert(
350            "encryption-example.demo", "example/cert.pem", "example/key.pem",
351            "example/cert_chain.pem", "example/fullchain.pem")
352        self.assertTrue(ssl_vhost.enabled)
353
354    def test_no_duplicate_include(self):
355        def mock_find_dir(directive, argument, _):
356            """Mock method for parser.find_dir"""
357            if directive == "Include" and argument.endswith("options-ssl-apache.conf"):
358                return ["/path/to/whatever"]
359            return None  # pragma: no cover
360
361        mock_add = mock.MagicMock()
362        self.config.parser.add_dir = mock_add
363        self.config._add_dummy_ssl_directives(self.vh_truth[0])  # pylint: disable=protected-access
364        tried_to_add = False
365        for a in mock_add.call_args_list:
366            if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf:
367                tried_to_add = True
368        # Include should be added, find_dir is not patched, and returns falsy
369        self.assertTrue(tried_to_add)
370
371        self.config.parser.find_dir = mock_find_dir
372        mock_add.reset_mock()
373        self.config._add_dummy_ssl_directives(self.vh_truth[0])  # pylint: disable=protected-access
374        for a in mock_add.call_args_list:
375            if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf:
376                self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \
377                    # pragma: no cover
378
379    @mock.patch('certbot_apache._internal.configurator.display_util.notify')
380    def test_deploy_cert(self, unused_mock_notify):
381        self.config.parser.modules["ssl_module"] = None
382        self.config.parser.modules["mod_ssl.c"] = None
383        self.config.parser.modules["socache_shmcb_module"] = None
384        # Patch _add_dummy_ssl_directives to make sure we write them correctly
385        # pylint: disable=protected-access
386        orig_add_dummy = self.config._add_dummy_ssl_directives
387        def mock_add_dummy_ssl(vhostpath):
388            """Mock method for _add_dummy_ssl_directives"""
389            def find_args(path, directive):
390                """Return list of arguments in requested directive at path"""
391                f_args = []
392                dirs = self.config.parser.find_dir(directive, None,
393                                                   path)
394                for d in dirs:
395                    f_args.append(self.config.parser.get_arg(d))
396                return f_args
397            # Verify that the dummy directives do not exist
398            self.assertFalse(
399                "insert_cert_file_path" in find_args(vhostpath,
400                                                     "SSLCertificateFile"))
401            self.assertFalse(
402                "insert_key_file_path" in find_args(vhostpath,
403                                                    "SSLCertificateKeyFile"))
404            orig_add_dummy(vhostpath)
405            # Verify that the dummy directives exist
406            self.assertTrue(
407                "insert_cert_file_path" in find_args(vhostpath,
408                                                     "SSLCertificateFile"))
409            self.assertTrue(
410                "insert_key_file_path" in find_args(vhostpath,
411                                                    "SSLCertificateKeyFile"))
412        # pylint: disable=protected-access
413        self.config._add_dummy_ssl_directives = mock_add_dummy_ssl
414
415        # Get the default 443 vhost
416        self.config.assoc["random.demo"] = self.vh_truth[1]
417        self.config.deploy_cert(
418            "random.demo",
419            "example/cert.pem", "example/key.pem", "example/cert_chain.pem")
420        self.config.save()
421
422        # Verify ssl_module was enabled.
423        self.assertTrue(self.vh_truth[1].enabled)
424        self.assertTrue("ssl_module" in self.config.parser.modules)
425
426        loc_cert = self.config.parser.find_dir(
427            "sslcertificatefile", "example/cert.pem", self.vh_truth[1].path)
428        loc_key = self.config.parser.find_dir(
429            "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path)
430        loc_chain = self.config.parser.find_dir(
431            "SSLCertificateChainFile", "example/cert_chain.pem",
432            self.vh_truth[1].path)
433
434        # Verify one directive was found in the correct file
435        self.assertEqual(len(loc_cert), 1)
436        self.assertEqual(
437            apache_util.get_file_path(loc_cert[0]),
438            self.vh_truth[1].filep)
439
440        self.assertEqual(len(loc_key), 1)
441        self.assertEqual(
442            apache_util.get_file_path(loc_key[0]),
443            self.vh_truth[1].filep)
444
445        self.assertEqual(len(loc_chain), 1)
446        self.assertEqual(
447            apache_util.get_file_path(loc_chain[0]),
448            self.vh_truth[1].filep)
449
450        # One more time for chain directive setting
451        self.config.deploy_cert(
452            "random.demo",
453            "two/cert.pem", "two/key.pem", "two/cert_chain.pem")
454        self.assertTrue(self.config.parser.find_dir(
455            "SSLCertificateChainFile", "two/cert_chain.pem",
456            self.vh_truth[1].path))
457
458    def test_is_name_vhost(self):
459        addr = obj.Addr.fromstring("*:80")
460        self.assertTrue(self.config.is_name_vhost(addr))
461        self.config.version = (2, 2)
462        self.assertFalse(self.config.is_name_vhost(addr))
463
464    def test_add_name_vhost(self):
465        self.config.add_name_vhost(obj.Addr.fromstring("*:443"))
466        self.config.add_name_vhost(obj.Addr.fromstring("*:80"))
467        self.assertTrue(self.config.parser.find_dir(
468            "NameVirtualHost", "*:443", exclude=False))
469        self.assertTrue(self.config.parser.find_dir(
470            "NameVirtualHost", "*:80"))
471
472    def test_add_listen_80(self):
473        mock_find = mock.Mock()
474        mock_add_dir = mock.Mock()
475        mock_find.return_value = []
476        self.config.parser.find_dir = mock_find
477        self.config.parser.add_dir = mock_add_dir
478        self.config.ensure_listen("80")
479        self.assertTrue(mock_add_dir.called)
480        self.assertTrue(mock_find.called)
481        self.assertEqual(mock_add_dir.call_args[0][1], "Listen")
482        self.assertEqual(mock_add_dir.call_args[0][2], "80")
483
484    def test_add_listen_80_named(self):
485        mock_find = mock.Mock()
486        mock_find.return_value = ["test1", "test2", "test3"]
487        mock_get = mock.Mock()
488        mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
489        mock_add_dir = mock.Mock()
490
491        self.config.parser.find_dir = mock_find
492        self.config.parser.get_arg = mock_get
493        self.config.parser.add_dir = mock_add_dir
494
495        self.config.ensure_listen("80")
496        self.assertEqual(mock_add_dir.call_count, 0)
497
498        # Reset return lists and inputs
499        mock_add_dir.reset_mock()
500        mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
501
502        # Test
503        self.config.ensure_listen("8080")
504        self.assertEqual(mock_add_dir.call_count, 3)
505        self.assertTrue(mock_add_dir.called)
506        self.assertEqual(mock_add_dir.call_args[0][1], "Listen")
507        call_found = False
508        for mock_call in mock_add_dir.mock_calls:
509            if mock_call[1][2] == ['1.2.3.4:8080']:
510                call_found = True
511        self.assertTrue(call_found)
512
513    @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
514    def test_prepare_server_https(self, mock_reset):
515        mock_enable = mock.Mock()
516        self.config.enable_mod = mock_enable
517
518        mock_find = mock.Mock()
519        mock_add_dir = mock.Mock()
520        mock_find.return_value = []
521
522        # This will test the Add listen
523        self.config.parser.find_dir = mock_find
524        self.config.parser.add_dir_to_ifmodssl = mock_add_dir
525        self.config.prepare_server_https("443")
526        # Changing the order these modules are enabled breaks the reverter
527        self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb")
528        self.assertEqual(mock_enable.call_args[0][0], "ssl")
529        self.assertEqual(mock_enable.call_args[1], {"temp": False})
530
531        self.config.prepare_server_https("8080", temp=True)
532        # Changing the order these modules are enabled breaks the reverter
533        self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb")
534        self.assertEqual(mock_enable.call_args[0][0], "ssl")
535        # Enable mod is temporary
536        self.assertEqual(mock_enable.call_args[1], {"temp": True})
537
538        self.assertEqual(mock_add_dir.call_count, 2)
539
540    @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
541    def test_prepare_server_https_named_listen(self, mock_reset):
542        mock_find = mock.Mock()
543        mock_find.return_value = ["test1", "test2", "test3"]
544        mock_get = mock.Mock()
545        mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
546        mock_add_dir = mock.Mock()
547        mock_enable = mock.Mock()
548
549        self.config.parser.find_dir = mock_find
550        self.config.parser.get_arg = mock_get
551        self.config.parser.add_dir_to_ifmodssl = mock_add_dir
552        self.config.enable_mod = mock_enable
553
554        # Test Listen statements with specific ip listeed
555        self.config.prepare_server_https("443")
556        # Should be 0 as one interface already listens to 443
557        self.assertEqual(mock_add_dir.call_count, 0)
558
559        # Reset return lists and inputs
560        mock_add_dir.reset_mock()
561        mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
562
563        # Test
564        self.config.prepare_server_https("8080", temp=True)
565        self.assertEqual(mock_add_dir.call_count, 3)
566        call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)]
567        self.assertEqual(
568            sorted(call_args_list),
569            sorted([["1.2.3.4:8080", "https"],
570                    ["[::1]:8080", "https"],
571                    ["1.1.1.1:8080", "https"]]))
572
573        # mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"]
574        # mock_find.return_value = ["test1", "test2", "test3"]
575        # self.config.parser.get_arg = mock_get
576        # self.config.prepare_server_https("8080", temp=True)
577        # self.assertEqual(self.listens, 0)
578
579    @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
580    def test_prepare_server_https_needed_listen(self, mock_reset):
581        mock_find = mock.Mock()
582        mock_find.return_value = ["test1", "test2"]
583        mock_get = mock.Mock()
584        mock_get.side_effect = ["1.2.3.4:8080", "80"]
585        mock_add_dir = mock.Mock()
586        mock_enable = mock.Mock()
587
588        self.config.parser.find_dir = mock_find
589        self.config.parser.get_arg = mock_get
590        self.config.parser.add_dir_to_ifmodssl = mock_add_dir
591        self.config.enable_mod = mock_enable
592
593        self.config.prepare_server_https("443")
594        self.assertEqual(mock_add_dir.call_count, 1)
595
596    @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
597    def test_prepare_server_https_mixed_listen(self, mock_reset):
598        mock_find = mock.Mock()
599        mock_find.return_value = ["test1", "test2"]
600        mock_get = mock.Mock()
601        mock_get.side_effect = ["1.2.3.4:8080", "443"]
602        mock_add_dir = mock.Mock()
603        mock_enable = mock.Mock()
604
605        self.config.parser.find_dir = mock_find
606        self.config.parser.get_arg = mock_get
607        self.config.parser.add_dir_to_ifmodssl = mock_add_dir
608        self.config.enable_mod = mock_enable
609
610        # Test Listen statements with specific ip listeed
611        self.config.prepare_server_https("443")
612        # Should only be 2 here, as the third interface
613        # already listens to the correct port
614        self.assertEqual(mock_add_dir.call_count, 0)
615
616    def test_make_vhost_ssl_with_mock_span(self):
617        # span excludes the closing </VirtualHost> tag in older versions
618        # of Augeas
619        return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142]
620        with mock.patch.object(self.config.parser.aug, 'span') as mock_span:
621            mock_span.return_value = return_value
622            self.test_make_vhost_ssl()
623
624    def test_make_vhost_ssl_with_mock_span2(self):
625        # span includes the closing </VirtualHost> tag in newer versions
626        # of Augeas
627        return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157]
628        with mock.patch.object(self.config.parser.aug, 'span') as mock_span:
629            mock_span.return_value = return_value
630            self.test_make_vhost_ssl()
631
632    def test_make_vhost_ssl_nonsymlink(self):
633        ssl_vhost_slink = self.config.make_vhost_ssl(self.vh_truth[8])
634        self.assertTrue(ssl_vhost_slink.ssl)
635        self.assertTrue(ssl_vhost_slink.enabled)
636        self.assertEqual(ssl_vhost_slink.name, "nonsym.link")
637
638    def test_make_vhost_ssl_nonexistent_vhost_path(self):
639        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
640        self.assertEqual(os.path.dirname(ssl_vhost.filep),
641                         os.path.dirname(filesystem.realpath(self.vh_truth[1].filep)))
642
643    def test_make_vhost_ssl(self):
644        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
645
646        self.assertEqual(
647            ssl_vhost.filep,
648            os.path.join(self.config_path, "sites-available",
649                         "encryption-example-le-ssl.conf"))
650
651        self.assertEqual(ssl_vhost.path,
652                         "/files" + ssl_vhost.filep + "/IfModule/Virtualhost")
653        self.assertEqual(len(ssl_vhost.addrs), 1)
654        self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
655        self.assertEqual(ssl_vhost.name, "encryption-example.demo")
656        self.assertTrue(ssl_vhost.ssl)
657        self.assertFalse(ssl_vhost.enabled)
658
659        self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]),
660                         self.config.is_name_vhost(ssl_vhost))
661
662        self.assertEqual(len(self.config.vhosts), 13)
663
664    def test_clean_vhost_ssl(self):
665        # pylint: disable=protected-access
666        for directive in ["SSLCertificateFile", "SSLCertificateKeyFile",
667                          "SSLCertificateChainFile", "SSLCACertificatePath"]:
668            for _ in range(10):
669                self.config.parser.add_dir(self.vh_truth[1].path,
670                                           directive, ["bogus"])
671        self.config.save()
672
673        self.config._clean_vhost(self.vh_truth[1])
674        self.config.save()
675
676        loc_cert = self.config.parser.find_dir(
677            'SSLCertificateFile', None, self.vh_truth[1].path, False)
678        loc_key = self.config.parser.find_dir(
679            'SSLCertificateKeyFile', None, self.vh_truth[1].path, False)
680        loc_chain = self.config.parser.find_dir(
681            'SSLCertificateChainFile', None, self.vh_truth[1].path, False)
682        loc_cacert = self.config.parser.find_dir(
683            'SSLCACertificatePath', None, self.vh_truth[1].path, False)
684
685        self.assertEqual(len(loc_cert), 1)
686        self.assertEqual(len(loc_key), 1)
687
688        self.assertEqual(len(loc_chain), 0)
689
690        self.assertEqual(len(loc_cacert), 10)
691
692    def test_deduplicate_directives(self):
693        # pylint: disable=protected-access
694        DIRECTIVE = "Foo"
695        for _ in range(10):
696            self.config.parser.add_dir(self.vh_truth[1].path,
697                                       DIRECTIVE, ["bar"])
698        self.config.save()
699
700        self.config._deduplicate_directives(self.vh_truth[1].path, [DIRECTIVE])
701        self.config.save()
702
703        self.assertEqual(
704            len(self.config.parser.find_dir(
705                DIRECTIVE, None, self.vh_truth[1].path, False)), 1)
706
707    def test_remove_directives(self):
708        # pylint: disable=protected-access
709        DIRECTIVES = ["Foo", "Bar"]
710        for directive in DIRECTIVES:
711            for _ in range(10):
712                self.config.parser.add_dir(self.vh_truth[2].path,
713                                           directive, ["baz"])
714        self.config.save()
715
716        self.config._remove_directives(self.vh_truth[2].path, DIRECTIVES)
717        self.config.save()
718
719        for directive in DIRECTIVES:
720            self.assertEqual(
721                len(self.config.parser.find_dir(
722                    directive, None, self.vh_truth[2].path, False)), 0)
723
724    def test_make_vhost_ssl_bad_write(self):
725        mock_open = mock.mock_open()
726        # This calls open
727        self.config.reverter.register_file_creation = mock.Mock()
728        mock_open.side_effect = IOError
729        with mock.patch("builtins.open", mock_open):
730            self.assertRaises(
731                errors.PluginError,
732                self.config.make_vhost_ssl, self.vh_truth[0])
733
734    def test_get_ssl_vhost_path(self):
735        # pylint: disable=protected-access
736        self.assertTrue(
737            self.config._get_ssl_vhost_path("example_path").endswith(".conf"))
738
739    def test_add_name_vhost_if_necessary(self):
740        # pylint: disable=protected-access
741        self.config.add_name_vhost = mock.Mock()
742        self.config.version = (2, 2)
743        self.config._add_name_vhost_if_necessary(self.vh_truth[0])
744        self.assertTrue(self.config.add_name_vhost.called)
745
746        new_addrs = set()
747        for addr in self.vh_truth[0].addrs:
748            new_addrs.add(obj.Addr(("_default_", addr.get_port(),)))
749
750        self.vh_truth[0].addrs = new_addrs
751        self.config._add_name_vhost_if_necessary(self.vh_truth[0])
752        self.assertEqual(self.config.add_name_vhost.call_count, 2)
753
754    @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform")
755    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart")
756    def test_perform(self, mock_restart, mock_http_perform):
757        # Only tests functionality specific to configurator.perform
758        # Note: As more challenges are offered this will have to be expanded
759        account_key, achalls = self.get_key_and_achalls()
760
761        expected = [achall.response(account_key) for achall in achalls]
762        mock_http_perform.return_value = expected
763
764        responses = self.config.perform(achalls)
765
766        self.assertEqual(mock_http_perform.call_count, 1)
767        self.assertEqual(responses, expected)
768
769        self.assertEqual(mock_restart.call_count, 1)
770
771    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart")
772    @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg")
773    def test_cleanup(self, mock_cfg, mock_restart):
774        mock_cfg.return_value = ""
775        _, achalls = self.get_key_and_achalls()
776
777        for achall in achalls:
778            self.config._chall_out.add(achall)  # pylint: disable=protected-access
779
780        for i, achall in enumerate(achalls):
781            self.config.cleanup([achall])
782            if i == len(achalls) - 1:
783                self.assertTrue(mock_restart.called)
784            else:
785                self.assertFalse(mock_restart.called)
786
787    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart")
788    @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg")
789    def test_cleanup_no_errors(self, mock_cfg, mock_restart):
790        mock_cfg.return_value = ""
791        _, achalls = self.get_key_and_achalls()
792        self.config.http_doer = mock.MagicMock()
793
794        for achall in achalls:
795            self.config._chall_out.add(achall)  # pylint: disable=protected-access
796
797        self.config.cleanup([achalls[-1]])
798        self.assertFalse(mock_restart.called)
799
800        self.config.cleanup(achalls)
801        self.assertTrue(mock_restart.called)
802
803    @mock.patch("certbot.util.run_script")
804    def test_get_version(self, mock_script):
805        mock_script.return_value = (
806            "Server Version: Apache/2.4.2 (Debian)", "")
807        self.assertEqual(self.config.get_version(), (2, 4, 2))
808
809        mock_script.return_value = (
810            "Server Version: Apache/2 (Linux)", "")
811        self.assertEqual(self.config.get_version(), (2,))
812
813        mock_script.return_value = (
814            "Server Version: Apache (Debian)", "")
815        self.assertRaises(errors.PluginError, self.config.get_version)
816
817        mock_script.return_value = (
818            "Server Version: Apache/2.3{0} Apache/2.4.7".format(
819                os.linesep), "")
820        self.assertRaises(errors.PluginError, self.config.get_version)
821
822        mock_script.side_effect = errors.SubprocessError("Can't find program")
823        self.assertRaises(errors.PluginError, self.config.get_version)
824
825    @mock.patch("certbot_apache._internal.configurator.util.run_script")
826    def test_restart(self, _):
827        self.config.restart()
828
829    @mock.patch("certbot_apache._internal.configurator.util.run_script")
830    def test_restart_bad_process(self, mock_run_script):
831        mock_run_script.side_effect = [None, errors.SubprocessError]
832
833        self.assertRaises(errors.MisconfigurationError, self.config.restart)
834
835    @mock.patch("certbot.util.run_script")
836    def test_config_test(self, _):
837        self.config.config_test()
838
839    @mock.patch("certbot.util.run_script")
840    def test_config_test_bad_process(self, mock_run_script):
841        mock_run_script.side_effect = errors.SubprocessError
842
843        self.assertRaises(errors.MisconfigurationError,
844                          self.config.config_test)
845
846    def test_more_info(self):
847        self.assertTrue(self.config.more_info())
848
849    def test_get_chall_pref(self):
850        self.assertTrue(isinstance(self.config.get_chall_pref(""), list))
851
852    def test_install_ssl_options_conf(self):
853        path = os.path.join(self.work_dir, "test_it")
854        other_path = os.path.join(self.work_dir, "other_test_it")
855        self.config.install_ssl_options_conf(path, other_path)
856        self.assertTrue(os.path.isfile(path))
857        self.assertTrue(os.path.isfile(other_path))
858
859    # TEST ENHANCEMENTS
860    def test_supported_enhancements(self):
861        self.assertTrue(isinstance(self.config.supported_enhancements(), list))
862
863    def test_find_http_vhost_without_ancestor(self):
864        # pylint: disable=protected-access
865        vhost = self.vh_truth[0]
866        vhost.ssl = True
867        vhost.ancestor = None
868        res = self.config._get_http_vhost(vhost)
869        self.assertEqual(self.vh_truth[0].name, res.name)
870        self.assertEqual(self.vh_truth[0].aliases, res.aliases)
871
872    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost")
873    @mock.patch("certbot_apache._internal.display_ops.select_vhost")
874    @mock.patch("certbot.util.exe_exists")
875    def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get):
876        self.config.parser.modules["rewrite_module"] = None
877        mock_exe.return_value = True
878        ssl_vh1 = obj.VirtualHost(
879            "fp1", "ap1", {obj.Addr(("*", "443"))},
880            True, False)
881        ssl_vh1.name = "satoshi.com"
882        self.config.vhosts.append(ssl_vh1)
883        mock_sel_vhost.return_value = None
884        mock_get.return_value = None
885
886        self.assertRaises(
887            errors.PluginError,
888            self.config.enhance, "satoshi.com", "redirect")
889
890    def test_enhance_unknown_enhancement(self):
891        self.assertRaises(
892            errors.PluginError,
893            self.config.enhance, "certbot.demo", "unknown_enhancement")
894
895    def test_enhance_no_ssl_vhost(self):
896        with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log:
897            self.assertRaises(errors.PluginError, self.config.enhance,
898                              "certbot.demo", "redirect")
899            # Check that correct logger.warning was printed
900            self.assertTrue("not able to find" in mock_log.call_args[0][0])
901            self.assertTrue("\"redirect\"" in mock_log.call_args[0][0])
902
903            mock_log.reset_mock()
904
905            self.assertRaises(errors.PluginError, self.config.enhance,
906                              "certbot.demo", "ensure-http-header", "Test")
907            # Check that correct logger.warning was printed
908            self.assertTrue("not able to find" in mock_log.call_args[0][0])
909            self.assertTrue("Test" in mock_log.call_args[0][0])
910
911    @mock.patch("certbot.util.exe_exists")
912    def test_ocsp_stapling(self, mock_exe):
913        self.config.parser.update_runtime_variables = mock.Mock()
914        self.config.parser.modules["mod_ssl.c"] = None
915        self.config.parser.modules["socache_shmcb_module"] = None
916        self.config.get_version = mock.Mock(return_value=(2, 4, 7))
917        mock_exe.return_value = True
918
919        # This will create an ssl vhost for certbot.demo
920        self.config.choose_vhost("certbot.demo")
921        self.config.enhance("certbot.demo", "staple-ocsp")
922
923        # Get the ssl vhost for certbot.demo
924        ssl_vhost = self.config.assoc["certbot.demo"]
925
926        ssl_use_stapling_aug_path = self.config.parser.find_dir(
927            "SSLUseStapling", "on", ssl_vhost.path)
928
929        self.assertEqual(len(ssl_use_stapling_aug_path), 1)
930
931        ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep)
932        stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache',
933                    "shmcb:/var/run/apache2/stapling_cache(128000)",
934                    ssl_vhost_aug_path)
935
936        self.assertEqual(len(stapling_cache_aug_path), 1)
937
938    @mock.patch("certbot.util.exe_exists")
939    def test_ocsp_stapling_twice(self, mock_exe):
940        self.config.parser.update_runtime_variables = mock.Mock()
941        self.config.parser.modules["mod_ssl.c"] = None
942        self.config.parser.modules["socache_shmcb_module"] = None
943        self.config.get_version = mock.Mock(return_value=(2, 4, 7))
944        mock_exe.return_value = True
945
946        # Checking the case with already enabled ocsp stapling configuration
947        self.config.choose_vhost("ocspvhost.com")
948        self.config.enhance("ocspvhost.com", "staple-ocsp")
949
950        # Get the ssl vhost for letsencrypt.demo
951        ssl_vhost = self.config.assoc["ocspvhost.com"]
952
953        ssl_use_stapling_aug_path = self.config.parser.find_dir(
954            "SSLUseStapling", "on", ssl_vhost.path)
955
956        self.assertEqual(len(ssl_use_stapling_aug_path), 1)
957        ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep)
958        stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache',
959                    "shmcb:/var/run/apache2/stapling_cache(128000)",
960                    ssl_vhost_aug_path)
961
962        self.assertEqual(len(stapling_cache_aug_path), 1)
963
964
965    @mock.patch("certbot.util.exe_exists")
966    def test_ocsp_unsupported_apache_version(self, mock_exe):
967        mock_exe.return_value = True
968        self.config.parser.update_runtime_variables = mock.Mock()
969        self.config.parser.modules["mod_ssl.c"] = None
970        self.config.parser.modules["socache_shmcb_module"] = None
971        self.config.get_version = mock.Mock(return_value=(2, 2, 0))
972        self.config.choose_vhost("certbot.demo")
973
974        self.assertRaises(errors.PluginError,
975                self.config.enhance, "certbot.demo", "staple-ocsp")
976
977
978    def test_get_http_vhost_third_filter(self):
979        ssl_vh = obj.VirtualHost(
980            "fp", "ap", {obj.Addr(("*", "443"))},
981            True, False)
982        ssl_vh.name = "satoshi.com"
983        self.config.vhosts.append(ssl_vh)
984
985        # pylint: disable=protected-access
986        http_vh = self.config._get_http_vhost(ssl_vh)
987        self.assertFalse(http_vh.ssl)
988
989    @mock.patch("certbot.util.run_script")
990    @mock.patch("certbot.util.exe_exists")
991    def test_http_header_hsts(self, mock_exe, _):
992        self.config.parser.update_runtime_variables = mock.Mock()
993        self.config.parser.modules["mod_ssl.c"] = None
994        self.config.parser.modules["headers_module"] = None
995        mock_exe.return_value = True
996
997        # This will create an ssl vhost for certbot.demo
998        self.config.choose_vhost("certbot.demo")
999        self.config.enhance("certbot.demo", "ensure-http-header",
1000                            "Strict-Transport-Security")
1001
1002        # Get the ssl vhost for certbot.demo
1003        ssl_vhost = self.config.assoc["certbot.demo"]
1004
1005        # These are not immediately available in find_dir even with save() and
1006        # load(). They must be found in sites-available
1007        hsts_header = self.config.parser.find_dir(
1008            "Header", None, ssl_vhost.path)
1009
1010        # four args to HSTS header
1011        self.assertEqual(len(hsts_header), 4)
1012
1013    def test_http_header_hsts_twice(self):
1014        self.config.parser.modules["mod_ssl.c"] = None
1015        # skip the enable mod
1016        self.config.parser.modules["headers_module"] = None
1017
1018        # This will create an ssl vhost for encryption-example.demo
1019        self.config.choose_vhost("encryption-example.demo")
1020        self.config.enhance("encryption-example.demo", "ensure-http-header",
1021                            "Strict-Transport-Security")
1022
1023        self.assertRaises(
1024            errors.PluginEnhancementAlreadyPresent,
1025            self.config.enhance, "encryption-example.demo",
1026            "ensure-http-header", "Strict-Transport-Security")
1027
1028    @mock.patch("certbot.util.run_script")
1029    @mock.patch("certbot.util.exe_exists")
1030    def test_http_header_uir(self, mock_exe, _):
1031        self.config.parser.update_runtime_variables = mock.Mock()
1032        self.config.parser.modules["mod_ssl.c"] = None
1033        self.config.parser.modules["headers_module"] = None
1034
1035        mock_exe.return_value = True
1036
1037        # This will create an ssl vhost for certbot.demo
1038        self.config.choose_vhost("certbot.demo")
1039        self.config.enhance("certbot.demo", "ensure-http-header",
1040                            "Upgrade-Insecure-Requests")
1041
1042        self.assertTrue("headers_module" in self.config.parser.modules)
1043
1044        # Get the ssl vhost for certbot.demo
1045        ssl_vhost = self.config.assoc["certbot.demo"]
1046
1047        # These are not immediately available in find_dir even with save() and
1048        # load(). They must be found in sites-available
1049        uir_header = self.config.parser.find_dir(
1050            "Header", None, ssl_vhost.path)
1051
1052        # four args to HSTS header
1053        self.assertEqual(len(uir_header), 4)
1054
1055    def test_http_header_uir_twice(self):
1056        self.config.parser.modules["mod_ssl.c"] = None
1057        # skip the enable mod
1058        self.config.parser.modules["headers_module"] = None
1059
1060        # This will create an ssl vhost for encryption-example.demo
1061        self.config.choose_vhost("encryption-example.demo")
1062        self.config.enhance("encryption-example.demo", "ensure-http-header",
1063                            "Upgrade-Insecure-Requests")
1064
1065        self.assertRaises(
1066            errors.PluginEnhancementAlreadyPresent,
1067            self.config.enhance, "encryption-example.demo",
1068            "ensure-http-header", "Upgrade-Insecure-Requests")
1069
1070    @mock.patch("certbot.util.run_script")
1071    @mock.patch("certbot.util.exe_exists")
1072    def test_redirect_well_formed_http(self, mock_exe, _):
1073        self.config.parser.modules["rewrite_module"] = None
1074        self.config.parser.update_runtime_variables = mock.Mock()
1075        mock_exe.return_value = True
1076        self.config.get_version = mock.Mock(return_value=(2, 2))
1077
1078        # This will create an ssl vhost for certbot.demo
1079        self.config.choose_vhost("certbot.demo")
1080        self.config.enhance("certbot.demo", "redirect")
1081
1082        # These are not immediately available in find_dir even with save() and
1083        # load(). They must be found in sites-available
1084        rw_engine = self.config.parser.find_dir(
1085            "RewriteEngine", "on", self.vh_truth[3].path)
1086        rw_rule = self.config.parser.find_dir(
1087            "RewriteRule", None, self.vh_truth[3].path)
1088
1089        self.assertEqual(len(rw_engine), 1)
1090        # three args to rw_rule
1091        self.assertEqual(len(rw_rule), 3)
1092
1093        # [:-3] to remove the vhost index number
1094        self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3]))
1095        self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3]))
1096
1097    def test_rewrite_rule_exists(self):
1098        # Skip the enable mod
1099        self.config.parser.modules["rewrite_module"] = None
1100        self.config.get_version = mock.Mock(return_value=(2, 3, 9))
1101        self.config.parser.add_dir(
1102            self.vh_truth[3].path, "RewriteRule", ["Unknown"])
1103        # pylint: disable=protected-access
1104        self.assertTrue(self.config._is_rewrite_exists(self.vh_truth[3]))
1105
1106    def test_rewrite_engine_exists(self):
1107        # Skip the enable mod
1108        self.config.parser.modules["rewrite_module"] = None
1109        self.config.get_version = mock.Mock(return_value=(2, 3, 9))
1110        self.config.parser.add_dir(
1111            self.vh_truth[3].path, "RewriteEngine", "on")
1112        # pylint: disable=protected-access
1113        self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3]))
1114
1115    @mock.patch("certbot.util.run_script")
1116    @mock.patch("certbot.util.exe_exists")
1117    def test_redirect_with_existing_rewrite(self, mock_exe, _):
1118        self.config.parser.modules["rewrite_module"] = None
1119        self.config.parser.update_runtime_variables = mock.Mock()
1120        mock_exe.return_value = True
1121        self.config.get_version = mock.Mock(return_value=(2, 2, 0))
1122
1123        # Create a preexisting rewrite rule
1124        self.config.parser.add_dir(
1125            self.vh_truth[3].path, "RewriteRule", ["UnknownPattern",
1126                                                   "UnknownTarget"])
1127        self.config.save()
1128
1129        # This will create an ssl vhost for certbot.demo
1130        self.config.choose_vhost("certbot.demo")
1131        self.config.enhance("certbot.demo", "redirect")
1132
1133        # These are not immediately available in find_dir even with save() and
1134        # load(). They must be found in sites-available
1135        rw_engine = self.config.parser.find_dir(
1136            "RewriteEngine", "on", self.vh_truth[3].path)
1137        rw_rule = self.config.parser.find_dir(
1138            "RewriteRule", None, self.vh_truth[3].path)
1139
1140        self.assertEqual(len(rw_engine), 1)
1141        # three args to rw_rule + 1 arg for the pre existing rewrite
1142        self.assertEqual(len(rw_rule), 5)
1143        # [:-3] to remove the vhost index number
1144        self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3]))
1145        self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3]))
1146
1147        self.assertTrue("rewrite_module" in self.config.parser.modules)
1148
1149    @mock.patch("certbot.util.run_script")
1150    @mock.patch("certbot.util.exe_exists")
1151    def test_redirect_with_old_https_redirection(self, mock_exe, _):
1152        self.config.parser.modules["rewrite_module"] = None
1153        self.config.parser.update_runtime_variables = mock.Mock()
1154        mock_exe.return_value = True
1155        self.config.get_version = mock.Mock(return_value=(2, 2, 0))
1156
1157        ssl_vhost = self.config.choose_vhost("certbot.demo")
1158
1159        # pylint: disable=protected-access
1160        http_vhost = self.config._get_http_vhost(ssl_vhost)
1161
1162        # Create an old (previously suppoorted) https redirectoin rewrite rule
1163        self.config.parser.add_dir(
1164            http_vhost.path, "RewriteRule",
1165            ["^",
1166             "https://%{SERVER_NAME}%{REQUEST_URI}",
1167             "[L,QSA,R=permanent]"])
1168
1169        self.config.save()
1170
1171        try:
1172            self.config.enhance("certbot.demo", "redirect")
1173        except errors.PluginEnhancementAlreadyPresent:
1174            args_paths = self.config.parser.find_dir(
1175                "RewriteRule", None, http_vhost.path, False)
1176            arg_vals = [self.config.parser.aug.get(x) for x in args_paths]
1177            self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS)
1178
1179
1180    def test_redirect_with_conflict(self):
1181        self.config.parser.modules["rewrite_module"] = None
1182        ssl_vh = obj.VirtualHost(
1183            "fp", "ap", {obj.Addr(("*", "443")),
1184                             obj.Addr(("zombo.com",))},
1185            True, False)
1186        # No names ^ this guy should conflict.
1187
1188        # pylint: disable=protected-access
1189        self.assertRaises(
1190            errors.PluginError, self.config._enable_redirect, ssl_vh, "")
1191
1192    def test_redirect_two_domains_one_vhost(self):
1193        # Skip the enable mod
1194        self.config.parser.modules["rewrite_module"] = None
1195        self.config.get_version = mock.Mock(return_value=(2, 3, 9))
1196
1197        # Creates ssl vhost for the domain
1198        self.config.choose_vhost("red.blue.purple.com")
1199
1200        self.config.enhance("red.blue.purple.com", "redirect")
1201        verify_no_redirect = ("certbot_apache._internal.configurator."
1202                              "ApacheConfigurator._verify_no_certbot_redirect")
1203        with mock.patch(verify_no_redirect) as mock_verify:
1204            self.config.enhance("green.blue.purple.com", "redirect")
1205        self.assertFalse(mock_verify.called)
1206
1207    def test_redirect_from_previous_run(self):
1208        # Skip the enable mod
1209        self.config.parser.modules["rewrite_module"] = None
1210        self.config.get_version = mock.Mock(return_value=(2, 3, 9))
1211        self.config.choose_vhost("red.blue.purple.com")
1212        self.config.enhance("red.blue.purple.com", "redirect")
1213        # Clear state about enabling redirect on this run
1214        # pylint: disable=protected-access
1215        self.config._enhanced_vhosts["redirect"].clear()
1216
1217        self.assertRaises(
1218            errors.PluginEnhancementAlreadyPresent,
1219            self.config.enhance, "green.blue.purple.com", "redirect")
1220
1221    def test_create_own_redirect(self):
1222        self.config.parser.modules["rewrite_module"] = None
1223        self.config.get_version = mock.Mock(return_value=(2, 3, 9))
1224        # For full testing... give names...
1225        self.vh_truth[1].name = "default.com"
1226        self.vh_truth[1].aliases = {"yes.default.com"}
1227
1228        # pylint: disable=protected-access
1229        self.config._enable_redirect(self.vh_truth[1], "")
1230        self.assertEqual(len(self.config.vhosts), 13)
1231
1232    def test_create_own_redirect_for_old_apache_version(self):
1233        self.config.parser.modules["rewrite_module"] = None
1234        self.config.get_version = mock.Mock(return_value=(2, 2))
1235        # For full testing... give names...
1236        self.vh_truth[1].name = "default.com"
1237        self.vh_truth[1].aliases = {"yes.default.com"}
1238
1239        # pylint: disable=protected-access
1240        self.config._enable_redirect(self.vh_truth[1], "")
1241        self.assertEqual(len(self.config.vhosts), 13)
1242
1243    def test_sift_rewrite_rule(self):
1244        # pylint: disable=protected-access
1245        small_quoted_target = "RewriteRule ^ \"http://\""
1246        self.assertFalse(self.config._sift_rewrite_rule(small_quoted_target))
1247
1248        https_target = "RewriteRule ^ https://satoshi"
1249        self.assertTrue(self.config._sift_rewrite_rule(https_target))
1250
1251        normal_target = "RewriteRule ^/(.*) http://www.a.com:1234/$1 [L,R]"
1252        self.assertFalse(self.config._sift_rewrite_rule(normal_target))
1253
1254        not_rewriterule = "NotRewriteRule ^ ..."
1255        self.assertFalse(self.config._sift_rewrite_rule(not_rewriterule))
1256
1257    def get_key_and_achalls(self):
1258        """Return testing achallenges."""
1259        account_key = self.rsa512jwk
1260        achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
1261            challb=acme_util.chall_to_challb(
1262                challenges.HTTP01(
1263                    token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"),
1264                "pending"),
1265            domain="encryption-example.demo", account_key=account_key)
1266        achall2 = achallenges.KeyAuthorizationAnnotatedChallenge(
1267            challb=acme_util.chall_to_challb(
1268                challenges.HTTP01(
1269                    token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"),
1270                "pending"),
1271            domain="certbot.demo", account_key=account_key)
1272        achall3 = achallenges.KeyAuthorizationAnnotatedChallenge(
1273            challb=acme_util.chall_to_challb(
1274                challenges.HTTP01(token=(b'x' * 16)), "pending"),
1275            domain="example.org", account_key=account_key)
1276
1277        return account_key, (achall1, achall2, achall3)
1278
1279    def test_enable_site_nondebian(self):
1280        inc_path = "/path/to/wherever"
1281        vhost = self.vh_truth[0]
1282        vhost.enabled = False
1283        vhost.filep = inc_path
1284        self.assertFalse(self.config.parser.find_dir("Include", inc_path))
1285        self.assertFalse(
1286            os.path.dirname(inc_path) in self.config.parser.existing_paths)
1287        self.config.enable_site(vhost)
1288        self.assertTrue(self.config.parser.find_dir("Include", inc_path))
1289        self.assertTrue(
1290            os.path.dirname(inc_path) in self.config.parser.existing_paths)
1291        self.assertTrue(
1292            os.path.basename(inc_path) in self.config.parser.existing_paths[
1293                os.path.dirname(inc_path)])
1294
1295    @mock.patch('certbot_apache._internal.configurator.display_util.notify')
1296    def test_deploy_cert_not_parsed_path(self, unused_mock_notify):
1297        # Make sure that we add include to root config for vhosts when
1298        # handle-sites is false
1299        self.config.parser.modules["ssl_module"] = None
1300        self.config.parser.modules["mod_ssl.c"] = None
1301        self.config.parser.modules["socache_shmcb_module"] = None
1302        tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot"))
1303        filesystem.chmod(tmp_path, 0o755)
1304        mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path"
1305        mock_a = "certbot_apache._internal.parser.ApacheParser.add_include"
1306
1307        with mock.patch(mock_p) as mock_path:
1308            mock_path.return_value = os.path.join(tmp_path, "whatever.conf")
1309            with mock.patch(mock_a) as mock_add:
1310                self.config.deploy_cert(
1311                    "encryption-example.demo",
1312                    "example/cert.pem", "example/key.pem",
1313                    "example/cert_chain.pem")
1314                # Test that we actually called add_include
1315                self.assertTrue(mock_add.called)
1316        shutil.rmtree(tmp_path)
1317
1318    def test_deploy_cert_no_mod_ssl(self):
1319        # Create
1320        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
1321        self.config.parser.modules["socache_shmcb_module"] = None
1322        self.config.prepare_server_https = mock.Mock()
1323
1324        self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert,
1325            "encryption-example.demo", "example/cert.pem", "example/key.pem",
1326            "example/cert_chain.pem", "example/fullchain.pem")
1327
1328    @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original")
1329    def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed):
1330        ret_vh = self.vh_truth[8]
1331        ret_vh.enabled = True
1332        self.config.enable_site(ret_vh)
1333        # Make sure that we return early
1334        self.assertFalse(mock_parsed.called)
1335
1336    def test_enable_mod_unsupported(self):
1337        self.assertRaises(errors.MisconfigurationError,
1338                          self.config.enable_mod,
1339                          "whatever")
1340
1341    def test_choose_vhosts_wildcard(self):
1342        # pylint: disable=protected-access
1343        mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple"
1344        with mock.patch(mock_path) as mock_select_vhs:
1345            mock_select_vhs.return_value = [self.vh_truth[3]]
1346            vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
1347                                                     create_ssl=True)
1348            # Check that the dialog was called with one vh: certbot.demo
1349            self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[3])
1350            self.assertEqual(len(mock_select_vhs.call_args_list), 1)
1351
1352            # And the actual returned values
1353            self.assertEqual(len(vhs), 1)
1354            self.assertEqual(vhs[0].name, "certbot.demo")
1355            self.assertTrue(vhs[0].ssl)
1356
1357            self.assertNotEqual(vhs[0], self.vh_truth[3])
1358
1359    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl")
1360    def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl):
1361        # pylint: disable=protected-access
1362        mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple"
1363        with mock.patch(mock_path) as mock_select_vhs:
1364            mock_select_vhs.return_value = [self.vh_truth[1]]
1365            vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
1366                                                     create_ssl=False)
1367            self.assertFalse(mock_makessl.called)
1368            self.assertEqual(vhs[0], self.vh_truth[1])
1369
1370    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard")
1371    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl")
1372    def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w):
1373        # pylint: disable=protected-access
1374        # Already SSL vhost
1375        mock_vh_for_w.return_value = [self.vh_truth[7]]
1376        mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple"
1377        with mock.patch(mock_path) as mock_select_vhs:
1378            mock_select_vhs.return_value = [self.vh_truth[7]]
1379            vhs = self.config._choose_vhosts_wildcard("whatever",
1380                                                     create_ssl=True)
1381            self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[7])
1382            self.assertEqual(len(mock_select_vhs.call_args_list), 1)
1383            # Ensure that make_vhost_ssl was not called, vhost.ssl == true
1384            self.assertFalse(mock_makessl.called)
1385
1386            # And the actual returned values
1387            self.assertEqual(len(vhs), 1)
1388            self.assertTrue(vhs[0].ssl)
1389            self.assertEqual(vhs[0], self.vh_truth[7])
1390
1391
1392    @mock.patch('certbot_apache._internal.configurator.display_util.notify')
1393    def test_deploy_cert_wildcard(self, unused_mock_notify):
1394        # pylint: disable=protected-access
1395        mock_choose_vhosts = mock.MagicMock()
1396        mock_choose_vhosts.return_value = [self.vh_truth[7]]
1397        self.config._choose_vhosts_wildcard = mock_choose_vhosts
1398        mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert"
1399        with mock.patch(mock_d) as mock_dep:
1400            self.config.deploy_cert("*.wildcard.example.org", "/tmp/path",
1401                                    "/tmp/path", "/tmp/path", "/tmp/path")
1402            self.assertTrue(mock_dep.called)
1403            self.assertEqual(len(mock_dep.call_args_list), 1)
1404            self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0])
1405
1406    @mock.patch("certbot_apache._internal.display_ops.select_vhost_multiple")
1407    def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog):
1408        # pylint: disable=protected-access
1409        mock_dialog.return_value = []
1410        self.assertRaises(errors.PluginError,
1411                          self.config.deploy_cert,
1412                          "*.wild.cat", "/tmp/path", "/tmp/path",
1413                           "/tmp/path", "/tmp/path")
1414
1415    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard")
1416    def test_enhance_wildcard_after_install(self, mock_choose):
1417        # pylint: disable=protected-access
1418        self.config.parser.modules["mod_ssl.c"] = None
1419        self.config.parser.modules["headers_module"] = None
1420        self.vh_truth[3].ssl = True
1421        self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]]
1422        self.config.enhance("*.certbot.demo", "ensure-http-header",
1423                            "Upgrade-Insecure-Requests")
1424        self.assertFalse(mock_choose.called)
1425
1426    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard")
1427    def test_enhance_wildcard_no_install(self, mock_choose):
1428        self.vh_truth[3].ssl = True
1429        mock_choose.return_value = [self.vh_truth[3]]
1430        self.config.parser.modules["mod_ssl.c"] = None
1431        self.config.parser.modules["headers_module"] = None
1432        self.config.enhance("*.certbot.demo", "ensure-http-header",
1433                            "Upgrade-Insecure-Requests")
1434        self.assertTrue(mock_choose.called)
1435
1436    def test_add_vhost_id(self):
1437        for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]:
1438            vh_id = self.config.add_vhost_id(vh)
1439            self.assertEqual(vh, self.config.find_vhost_by_id(vh_id))
1440
1441    def test_find_vhost_by_id_404(self):
1442        self.assertRaises(errors.PluginError,
1443                          self.config.find_vhost_by_id,
1444                          "nonexistent")
1445
1446    def test_add_vhost_id_already_exists(self):
1447        first_id = self.config.add_vhost_id(self.vh_truth[0])
1448        second_id = self.config.add_vhost_id(self.vh_truth[0])
1449        self.assertEqual(first_id, second_id)
1450
1451    def test_realpath_replaces_symlink(self):
1452        orig_match = self.config.parser.aug.match
1453        mock_vhost = copy.deepcopy(self.vh_truth[0])
1454        mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available')
1455        mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available')
1456        mock_vhost.enabled = False
1457        self.config.parser.parse_file(mock_vhost.filep)
1458
1459        def mock_match(aug_expr):
1460            """Return a mocked match list of VirtualHosts"""
1461            if "/mocked/path" in aug_expr:
1462                return [self.vh_truth[1].path, self.vh_truth[0].path, mock_vhost.path]
1463            return orig_match(aug_expr)
1464
1465        self.config.parser.parser_paths = ["/mocked/path"]
1466        self.config.parser.aug.match = mock_match
1467        vhs = self.config.get_virtual_hosts()
1468        self.assertEqual(len(vhs), 2)
1469        self.assertEqual(vhs[0], self.vh_truth[1])
1470        # mock_vhost should have replaced the vh_truth[0], because its filepath
1471        # isn't a symlink
1472        self.assertEqual(vhs[1], mock_vhost)
1473
1474
1475class AugeasVhostsTest(util.ApacheTest):
1476    """Test vhosts with illegal names dependent on augeas version."""
1477    # pylint: disable=protected-access
1478
1479    def setUp(self):  # pylint: disable=arguments-differ
1480        td = "debian_apache_2_4/augeas_vhosts"
1481        cr = "debian_apache_2_4/augeas_vhosts/apache2"
1482        vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available"
1483        super().setUp(test_dir=td,
1484                      config_root=cr,
1485                      vhost_root=vr)
1486
1487        self.config = util.get_apache_configurator(
1488            self.config_path, self.vhost_path, self.config_dir,
1489            self.work_dir)
1490
1491    def test_choosevhost_with_illegal_name(self):
1492        self.config.parser.aug = mock.MagicMock()
1493        self.config.parser.aug.match.side_effect = RuntimeError
1494        path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
1495        chosen_vhost = self.config._create_vhost(path)
1496        self.assertEqual(None, chosen_vhost)
1497
1498    def test_choosevhost_works(self):
1499        path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
1500        chosen_vhost = self.config._create_vhost(path)
1501        self.assertTrue(chosen_vhost is None or chosen_vhost.path == path)
1502
1503    @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost")
1504    def test_get_vhost_continue(self, mock_vhost):
1505        mock_vhost.return_value = None
1506        vhs = self.config.get_virtual_hosts()
1507        self.assertEqual([], vhs)
1508
1509    def test_choose_vhost_with_matching_wildcard(self):
1510        names = (
1511            "an.example.net", "another.example.net", "an.other.example.net")
1512        for name in names:
1513            self.assertFalse(name in self.config.choose_vhost(name).aliases)
1514
1515    @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts")
1516    def test_choose_vhost_without_matching_wildcard(self, mock_conflicts):
1517        mock_conflicts.return_value = False
1518        mock_path = "certbot_apache._internal.display_ops.select_vhost"
1519        with mock.patch(mock_path, lambda _, vhosts: vhosts[0]):
1520            for name in ("a.example.net", "other.example.net"):
1521                self.assertTrue(name in self.config.choose_vhost(name).aliases)
1522
1523    @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts")
1524    def test_choose_vhost_wildcard_not_found(self, mock_conflicts):
1525        mock_conflicts.return_value = False
1526        mock_path = "certbot_apache._internal.display_ops.select_vhost"
1527        names = (
1528            "abc.example.net", "not.there.tld", "aa.wildcard.tld"
1529        )
1530        with mock.patch(mock_path) as mock_select:
1531            mock_select.return_value = self.config.vhosts[0]
1532            for name in names:
1533                orig_cc = mock_select.call_count
1534                self.config.choose_vhost(name)
1535                self.assertEqual(mock_select.call_count - orig_cc, 1)
1536
1537    def test_choose_vhost_wildcard_found(self):
1538        mock_path = "certbot_apache._internal.display_ops.select_vhost"
1539        names = (
1540            "ab.example.net", "a.wildcard.tld", "yetanother.example.net"
1541        )
1542        with mock.patch(mock_path) as mock_select:
1543            mock_select.return_value = self.config.vhosts[0]
1544            for name in names:
1545                self.config.choose_vhost(name)
1546                self.assertEqual(mock_select.call_count, 0)
1547
1548    def test_augeas_span_error(self):
1549        broken_vhost = self.config.vhosts[0]
1550        broken_vhost.path = broken_vhost.path + "/nonexistent"
1551        self.assertRaises(errors.PluginError, self.config.make_vhost_ssl,
1552                          broken_vhost)
1553
1554class MultiVhostsTest(util.ApacheTest):
1555    """Test configuration with multiple virtualhosts in a single file."""
1556    # pylint: disable=protected-access
1557
1558    def setUp(self):  # pylint: disable=arguments-differ
1559        td = "debian_apache_2_4/multi_vhosts"
1560        cr = "debian_apache_2_4/multi_vhosts/apache2"
1561        vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available"
1562        super().setUp(test_dir=td,
1563                      config_root=cr,
1564                      vhost_root=vr)
1565
1566        self.config = util.get_apache_configurator(
1567            self.config_path, self.vhost_path,
1568            self.config_dir, self.work_dir, conf_vhost_path=self.vhost_path)
1569        self.vh_truth = util.get_vh_truth(
1570            self.temp_dir, "debian_apache_2_4/multi_vhosts")
1571
1572    def test_make_vhost_ssl(self):
1573        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
1574
1575        self.assertEqual(
1576            ssl_vhost.filep,
1577            os.path.join(self.config_path, "sites-available",
1578                         "default-le-ssl.conf"))
1579
1580        self.assertEqual(ssl_vhost.path,
1581                         "/files" + ssl_vhost.filep + "/IfModule/VirtualHost")
1582        self.assertEqual(len(ssl_vhost.addrs), 1)
1583        self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
1584        self.assertEqual(ssl_vhost.name, "banana.vomit.com")
1585        self.assertTrue(ssl_vhost.ssl)
1586        self.assertFalse(ssl_vhost.enabled)
1587
1588
1589        self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]),
1590                         self.config.is_name_vhost(ssl_vhost))
1591
1592        mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path"
1593        with mock.patch(mock_path) as mock_getpath:
1594            mock_getpath.return_value = None
1595            self.assertRaises(errors.PluginError, self.config.make_vhost_ssl,
1596                              self.vh_truth[1])
1597
1598    def test_get_new_path(self):
1599        with_index_1 = ["/path[1]/section[1]"]
1600        without_index = ["/path/section"]
1601        with_index_2 = ["/path[2]/section[2]"]
1602        self.assertEqual(self.config._get_new_vh_path(without_index,
1603                                                      with_index_1),
1604                         None)
1605        self.assertEqual(self.config._get_new_vh_path(without_index,
1606                                                      with_index_2),
1607                         with_index_2[0])
1608
1609        both = with_index_1 + with_index_2
1610        self.assertEqual(self.config._get_new_vh_path(without_index, both),
1611                         with_index_2[0])
1612
1613    @mock.patch("certbot_apache._internal.configurator.display_util.notify")
1614    def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify):
1615        self.config.parser.modules["rewrite_module"] = None
1616
1617        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4])
1618
1619        self.assertTrue(self.config.parser.find_dir(
1620            "RewriteEngine", "on", ssl_vhost.path, False))
1621
1622        with open(ssl_vhost.filep) as the_file:
1623            conf_text = the_file.read()
1624        commented_rewrite_rule = ("# RewriteRule \"^/secrets/(.+)\" "
1625                                  "\"https://new.example.com/docs/$1\" [R,L]")
1626        uncommented_rewrite_rule = ("RewriteRule \"^/docs/(.+)\"  "
1627                                    "\"http://new.example.com/docs/$1\"  [R,L]")
1628        self.assertTrue(commented_rewrite_rule in conf_text)
1629        self.assertTrue(uncommented_rewrite_rule in conf_text)
1630        self.assertEqual(mock_notify.call_count, 1)
1631        self.assertIn("Some rewrite rules", mock_notify.call_args[0][0])
1632
1633    @mock.patch("certbot_apache._internal.configurator.display_util.notify")
1634    def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_notify):
1635        self.config.parser.modules["rewrite_module"] = None
1636
1637        ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
1638
1639        with open(ssl_vhost.filep) as the_file:
1640            conf_lines = the_file.readlines()
1641        conf_line_set = [l.strip() for l in conf_lines]
1642        not_commented_cond1 = ("RewriteCond "
1643                "%{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f")
1644        not_commented_rewrite_rule = ("RewriteRule "
1645            "^(.*)$ b://u%{REQUEST_URI} [P,NE,L]")
1646
1647        commented_cond1 = "# RewriteCond %{HTTPS} !=on"
1648        commented_cond2 = "# RewriteCond %{HTTPS} !^$"
1649        commented_rewrite_rule = ("# RewriteRule ^ "
1650                                  "https://%{SERVER_NAME}%{REQUEST_URI} "
1651                                  "[L,NE,R=permanent]")
1652
1653        self.assertTrue(not_commented_cond1 in conf_line_set)
1654        self.assertTrue(not_commented_rewrite_rule in conf_line_set)
1655
1656        self.assertTrue(commented_cond1 in conf_line_set)
1657        self.assertTrue(commented_cond2 in conf_line_set)
1658        self.assertTrue(commented_rewrite_rule in conf_line_set)
1659        self.assertEqual(mock_notify.call_count, 1)
1660        self.assertIn("Some rewrite rules", mock_notify.call_args[0][0])
1661
1662
1663class InstallSslOptionsConfTest(util.ApacheTest):
1664    """Test that the options-ssl-nginx.conf file is installed and updated properly."""
1665
1666    def setUp(self): # pylint: disable=arguments-differ
1667        super().setUp()
1668
1669        self.config = util.get_apache_configurator(
1670            self.config_path, self.vhost_path, self.config_dir, self.work_dir)
1671
1672    def _call(self):
1673        self.config.install_ssl_options_conf(self.config.mod_ssl_conf,
1674                                             self.config.updated_mod_ssl_conf_digest)
1675
1676    def _current_ssl_options_hash(self):
1677        return crypto_util.sha256sum(self.config.pick_apache_config())
1678
1679    def _assert_current_file(self):
1680        self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
1681        self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
1682            self._current_ssl_options_hash())
1683
1684    def test_no_file(self):
1685        # prepare should have placed a file there
1686        self._assert_current_file()
1687        os.remove(self.config.mod_ssl_conf)
1688        self.assertFalse(os.path.isfile(self.config.mod_ssl_conf))
1689        self._call()
1690        self._assert_current_file()
1691
1692    def test_current_file(self):
1693        self._assert_current_file()
1694        self._call()
1695        self._assert_current_file()
1696
1697    def test_prev_file_updates_to_current(self):
1698        from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
1699        ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match")
1700        with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256:
1701            mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0]
1702            self._call()
1703        self._assert_current_file()
1704
1705    def test_manually_modified_current_file_does_not_update(self):
1706        with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf:
1707            mod_ssl_conf.write("a new line for the wrong hash\n")
1708        with mock.patch("certbot.plugins.common.logger") as mock_logger:
1709            self._call()
1710            self.assertFalse(mock_logger.warning.called)
1711        self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
1712        self.assertEqual(crypto_util.sha256sum(
1713            self.config.pick_apache_config()),
1714            self._current_ssl_options_hash())
1715        self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
1716            self._current_ssl_options_hash())
1717
1718    def test_manually_modified_past_file_warns(self):
1719        with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf:
1720            mod_ssl_conf.write("a new line for the wrong hash\n")
1721        with open(self.config.updated_mod_ssl_conf_digest, "w") as f:
1722            f.write("hashofanoldversion")
1723        with mock.patch("certbot.plugins.common.logger") as mock_logger:
1724            self._call()
1725            self.assertEqual(mock_logger.warning.call_args[0][0],
1726                "%s has been manually modified; updated file "
1727                "saved to %s. We recommend updating %s for security purposes.")
1728        self.assertEqual(crypto_util.sha256sum(
1729            self.config.pick_apache_config()),
1730            self._current_ssl_options_hash())
1731        # only print warning once
1732        with mock.patch("certbot.plugins.common.logger") as mock_logger:
1733            self._call()
1734            self.assertFalse(mock_logger.warning.called)
1735
1736    def test_ssl_config_files_hash_in_all_hashes(self):
1737        """
1738        It is really critical that all TLS Apache config files have their SHA256 hash registered in
1739        constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config
1740        file has been manually edited by the user, and will refuse to update it.
1741        This test ensures that all necessary hashes are present.
1742        """
1743        from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
1744        import pkg_resources
1745
1746        tls_configs_dir = pkg_resources.resource_filename(
1747            "certbot_apache", os.path.join("_internal", "tls_configs"))
1748        all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir)
1749                     if name.endswith('options-ssl-apache.conf')]
1750        self.assertTrue(all_files)
1751        for one_file in all_files:
1752            file_hash = crypto_util.sha256sum(one_file)
1753            self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES,
1754                            "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 "
1755                            "hash of {0} when it is updated.".format(one_file))
1756
1757    def test_openssl_version(self):
1758        self.config._openssl_version = None
1759        some_string_contents = b"""
1760            SSLOpenSSLConfCmd
1761            OpenSSL configuration command
1762            SSLv3 not supported by this version of OpenSSL
1763            '%s': invalid OpenSSL configuration command
1764            OpenSSL 1.0.2g  1 Mar 2016
1765            OpenSSL
1766            AH02407: "SSLOpenSSLConfCmd %s %s" failed for %s
1767            AH02556: "SSLOpenSSLConfCmd %s %s" applied to %s
1768            OpenSSL 1.0.2g  1 Mar 2016
1769            """
1770        # ssl_module as a DSO
1771        self.config.parser.modules['ssl_module'] = '/fake/path'
1772        with mock.patch("certbot_apache._internal.configurator."
1773            "ApacheConfigurator._open_module_file") as mock_omf:
1774            mock_omf.return_value = some_string_contents
1775            self.assertEqual(self.config.openssl_version(), "1.0.2g")
1776
1777        # ssl_module statically linked
1778        self.config._openssl_version = None
1779        self.config.parser.modules['ssl_module'] = None
1780        self.config.options.bin = '/fake/path/to/httpd'
1781        with mock.patch("certbot_apache._internal.configurator."
1782            "ApacheConfigurator._open_module_file") as mock_omf:
1783            mock_omf.return_value = some_string_contents
1784            self.assertEqual(self.config.openssl_version(), "1.0.2g")
1785
1786    def test_current_version(self):
1787        self.config.version = (2, 4, 10)
1788        self.config._openssl_version = '1.0.2m'
1789        self.assertTrue('old' in self.config.pick_apache_config())
1790
1791        self.config.version = (2, 4, 11)
1792        self.config._openssl_version = '1.0.2m'
1793        self.assertTrue('current' in self.config.pick_apache_config())
1794
1795        self.config._openssl_version = '1.0.2a'
1796        self.assertTrue('old' in self.config.pick_apache_config())
1797
1798    def test_openssl_version_warns(self):
1799        self.config._openssl_version = '1.0.2a'
1800        self.assertEqual(self.config.openssl_version(), '1.0.2a')
1801
1802        self.config._openssl_version = None
1803        with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
1804            self.assertEqual(self.config.openssl_version(), None)
1805            self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
1806
1807        # When no ssl_module is present at all
1808        self.config._openssl_version = None
1809        self.assertTrue("ssl_module" not in self.config.parser.modules)
1810        with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
1811            self.assertEqual(self.config.openssl_version(), None)
1812            self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
1813
1814        # When ssl_module is statically linked but --apache-bin not provided
1815        self.config._openssl_version = None
1816        self.config.options.bin = None
1817        self.config.parser.modules['ssl_module'] = None
1818        with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
1819            self.assertEqual(self.config.openssl_version(), None)
1820            self.assertTrue("ssl_module is statically linked but" in mock_log.call_args[0][0])
1821
1822        self.config.parser.modules['ssl_module'] = "/fake/path"
1823        with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
1824            # Check that correct logger.warning was printed
1825            self.assertEqual(self.config.openssl_version(), None)
1826            self.assertTrue("Unable to read" in mock_log.call_args[0][0])
1827
1828        contents_missing_openssl = b"these contents won't match the regex"
1829        with mock.patch("certbot_apache._internal.configurator."
1830            "ApacheConfigurator._open_module_file") as mock_omf:
1831            mock_omf.return_value = contents_missing_openssl
1832            with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
1833                # Check that correct logger.warning was printed
1834                self.assertEqual(self.config.openssl_version(), None)
1835                self.assertTrue("Could not find OpenSSL" in mock_log.call_args[0][0])
1836
1837    def test_open_module_file(self):
1838        mock_open = mock.mock_open(read_data="testing 12 3")
1839        with mock.patch("builtins.open", mock_open):
1840            self.assertEqual(self.config._open_module_file("/nonsense/"), "testing 12 3")
1841
1842if __name__ == "__main__":
1843    unittest.main()  # pragma: no cover
1844