Repository: certbot/certbot

Issue:
Asks about HTTPS redirect despite installer not supporting it
Hi,

I'm writing an installer plugin that will not support enhancements, and thus returns none:

```
def supported_enhancements(self):
    return []
```

Nevertheless, on letsencrypt install (with this plugin), I get the choice “Please choose whether HTTPS access is required or optional.”, and then I can choose between Easy or Secure. Shouldn't it check with the installer if it can do the “redirect” enhancement before it asks? It just calls enhance() with no more ifs and buts, which fails.



Discussion:
There's actually a TODO about this in the `enhance_config` function in `client.py`. Thanks for creating this issue though. Github is certainly a better place to track the issue than a TODO comment.

The relevant change needs to be made in `enhance_config`.


Resolving patch:
diff --git a/letsencrypt/client.py b/letsencrypt/client.py
--- a/letsencrypt/client.py
+++ b/letsencrypt/client.py
@@ -407,9 +407,10 @@ def enhance_config(self, domains, config):
             logger.warning("No config is specified.")
             raise errors.Error("No config available")
 
-        redirect = config.redirect
-        hsts = config.hsts
-        uir = config.uir # Upgrade Insecure Requests
+        supported = self.installer.supported_enhancements()
+        redirect = config.redirect if "redirect" in supported else False
+        hsts = config.hsts if "ensure-http-header" in supported else False
+        uir = config.uir if "ensure-http-header" in supported else False
 
         if redirect is None:
             redirect = enhancements.ask("redirect")


Test patch:
diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py
--- a/letsencrypt/tests/client_test.py
+++ b/letsencrypt/tests/client_test.py
@@ -240,6 +240,7 @@ def test_enhance_config(self, mock_enhancements):
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect"]
 
         self.client.enhance_config(["foo.bar"], config)
         installer.enhance.assert_called_once_with("foo.bar", "redirect", None)
@@ -255,6 +256,7 @@ def test_enhance_config_no_ask(self, mock_enhancements):
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect", "ensure-http-header"]
 
         config = ConfigHelper(redirect=True, hsts=False, uir=False)
         self.client.enhance_config(["foo.bar"], config)
@@ -273,6 +275,17 @@ def test_enhance_config_no_ask(self, mock_enhancements):
         self.assertEqual(installer.save.call_count, 3)
         self.assertEqual(installer.restart.call_count, 3)
 
+    @mock.patch("letsencrypt.client.enhancements")
+    def test_enhance_config_unsupported(self, mock_enhancements):
+        installer = mock.MagicMock()
+        self.client.installer = installer
+        installer.supported_enhancements.return_value = []
+
+        config = ConfigHelper(redirect=None, hsts=True, uir=True)
+        self.client.enhance_config(["foo.bar"], config)
+        installer.enhance.assert_not_called()
+        mock_enhancements.ask.assert_not_called()
+
     def test_enhance_config_no_installer(self):
         config = ConfigHelper(redirect=True, hsts=False, uir=False)
         self.assertRaises(errors.Error,
@@ -285,6 +298,7 @@ def test_enhance_config_enhance_failure(self, mock_enhancements,
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect"]
         installer.enhance.side_effect = errors.PluginError
 
         config = ConfigHelper(redirect=True, hsts=False, uir=False)
@@ -301,6 +315,7 @@ def test_enhance_config_save_failure(self, mock_enhancements,
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect"]
         installer.save.side_effect = errors.PluginError
 
         config = ConfigHelper(redirect=True, hsts=False, uir=False)
@@ -317,6 +332,7 @@ def test_enhance_config_restart_failure(self, mock_enhancements,
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect"]
         installer.restart.side_effect = [errors.PluginError, None]
 
         config = ConfigHelper(redirect=True, hsts=False, uir=False)
@@ -335,6 +351,7 @@ def test_enhance_config_restart_failure2(self, mock_enhancements,
         mock_enhancements.ask.return_value = True
         installer = mock.MagicMock()
         self.client.installer = installer
+        installer.supported_enhancements.return_value = ["redirect"]
         installer.restart.side_effect = errors.PluginError
         installer.rollback_checkpoints.side_effect = errors.ReverterError