mirror of
https://github.com/certbot/certbot.git
synced 2026-08-03 08:03:10 +02:00
Revisions through running/testing
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user