Rationalise challenge and port selection flags #3343 [see #3466])
diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py
index a9473457202..dcde3f9a7a5 100644
--- a/certbot/auth_handler.py
+++ b/certbot/auth_handler.py
@@ -33,14 +33,16 @@ class AuthHandler(object):
         and values are :class:`acme.messages.AuthorizationResource`
     :ivar list achalls: DV challenges in the form of
         :class:`certbot.achallenges.AnnotatedChallenge`
+    :ivar list pref_challs: A list of user specified preferred challenges
 
     """
-    def __init__(self, auth, acme, account):
+    def __init__(self, auth, acme, account, pref_challs):
         self.auth = auth
         self.acme = acme
 
         self.account = account
         self.authzr = dict()
+        self.pref_challs = pref_challs
 
         # List must be used to keep responses straight.
         self.achalls = []
@@ -246,6 +248,14 @@ def _get_chall_pref(self, domain):
         """
         # Make sure to make a copy...
         chall_prefs = []
+        plugin_pref = self.auth.get_chall_pref(domain)
+        if self.pref_challs:
+            out = [pref for pref in self.pref_challs if pref in plugin_pref]
+            if out:
+                return out
+            else:
+                raise errors.AuthorizationError(
+                        "None of the selected challenges are supported by the selected plugins")
         chall_prefs.extend(self.auth.get_chall_pref(domain))
         return chall_prefs
 
diff --git a/certbot/cli.py b/certbot/cli.py
index 46ff74cd087..a0cd9b173bb 100644
--- a/certbot/cli.py
+++ b/certbot/cli.py
@@ -13,6 +13,8 @@
 
 import certbot
 
+from acme import challenges
+
 from certbot import constants
 from certbot import crypto_util
 from certbot import errors
@@ -844,6 +846,13 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):  # pylint: dis
         "security", "--strict-permissions", action="store_true",
         help="Require that all configuration files are owned by the current "
              "user; only needed if your config is somewhere unsafe like /tmp/")
+    helpful.add(
+        "security", "--preferred-challenges", dest="pref_chall",
+        action=_PrefChallAction, default=[],
+        help="Specify which challenges you'd prefer to use. If any of those "
+             "challenges are valid for your authenticator they will be used. "
+             "Otherwise Certbot will not attempt authorization. The first "
+             "challenge listed that is supported by the plugin will be used.")
     helpful.add(
         "renew", "--pre-hook",
         help="Command to be run in a shell before obtaining any certificates."
@@ -1032,3 +1041,34 @@ def add_domains(args_or_config, domains):
             args_or_config.domains.append(domain)
 
     return validated_domains
+
+class _PrefChallAction(argparse.Action):
+    """Action class for parsing preferred challenges."""
+
+    def __call__(self, parser, namespace, pref_chall, option_string=None):
+        """Just wrap add_pref_challs in argparseese."""
+        _ = add_pref_challs(namespace, pref_chall)
+
+def add_pref_challs(namespace, pref_challs):
+    """Parses and validates user specified challenge types.
+
+    Adds challenges (in order) to the configuration object.
+
+    :param namespace: parsed command line arguments
+    :type namespace: argparse.Namespace or
+        configuration.NamespaceConfig
+    :param str pref_challs: one or more comma separated challenge types
+
+    :returns: Challenge objects which match the validated string inputs
+    :rtype: `list`
+    """
+    challs = pref_challs.split(",")
+    unrecognized = [name for name in challs if name not in challenges.Challenge.TYPES]
+    if unrecognized:
+        raise argparse.ArgumentTypeError(
+                "Unrecognized challenges: {0}".format(", ".join(unrecognized)))
+
+    out = [challenges.Challenge.TYPES[name] for name in challs]
+    print(namespace)
+    namespace.pref_chall.extend(out)
+    return out
diff --git a/certbot/client.py b/certbot/client.py
index ef59c6ce3b8..66e90bb1f37 100644
--- a/certbot/client.py
+++ b/certbot/client.py
@@ -192,7 +192,7 @@ def __init__(self, config, account_, auth, installer, acme=None):
 
         if auth is not None:
             self.auth_handler = auth_handler.AuthHandler(
-                auth, self.acme, self.account)
+                auth, self.acme, self.account, self.config.pref_chall)
         else:
             self.auth_handler = None
 
diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py
index 97aca351a47..c00f3005285 100644
--- a/certbot/plugins/standalone.py
+++ b/certbot/plugins/standalone.py
@@ -3,6 +3,7 @@
 import collections
 import logging
 import socket
+import sys
 import threading
 
 import OpenSSL
@@ -119,6 +120,8 @@ def supported_challenges_validator(data):
     It should be passed as `type` argument to `add_argument`.
 
     """
+    sys.stderr.write("WARNING: The standalone specific supported challenges flag is depricated")
+    sys.stderr.write("\nPlease use the --preferred-challenges flag instead.\n")
     challs = data.split(",")
 
     # tls-sni-01 was dvsni during private beta
@@ -177,7 +180,7 @@ def __init__(self, *args, **kwargs):
     @classmethod
     def add_parser_arguments(cls, add):
         add("supported-challenges",
-            help="Supported challenges. Preferred in the order they are listed.",
+            help=argparse.SUPPRESS,
             type=supported_challenges_validator,
             default=",".join(chall.typ for chall in SUPPORTED_CHALLENGES))
 
diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py
index fce130f7c43..e6e2445d9fe 100644
--- a/certbot/tests/auth_handler_test.py
+++ b/certbot/tests/auth_handler_test.py
@@ -24,7 +24,7 @@ def setUp(self):
         from certbot.auth_handler import AuthHandler
 
         # Account is mocked...
-        self.handler = AuthHandler(None, None, mock.Mock(key="mock_key"))
+        self.handler = AuthHandler(None, None, mock.Mock(key="mock_key"), [])
 
         self.dom = "test"
         self.handler.authzr[self.dom] = acme_util.gen_authzr(
@@ -74,7 +74,7 @@ def setUp(self):
         self.mock_net = mock.MagicMock(spec=acme_client.Client)
 
         self.handler = AuthHandler(
-            self.mock_auth, self.mock_net, self.mock_account)
+            self.mock_auth, self.mock_net, self.mock_account, [])
 
         logging.disable(logging.CRITICAL)
 
@@ -189,7 +189,7 @@ def setUp(self):
         # Account and network are mocked...
         self.mock_net = mock.MagicMock()
         self.handler = AuthHandler(
-            None, self.mock_net, mock.Mock(key="mock_key"))
+            None, self.mock_net, mock.Mock(key="mock_key"), [])
 
         self.doms = ["0", "1", "2"]
         self.handler.authzr[self.doms[0]] = acme_util.gen_authzr(
diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py
index 2c6e3270515..d011be9574c 100644
--- a/certbot/tests/cli_test.py
+++ b/certbot/tests/cli_test.py
@@ -1035,7 +1035,6 @@ def test_text_mode_when_verbose(self):
         namespace = parse(short_args)
         self.assertTrue(namespace.text_mode)
 
-
 class DetermineAccountTest(unittest.TestCase):
     """Tests for certbot.cli._determine_account."""
 

resolves issue #3343 
