in progress change

This commit is contained in:
Noah Swartz
2016-08-24 16:18:23 -07:00
parent 2fa15641e3
commit 51afe06ff7
7 changed files with 96 additions and 61 deletions
+11 -1
View File
@@ -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 @@ class AuthHandler(object):
"""
# 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
+40
View File
@@ -13,6 +13,8 @@ import six
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
+1 -1
View File
@@ -186,7 +186,7 @@ class Client(object):
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
+13 -34
View File
@@ -3,6 +3,7 @@ import argparse
import collections
import logging
import socket
import sys
import threading
import OpenSSL
@@ -12,6 +13,7 @@ import zope.interface
from acme import challenges
from acme import standalone as acme_standalone
from certbot import cli
from certbot import errors
from certbot import interfaces
@@ -110,38 +112,17 @@ class ServerManager(object):
in six.iteritems(self._instances))
SUPPORTED_CHALLENGES = [challenges.TLSSNI01, challenges.HTTP01]
def supported_challenges_validator(data):
"""Supported challenges validator for the `argparse`.
It should be passed as `type` argument to `add_argument`.
"""
challs = data.split(",")
# tls-sni-01 was dvsni during private beta
if "dvsni" in challs:
logger.info("Updating legacy standalone_supported_challenges value")
challs = [challenges.TLSSNI01.typ if chall == "dvsni" else chall
for chall in challs]
data = ",".join(challs)
unrecognized = [name for name in challs
if name not in challenges.Challenge.TYPES]
if unrecognized:
raise argparse.ArgumentTypeError(
"Unrecognized challenges: {0}".format(", ".join(unrecognized)))
choices = set(chall.typ for chall in SUPPORTED_CHALLENGES)
if not set(challs).issubset(choices):
raise argparse.ArgumentTypeError(
"Plugin does not support the following (valid) "
"challenges: {0}".format(", ".join(set(challs) - choices)))
return data
class supported_challenges_wrapper(argparse.Action):
"""Wrapper for the depricated supported challenges flag"""
def __call__(self, parser, namespace, pref_chall, option_string=None):
# print deprecation warning
sys.stderr.write("WARNING: The standalone specific supported challenges flag is depricated")
sys.stderr.write("\nPlease use the --preferred-challenges flag instead.\n")
#call cli version - move namespace back into it
_ = cli.add_pref_challs(namespace, pref_chall)
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
@@ -178,14 +159,12 @@ class Authenticator(common.Plugin):
def add_parser_arguments(cls, add):
add("supported-challenges",
help="Supported challenges. Preferred in the order they are listed.",
type=supported_challenges_validator,
default=",".join(chall.typ for chall in SUPPORTED_CHALLENGES))
action=supported_challenges_wrapper, dest="pref_chall")
@property
def supported_challenges(self):
"""Challenges supported by this plugin."""
return [challenges.Challenge.TYPES[name] for name in
self.conf("supported-challenges").split(",")]
return self.config.pref_chall
@property
def _necessary_ports(self):
@@ -208,7 +187,7 @@ class Authenticator(common.Plugin):
def get_chall_pref(self, domain):
# pylint: disable=unused-argument,missing-docstring
return self.supported_challenges
return [challenges.TLSSNI01, challenges.HTTP01]
def perform(self, achalls): # pylint: disable=missing-docstring
renewer = self.config.verb == "renew"
+10 -22
View File
@@ -67,29 +67,17 @@ class ServerManagerTest(unittest.TestCase):
class SupportedChallengesValidatorTest(unittest.TestCase):
"""Tests for plugins.standalone.supported_challenges_validator."""
def _call(self, data):
from certbot.plugins.standalone import (
supported_challenges_validator)
return supported_challenges_validator(data)
def test_correct(self):
self.assertEqual("tls-sni-01", self._call("tls-sni-01"))
self.assertEqual("http-01", self._call("http-01"))
self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01"))
def test_unrecognized(self):
assert "foo" not in challenges.Challenge.TYPES
self.assertRaises(argparse.ArgumentTypeError, self._call, "foo")
def test_not_subset(self):
self.assertRaises(argparse.ArgumentTypeError, self._call, "dns")
def test_dvsni(self):
self.assertEqual("tls-sni-01", self._call("dvsni"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,dvsni"))
self.assertEqual("tls-sni-01,http-01", self._call("dvsni,http-01"))
def setUp(self):
self.parser = argparse.ArgumentParser()
from certbot.plugins import standalone
standalone.Authenticator.add_parser_arguments(self.parser.add_argument)
def test_standalone_flag(self):
config = self.parser.parse_args(["--supported_challenges", "http-01"])
http = challenges.Challenge.TYPES["http-01"]
tls = challenges.Challenge.TYPES["tls-sni-01"]
print config
self.assertEqual(config.pref_chall, [tls, http])
class AuthenticatorTest(unittest.TestCase):
"""Tests for certbot.plugins.standalone.Authenticator."""
+3 -3
View File
@@ -24,7 +24,7 @@ class ChallengeFactoryTest(unittest.TestCase):
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 @@ class GetAuthorizationsTest(unittest.TestCase):
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 @@ class PollChallengesTest(unittest.TestCase):
# 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(
+18
View File
@@ -1035,6 +1035,24 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
namespace = parse(short_args)
self.assertTrue(namespace.text_mode)
#TODO massage this to work in cli
def test_correct(self):
self.assertEqual("tls-sni-01", self._call("tls-sni-01"))
self.assertEqual("http-01", self._call("http-01"))
self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01"))
def test_unrecognized(self):
assert "foo" not in challenges.Challenge.TYPES
self.assertRaises(argparse.ArgumentTypeError, self._call, "foo")
def test_not_subset(self):
self.assertRaises(argparse.ArgumentTypeError, self._call, "dns")
def test_dvsni(self):
self.assertEqual("tls-sni-01", self._call("dvsni"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,dvsni"))
self.assertEqual("tls-sni-01,http-01", self._call("dvsni,http-01"))
class DetermineAccountTest(unittest.TestCase):
"""Tests for certbot.cli._determine_account."""