Revisions through running/testing

This commit is contained in:
James Kasten
2015-02-19 04:13:39 -08:00
parent 08fc0852d7
commit 6c8eb8be17
14 changed files with 257 additions and 86 deletions
+9 -3
View File
@@ -942,9 +942,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return tuple([int(i) for i in matches[0].split('.')]) return tuple([int(i) for i in matches[0].split('.')])
def __str__(self): def more_info(self):
return "Apache version %s" % ".".join(self.get_version()) """Human-readable string to help understand the module"""
return (
"Configures Apache to authenticate and install HTTPS.{0}"
"Server root: {root}{0}"
"Version: {version}".format(
os.linesep, root=self.parser.loc["root"],
version=".".join(str(i) for i in self.version))
)
########################################################################### ###########################################################################
# Challenges Section # Challenges Section
+47 -8
View File
@@ -19,6 +19,7 @@ from letsencrypt.client import le_util
from letsencrypt.client import network from letsencrypt.client import network
from letsencrypt.client import reverter from letsencrypt.client import reverter
from letsencrypt.client import revoker from letsencrypt.client import revoker
from letsencrypt.client import standalone_authenticator
from letsencrypt.client.apache import configurator from letsencrypt.client.apache import configurator
from letsencrypt.client.display import ops from letsencrypt.client.display import ops
@@ -102,7 +103,8 @@ class Client(object):
cert_file, chain_file = self.save_certificate( cert_file, chain_file = self.save_certificate(
certificate_msg, self.config.cert_path, self.config.chain_path) certificate_msg, self.config.cert_path, self.config.chain_path)
revoker.Revoker.store_cert_key(cert_file, self.authkey.file, False) revoker.Revoker.store_cert_key(
cert_file, self.authkey.file, self.config)
return cert_file, chain_file return cert_file, chain_file
@@ -350,16 +352,53 @@ def determine_authenticator(config):
:param config: Configuration. :param config: Configuration.
:type config: :class:`letsencrypt.client.interfaces.IConfig` :type config: :class:`letsencrypt.client.interfaces.IConfig`
:returns: Valid Authenticator object or None
""" """
auths = [] # list of (Description, Known Authenticator classes, init arguments)
all_auths = [
("Apache Web Server", configurator.ApacheConfigurator, config),
("Standalone Authenticator",
standalone_authenticator.StandaloneAuthenticator),
]
# Available Authenticator objects
avail_auths = []
# Error messages for misconfigured authenticators
errs = {}
for pot_auth in all_auths:
try: try:
auths.append(configurator.ApacheConfigurator(config)) # I do not think this a great solution but haven't come up with
# anything better yet...
if len(pot_auth) == 2:
# pylint: disable=no-value-for-parameter
avail_auths.append((pot_auth[0], pot_auth[1]()))
elif len(pot_auth) == 3:
avail_auths.append((pot_auth[0], pot_auth[1](pot_auth[2])))
else:
errors.LetsEncryptClientError(
"IAuthenticator: Number of parameters not supported")
except errors.LetsEncryptMisconfigurationError as err:
errs[pot_auth[1]] = err
avail_auths.append((pot_auth[0], pot_auth[1]))
except errors.LetsEncryptNoInstallationError: except errors.LetsEncryptNoInstallationError:
logging.info("Unable to determine a way to authenticate the server") pass
if len(auths) > 1:
return ops.choose_authenticator(auths) if len(avail_auths) > 1:
elif len(auths) == 1: auth = ops.choose_authenticator(avail_auths, errs)
return auths[0] elif len(avail_auths) == 1:
auth = avail_auths[0]
else:
auth = None
if auth in errs:
logging.error("Please fix the configuration for the Authenticator. "
"The following error message was received: "
"%s", errs[auth])
sys.exit(1)
return auth
def determine_installer(config): def determine_installer(config):
+20 -6
View File
@@ -11,25 +11,39 @@ from letsencrypt.client.display import util as display_util
util = zope.component.getUtility # pylint: disable=invalid-name util = zope.component.getUtility # pylint: disable=invalid-name
def choose_authenticator(auths): def choose_authenticator(auths, errs):
"""Allow the user to choose their authenticator. """Allow the user to choose their authenticator.
:param list auths: Where each is a :param list auths: Where each is a tuple of the form
:class:`letsencrypt.client.interfaces.IAuthenticator` object ('description', 'IAuthenticator') where IAuthenticator is a
:class:`letsencrypt.client.interfaces.IAuthenticator` object or class
:param dict errs: Mapping IAuthenticator objects to error messages
:returns: Authenticator selected :returns: Authenticator selected
:rtype: :class:`letsencrypt.client.interfaces.IAuthenticator` :rtype: :class:`letsencrypt.client.interfaces.IAuthenticator`
""" """
descs = [auth[0] for auth in auths]
iauths = [auth[1] for auth in auths]
while True:
code, index = util(interfaces.IDisplay).menu( code, index = util(interfaces.IDisplay).menu(
"How would you like to authenticate with the Let's Encrypt CA?", "How would you like to authenticate with the Let's Encrypt CA?",
[str(auth) for auth in auths]) descs, help_label="More Info")
if code == display_util.OK: if code == display_util.OK:
return auths[index] return iauths[index]
elif code == display_util.HELP:
if iauths[index] in errs:
msg = "Reported Error: %s" % errs[iauths[index]]
else:
msg = iauths[index].more_info()
util(interfaces.IDisplay).notification(
msg, height=display_util.HEIGHT)
else: else:
sys.exit(0) sys.exit(0)
def choose_names(installer): def choose_names(installer):
"""Display screen to select domains to validate. """Display screen to select domains to validate.
@@ -116,7 +130,7 @@ def _gen_https_names(domains):
return "https://{dom[0]} and https://{dom[1]}".format(dom=domains) return "https://{dom[0]} and https://{dom[1]}".format(dom=domains)
elif len(domains) > 2: elif len(domains) > 2:
return "{0}{1}{2}".format( return "{0}{1}{2}".format(
", ".join("https://" + dom for dom in domains[:-1]), ", ".join("https://%s" % dom for dom in domains[:-1]),
", and https://", ", and https://",
domains[-1]) domains[-1])
-2
View File
@@ -20,9 +20,7 @@ def choose_certs(certs):
""" """
while True: while True:
code, selection = _display_certs(certs) code, selection = _display_certs(certs)
if code == display_util.OK: if code == display_util.OK:
if confirm_revocation(certs[selection]):
return selection return selection
elif code == display_util.HELP: elif code == display_util.HELP:
more_info_cert(certs[selection]) more_info_cert(certs[selection])
+16 -8
View File
@@ -93,11 +93,11 @@ class NcursesDisplay(object):
help_button=help_button, help_label=help_label, help_button=help_button, help_label=help_label,
width=self.width, height=self.height) width=self.width, height=self.height)
if code == OK: if code == CANCEL:
return code, int(tag) - 1
return code, -1 return code, -1
return code, int(tag) - 1
def input(self, message): def input(self, message):
"""Display an input box to the user. """Display an input box to the user.
@@ -108,7 +108,7 @@ class NcursesDisplay(object):
`string` - input entered by the user `string` - input entered by the user
""" """
return self.dialog.inputbox(message) return self.dialog.inputbox(message, width=self.width)
def yesno(self, message, yes_label="Yes", no_label="No"): def yesno(self, message, yes_label="Yes", no_label="No"):
"""Display a Yes/No dialog box """Display a Yes/No dialog box
@@ -232,12 +232,19 @@ class FileDisplay(object):
self.outfile.write("{0}{frame}{msg}{0}{frame}".format( self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
os.linesep, frame=side_frame, msg=message)) os.linesep, frame=side_frame, msg=message))
while True:
ans = raw_input("{yes}/{no}: ".format( ans = raw_input("{yes}/{no}: ".format(
yes=_parens_around_char(yes_label), yes=_parens_around_char(yes_label),
no=_parens_around_char(no_label))) no=_parens_around_char(no_label)))
return (ans.startswith(yes_label[0].lower()) or # Couldn't get pylint indentation right with elif
ans.startswith(yes_label[0].upper())) # elif doesn't matter in this situation
if (ans.startswith(yes_label[0].lower()) or
ans.startswith(yes_label[0].upper())):
return True
if (ans.startswith(no_label[0].lower()) or
ans.startswith(no_label[0].upper())):
return False
def checklist(self, message, tags): def checklist(self, message, tags):
"""Display a checklist. """Display a checklist.
@@ -275,7 +282,7 @@ class FileDisplay(object):
:param list indices: input :param list indices: input
:param list tags: Original tags of the checklist :param list tags: Original tags of the checklist
:returns: tags the user selected :returns: valid tags the user selected
:rtype: :class:`list` of :class:`str` :rtype: :class:`list` of :class:`str`
""" """
@@ -387,7 +394,8 @@ def separate_list_input(input_):
""" """
no_commas = input_.replace(",", " ") no_commas = input_.replace(",", " ")
return [string for string in no_commas.split()] # Each string is naturally unicode, this causes problems with M2Crypto SANs
return [str(string) for string in no_commas.split()]
def _parens_around_char(label): def _parens_around_char(label):
"""Place parens around first character of label. """Place parens around first character of label.
+7
View File
@@ -57,6 +57,13 @@ class IAuthenticator(zope.interface.Interface):
""" """
def more_info(self):
"""Human-readable string to help the user.
Should describe the steps taken and any relevant info to help the user
decide which Authenticator to use.
"""
class IConfig(zope.interface.Interface): class IConfig(zope.interface.Interface):
"""Let's Encrypt user-supplied configuration. """Let's Encrypt user-supplied configuration.
+28 -10
View File
@@ -105,10 +105,12 @@ class Revoker(object):
if certs: if certs:
selection = revocation.choose_certs(certs) selection = revocation.choose_certs(certs)
self._safe_revoke([certs[selection]]) revoked_certs = self._safe_revoke([certs[selection]])
# This is safer than using remove as Revoker.Certs only check # Since we are currently only revoking one cert at a time...
# the DER value of the cert. There could potentially be multiple if revoked_certs:
# backup certs with the same value. # This is safer than using remove as Revoker.Certs only
# check the DER value of the cert. There could potentially
# be multiple backup certs with the same value.
del certs[selection] del certs[selection]
else: else:
logging.info( logging.info(
@@ -116,8 +118,6 @@ class Revoker(object):
"certificates for this server.") "certificates for this server.")
return return
def _populate_saved_certs(self, csha1_vhlist): def _populate_saved_certs(self, csha1_vhlist):
# pylint: disable=no-self-use # pylint: disable=no-self-use
"""Populate a list of all the saved certs. """Populate a list of all the saved certs.
@@ -174,6 +174,9 @@ class Revoker(object):
:param certs: certs intended to be revoked :param certs: certs intended to be revoked
:type certs: :class:`list` of :class:`letsencrypt.client.revoker.Cert` :type certs: :class:`list` of :class:`letsencrypt.client.revoker.Cert`
:returns: certs successfully revoked
:rtype: :class:`list` of :class:`letsencrypt.client.revoker.Cert`
""" """
success_list = [] success_list = []
try: try:
@@ -192,6 +195,8 @@ class Revoker(object):
if success_list: if success_list:
self._remove_certs_keys(success_list) self._remove_certs_keys(success_list)
return success_list
def _acme_revoke(self, cert): def _acme_revoke(self, cert):
"""Revoke the certificate with the ACME server. """Revoke the certificate with the ACME server.
@@ -290,9 +295,9 @@ class Revoker(object):
cls._catalog_files( cls._catalog_files(
config.cert_key_backup, cert_path, key_path, list_path) config.cert_key_backup, cert_path, key_path, list_path)
@classmethod @classmethod
def _catalog_files(cls, backup_dir, cert_path, key_path, list_path): def _catalog_files(cls, backup_dir, cert_path, key_path, list_path):
idx = 0
if os.path.isfile(list_path): if os.path.isfile(list_path):
with open(list_path, "r+b") as csvfile: with open(list_path, "r+b") as csvfile:
csvreader = csv.reader(csvfile) csvreader = csv.reader(csvfile)
@@ -309,8 +314,8 @@ class Revoker(object):
with open(list_path, "wb") as csvfile: with open(list_path, "wb") as csvfile:
csvwriter = csv.writer(csvfile) csvwriter = csv.writer(csvfile)
# You must move the files before appending the row # You must move the files before appending the row
cls._copy_files(backup_dir, "0", cert_path, key_path) cls._copy_files(backup_dir, idx, cert_path, key_path)
csvwriter.writerow(["0", cert_path, key_path]) csvwriter.writerow([str(idx), cert_path, key_path])
@classmethod @classmethod
def _copy_files(cls, backup_dir, idx, cert_path, key_path): def _copy_files(cls, backup_dir, idx, cert_path, key_path):
@@ -481,8 +486,21 @@ class Cert(object):
text.append("Not Before: %s" % str(self.get_not_before())) text.append("Not Before: %s" % str(self.get_not_before()))
text.append("Not After: %s" % str(self.get_not_after())) text.append("Not After: %s" % str(self.get_not_after()))
text.append("Serial Number: %s" % self.get_serial()) text.append("Serial Number: %s" % self.get_serial())
text.append("SHA1: %s" % self.get_fingerprint()) text.append("SHA1: %s%s" % (self.get_fingerprint(), os.linesep))
text.append("Installed: %s" % self.get_installed_msg()) text.append("Installed: %s" % self.get_installed_msg())
if self.orig is not None:
if self.orig.status == "":
text.append("Path: %s" % self.orig.path)
else:
text.append("Orig Path: %s (%s)" % self.orig)
if self.orig_key is not None:
if self.orig_key.status == "":
text.append("Auth Key Path: %s" % self.orig_key.path)
else:
text.append("Orig Auth Key Path: %s (%s)" % self.orig_key)
text.append("")
return os.linesep.join(text) return os.linesep.join(text)
def pretty_print(self): def pretty_print(self):
+13 -4
View File
@@ -149,14 +149,14 @@ class StandaloneAuthenticator(object):
if self.subproc_state == "ready": if self.subproc_state == "ready":
return True return True
elif self.subproc_state == "inuse": elif self.subproc_state == "inuse":
display.generic_notification( display.notification(
"Could not bind TCP port {0} because it is already in " "Could not bind TCP port {0} because it is already in "
"use by another process on this system (such as a web " "use by another process on this system (such as a web "
"server). Please stop the program in question and then " "server). Please stop the program in question and then "
"try again.".format(port)) "try again.".format(port))
return False return False
elif self.subproc_state == "cantbind": elif self.subproc_state == "cantbind":
display.generic_notification( display.notification(
"Could not bind TCP port {0} because you don't have " "Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you " "the appropriate permissions (for example, you "
"aren't running this program as " "aren't running this program as "
@@ -164,7 +164,7 @@ class StandaloneAuthenticator(object):
return False return False
time.sleep(0.1) time.sleep(0.1)
display.generic_notification( display.notification(
"Subprocess unexpectedly timed out while trying to bind TCP " "Subprocess unexpectedly timed out while trying to bind TCP "
"port {0}.".format(port)) "port {0}.".format(port))
@@ -291,7 +291,7 @@ class StandaloneAuthenticator(object):
if listeners: if listeners:
pid, name = listeners[0].split("/") pid, name = listeners[0].split("/")
display = zope.component.getUtility(interfaces.IDisplay) display = zope.component.getUtility(interfaces.IDisplay)
display.generic_notification( display.notification(
"The program {0} (process ID {1}) is already listening " "The program {0} (process ID {1}) is already listening "
"on TCP port {2}. This will prevent us from binding to " "on TCP port {2}. This will prevent us from binding to "
"that port. Please stop the {0} program temporarily " "that port. Please stop the {0} program temporarily "
@@ -406,3 +406,12 @@ class StandaloneAuthenticator(object):
# TODO: restore original signal handlers in parent process # TODO: restore original signal handlers in parent process
# by resetting their actions to SIG_DFL # by resetting their actions to SIG_DFL
# print "TCP listener subprocess has been told to shut down" # print "TCP listener subprocess has been told to shut down"
def more_info(self): # pylint: disable=no-self-use
"""Human-readable string that describes the Authenticator."""
return ("The Standalone Authenticator uses PyOpenSSL to listen "
"on port 443 and perform DVSNI challenges. Once a certificate"
"is attained, it will be saved in the "
"(TODO) current working directory.{0}{0}"
"Port 443 must be open in order to use the "
"Standalone Authenticator.")
+29 -5
View File
@@ -12,25 +12,49 @@ class ChooseAuthenticatorTest(unittest.TestCase):
"""Test choose_authenticator function.""" """Test choose_authenticator function."""
def setUp(self): def setUp(self):
zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
self.mock_apache = mock.Mock()
self.mock_stand = mock.Mock()
self.mock_apache().more_info.return_value = "Apache Info"
self.mock_stand().more_info.return_value = "Standalone Info"
self.auths = [
("Apache Tag", self.mock_apache),
("Standalone Tag", self.mock_stand)
]
self.errs = {self.mock_apache: "This is an error message."}
@classmethod @classmethod
def _call(cls, auths): def _call(cls, auths, errs):
from letsencrypt.client.display.ops import choose_authenticator from letsencrypt.client.display.ops import choose_authenticator
return choose_authenticator(auths) return choose_authenticator(auths, errs)
@mock.patch("letsencrypt.client.display.ops.util") @mock.patch("letsencrypt.client.display.ops.util")
def test_successful_choice(self, mock_util): def test_successful_choice(self, mock_util):
mock_util().menu.return_value = (display_util.OK, 0) mock_util().menu.return_value = (display_util.OK, 0)
ret = self._call(["authenticator1", "auth2"]) ret = self._call(self.auths, {})
self.assertEqual(ret, "authenticator1") self.assertEqual(ret, self.mock_apache)
@mock.patch("letsencrypt.client.display.ops.util")
def test_more_info(self, mock_util):
mock_util().menu.side_effect = [
(display_util.HELP, 0),
(display_util.HELP, 1),
(display_util.OK, 1),
]
ret = self._call(self.auths, self.errs)
self.assertEqual(mock_util().notification.call_count, 2)
self.assertEqual(ret, self.mock_stand)
@mock.patch("letsencrypt.client.display.ops.util") @mock.patch("letsencrypt.client.display.ops.util")
def test_no_choice(self, mock_util): def test_no_choice(self, mock_util):
mock_util().menu.return_value = (display_util.CANCEL, 0) mock_util().menu.return_value = (display_util.CANCEL, 0)
self.assertRaises(SystemExit, self._call, ["authenticator1"]) self.assertRaises(SystemExit, self._call, self.auths, {})
class GenHttpsNamesTest(unittest.TestCase): class GenHttpsNamesTest(unittest.TestCase):
@@ -29,8 +29,7 @@ class ChooseCertsTest(unittest.TestCase):
return choose_certs(certs) return choose_certs(certs)
@mock.patch("letsencrypt.client.display.revocation.util") @mock.patch("letsencrypt.client.display.revocation.util")
def test_confirm_revocation(self, mock_util): def test_revocation(self, mock_util):
mock_util().yesno.return_value = True
mock_util().menu.return_value = (display_util.OK, 0) mock_util().menu.return_value = (display_util.OK, 0)
choice = self._call(self.certs) choice = self._call(self.certs)
@@ -38,12 +37,8 @@ class ChooseCertsTest(unittest.TestCase):
self.assertTrue(self.certs[choice] == self.cert0) self.assertTrue(self.certs[choice] == self.cert0)
@mock.patch("letsencrypt.client.display.revocation.util") @mock.patch("letsencrypt.client.display.revocation.util")
def test_confirm_cancel(self, mock_util): def test_cancel(self, mock_util):
mock_util().yesno.return_value = False mock_util().menu.return_value = (display_util.CANCEL, -1)
mock_util().menu.side_effect = [
(display_util.OK, 0),
(display_util.CANCEL, -1)
]
self.assertRaises(SystemExit, self._call, self.certs) self.assertRaises(SystemExit, self._call, self.certs)
@@ -53,7 +48,6 @@ class ChooseCertsTest(unittest.TestCase):
(display_util.HELP, 1), (display_util.HELP, 1),
(display_util.OK, 1), (display_util.OK, 1),
] ]
mock_util().yesno.return_value = True
choice = self._call(self.certs) choice = self._call(self.certs)
@@ -79,5 +73,25 @@ class SuccessRevocationTest(unittest.TestCase):
self.assertEqual(mock_util().notification.call_count, 1) self.assertEqual(mock_util().notification.call_count, 1)
class ConfirmRevocationTest(unittest.TestCase):
def setUp(self):
from letsencrypt.client.revoker import Cert
self.cert = Cert(pkg_resources.resource_filename(
"letsencrypt.client.tests", os.path.join("testdata", "cert.pem")))
@classmethod
def _call(cls, cert):
from letsencrypt.client.display.revocation import confirm_revocation
return confirm_revocation(cert)
@mock.patch("letsencrypt.client.display.revocation.util")
def test_confirm_revocation(self, mock_util):
mock_util().yesno.return_value = True
self.assertTrue(self._call(self.cert))
mock_util().yesno.return_value = False
self.assertFalse(self._call(self.cert))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+15 -2
View File
@@ -91,6 +91,14 @@ class NcursesDisplayTest(DisplayT):
self.assertEqual(ret, (display_util.OK, 0)) self.assertEqual(ret, (display_util.OK, 0))
@mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu")
def test_menu_desc_only_help(self, mock_menu):
mock_menu.return_value = (display_util.HELP, "2")
ret = self.displayer.menu("Message", self.tags, help_label="More Info")
self.assertEqual(ret, (display_util.HELP, 1))
@mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu") @mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu")
def test_menu_desc_only_cancel(self, mock_menu): def test_menu_desc_only_cancel(self, mock_menu):
mock_menu.return_value = (display_util.CANCEL, "") mock_menu.return_value = (display_util.CANCEL, "")
@@ -103,7 +111,7 @@ class NcursesDisplayTest(DisplayT):
"dialog.Dialog.inputbox") "dialog.Dialog.inputbox")
def test_input(self, mock_input): def test_input(self, mock_input):
self.displayer.input("message") self.displayer.input("message")
mock_input.assert_called_with("message") self.assertEqual(mock_input.call_count, 1)
@mock.patch("letsencrypt.client.display.util.dialog.Dialog.yesno") @mock.patch("letsencrypt.client.display.util.dialog.Dialog.yesno")
def test_yesno(self, mock_yesno): def test_yesno(self, mock_yesno):
@@ -182,8 +190,13 @@ class FileOutputDisplayTest(DisplayT):
self.assertTrue(self.displayer.yesno("message")) self.assertTrue(self.displayer.yesno("message"))
with mock.patch("__builtin__.raw_input", return_value="y"): with mock.patch("__builtin__.raw_input", return_value="y"):
self.assertTrue(self.displayer.yesno("message")) self.assertTrue(self.displayer.yesno("message"))
with mock.patch("__builtin__.raw_input", return_value="cancel"): with mock.patch("__builtin__.raw_input", side_effect=["maybe", "y"]):
self.assertTrue(self.displayer.yesno("message"))
with mock.patch("__builtin__.raw_input", return_value="No"):
self.assertFalse(self.displayer.yesno("message")) self.assertFalse(self.displayer.yesno("message"))
with mock.patch("__builtin__.raw_input", side_effect=["cancel", "n"]):
self.assertFalse(self.displayer.yesno("message"))
with mock.patch("__builtin__.raw_input", return_value="a"): with mock.patch("__builtin__.raw_input", return_value="a"):
self.assertTrue(self.displayer.yesno("msg", yes_label="Agree")) self.assertTrue(self.displayer.yesno("msg", yes_label="Agree"))
+12 -2
View File
@@ -309,11 +309,21 @@ class CertTest(unittest.TestCase):
self.assertEqual(self.certs[0].orig.status, "") self.assertEqual(self.certs[0].orig.status, "")
self.assertEqual(self.certs[0].orig_key.status, "") self.assertEqual(self.certs[0].orig_key.status, "")
def test_print(self): def test_print_meta(self):
"""Just make sure there aren't any errors.""" """Just make sure there aren't any major errors."""
self.certs[0].add_meta(
0, self.paths[0], self.key_path, self.paths[0], self.key_path)
# Changed path and deleted file
self.certs[1].add_meta(
1, self.paths[0], "/not/a/path", self.paths[1], self.key_path)
self.assertTrue(self.certs[0].pretty_print()) self.assertTrue(self.certs[0].pretty_print())
self.assertTrue(self.certs[1].pretty_print()) self.assertTrue(self.certs[1].pretty_print())
def test_print_no_meta(self):
self.assertTrue(self.certs[0].pretty_print())
self.assertTrue(self.certs[1].pretty_print())
def create_revoker_certs(): def create_revoker_certs():
"""Create a few revoker.Cert objects.""" """Create a few revoker.Cert objects."""
from letsencrypt.client.revoker import Cert from letsencrypt.client.revoker import Cert
@@ -542,5 +542,17 @@ class CleanupTest(unittest.TestCase):
self.assertRaises(ValueError, self.authenticator.cleanup, [chall]) self.assertRaises(ValueError, self.authenticator.cleanup, [chall])
class MoreInfoTest(unittest.TestCase):
"""Tests for more_info() method. (trivially)"""
def setUp(self):
from letsencrypt.client.standalone_authenticator import \
StandaloneAuthenticator
self.authenticator = StandaloneAuthenticator()
def test_chall_pref(self):
"""Make sure exceptions aren't raised."""
self.authenticator.more_info()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+13 -14
View File
@@ -16,7 +16,6 @@ import letsencrypt
from letsencrypt.client import configuration from letsencrypt.client import configuration
from letsencrypt.client import client from letsencrypt.client import client
from letsencrypt.client import errors
from letsencrypt.client import interfaces from letsencrypt.client import interfaces
from letsencrypt.client import le_util from letsencrypt.client import le_util
from letsencrypt.client import log from letsencrypt.client import log
@@ -116,12 +115,14 @@ def main(): # pylint: disable=too-many-branches
displayer = display_util.NcursesDisplay() displayer = display_util.NcursesDisplay()
else: else:
displayer = display_util.FileDisplay(sys.stdout) displayer = display_util.FileDisplay(sys.stdout)
zope.component.provideUtility(displayer) zope.component.provideUtility(displayer)
if args.view_config_changes: if args.view_config_changes:
client.view_config_changes(config) client.view_config_changes(config)
sys.exit() sys.exit()
# TODO: if revoke, rev_cert...
if args.revoke: if args.revoke:
client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key)
sys.exit() sys.exit()
@@ -135,32 +136,30 @@ def main(): # pylint: disable=too-many-branches
# Make sure we actually get an installer that is functioning properly # Make sure we actually get an installer that is functioning properly
# before we begin to try to use it. # before we begin to try to use it.
try:
auth = client.determine_authenticator(config) auth = client.determine_authenticator(config)
except errors.LetsEncryptMisconfigurationError as err: if auth is None:
logging.fatal("Please fix your configuration before proceeding.%s" logging.critical("Unable to find a way to authenticate the server.")
"The Authenticator exited with the following message: " sys.exit(4)
"%s", os.linesep, err)
sys.exit(1)
# Use the same object if possible # Use the same object if possible
if interfaces.IInstaller.providedBy(auth): # pylint: disable=no-member if interfaces.IInstaller.providedBy(auth): # pylint: disable=no-member
installer = auth installer = auth
else: else:
installer = client.determine_installer(config) # This is simple and avoids confusion right now.
installer = None
doms = ops.choose_names(installer) if args.domains is None else args.domains doms = ops.choose_names(installer) if args.domains is None else args.domains
# Prepare for init of Client # Prepare for init of Client
if args.privkey is None: if args.authkey is None:
privkey = client.init_key(args.rsa_key_size, config.key_dir) authkey = client.init_key(args.rsa_key_size, config.key_dir)
else: else:
privkey = le_util.Key(args.authkey[0], args.authkey[1]) authkey = le_util.Key(args.authkey[0], args.authkey[1])
acme = client.Client(config, privkey, auth, installer) acme = client.Client(config, authkey, auth, installer)
# Validate the key and csr # Validate the key and csr
client.validate_key_csr(privkey) client.validate_key_csr(authkey)
# This more closely mimics the capabilities of the CLI # This more closely mimics the capabilities of the CLI
# It should be possible for reconfig only, install-only, no-install # It should be possible for reconfig only, install-only, no-install
@@ -170,7 +169,7 @@ def main(): # pylint: disable=too-many-branches
if auth is not None: if auth is not None:
cert_file, chain_file = acme.obtain_certificate(doms) cert_file, chain_file = acme.obtain_certificate(doms)
if installer is not None and cert_file is not None: if installer is not None and cert_file is not None:
acme.deploy_certificate(doms, privkey, cert_file, chain_file) acme.deploy_certificate(doms, authkey, cert_file, chain_file)
if installer is not None: if installer is not None:
acme.enhance_config(doms, args.redirect) acme.enhance_config(doms, args.redirect)