refactor and enhance display, update revoker

This commit is contained in:
James Kasten 2015-02-08 00:46:16 -08:00
parent e000cfd7c6
commit 168a70c273
18 changed files with 1174 additions and 315 deletions

View file

@ -107,7 +107,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.check_parsing_errors("httpd.aug")
# Set Version
self.version = self.get_version() if version is None else version
self.version = get_version() if version is None else version
# Get all of the available vhosts
self.vhosts = self.get_virtual_hosts()
@ -911,37 +911,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return True
def get_version(self): # pylint: disable=no-self-use
"""Return version of Apache Server.
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7))
:returns: version
:rtype: tuple
:raises errors.LetsEncryptConfiguratorError:
Unable to find Apache version
"""
try:
proc = subprocess.Popen(
[CONFIG.APACHE_CTL, '-v'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
text = proc.communicate()[0]
except (OSError, ValueError):
raise errors.LetsEncryptConfiguratorError(
"Unable to run %s -v" % CONFIG.APACHE_CTL)
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(text)
if len(matches) != 1:
raise errors.LetsEncryptConfiguratorError(
"Unable to find Apache version")
return tuple([int(i) for i in matches[0].split('.')])
def verify_setup(self):
"""Verify the setup to ensure safe operating environment.
@ -955,6 +924,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
le_util.make_or_verify_dir(self.direc["work"], 0o755, uid)
le_util.make_or_verify_dir(self.direc["backup"], 0o755, uid)
@classmethod
def __str__(cls):
return "Apache version %s" % ".".join(get_version())
###########################################################################
# Challenges Section
###########################################################################
@ -1012,6 +985,38 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.restart()
def get_version(self):
"""Return version of Apache Server.
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7))
:returns: version
:rtype: tuple
:raises errors.LetsEncryptConfiguratorError:
Unable to find Apache version
"""
try:
proc = subprocess.Popen(
[CONFIG.APACHE_CTL, '-v'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
text = proc.communicate()[0]
except (OSError, ValueError):
raise errors.LetsEncryptConfiguratorError(
"Unable to run %s -v" % CONFIG.APACHE_CTL)
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(text)
if len(matches) != 1:
raise errors.LetsEncryptConfiguratorError(
"Unable to find Apache version")
return tuple([int(i) for i in matches[0].split('.')])
def enable_mod(mod_name):
"""Enables module in Apache.

View file

@ -1,8 +1,6 @@
"""ACME protocol client class and helper functions."""
import csv
import logging
import os
import shutil
import sys
import M2Crypto
@ -21,6 +19,7 @@ from letsencrypt.client import reverter
from letsencrypt.client import revoker
from letsencrypt.client.apache import configurator
from letsencrypt.client.display import ops
class Client(object):
@ -103,7 +102,7 @@ class Client(object):
cert_file, chain_file = self.save_certificate(
certificate_dict, cert_path, chain_path)
self.store_cert_key(cert_file, False)
revoker.Revoker.store_cert_key(cert_file, False)
return cert_file, chain_file
@ -194,8 +193,7 @@ class Client(object):
# sites may have been enabled / final cleanup
self.installer.restart()
zope.component.getUtility(
interfaces.IDisplay).success_installation(domains)
ops.success_installation(domains)
def enhance_config(self, domains, redirect=None):
"""Enhance the configuration.
@ -225,52 +223,6 @@ class Client(object):
if redirect:
self.redirect_to_ssl(domains)
def store_cert_key(self, cert_file, encrypt=False):
"""Store certificate key. (Used to allow quick revocation)
:param str cert_file: Path to a certificate file.
:param bool encrypt: Should the certificate key be encrypted?
:returns: True if key file was stored successfully, False otherwise.
:rtype: bool
"""
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700)
idx = 0
if encrypt:
logging.error(
"Unfortunately securely storing the certificates/"
"keys is not yet available. Stay tuned for the "
"next update!")
return False
if os.path.isfile(list_file):
with open(list_file, 'r+b') as csvfile:
csvreader = csv.reader(csvfile)
for row in csvreader:
idx = int(row[0]) + 1
csvwriter = csv.writer(csvfile)
csvwriter.writerow([str(idx), cert_file, self.authkey.file])
else:
with open(list_file, 'wb') as csvfile:
csvwriter = csv.writer(csvfile)
csvwriter.writerow(["0", cert_file, self.authkey.file])
shutil.copy2(self.authkey.file,
os.path.join(
CONFIG.CERT_KEY_BACKUP,
os.path.basename(self.authkey.file) + "_" + str(idx)))
shutil.copy2(cert_file,
os.path.join(
CONFIG.CERT_KEY_BACKUP,
os.path.basename(cert_file) + "_" + str(idx)))
return True
def redirect_to_ssl(self, domains):
"""Redirect all traffic from HTTP to HTTPS
@ -389,10 +341,13 @@ def csr_pem_to_der(csr):
# This should be controlled by commandline parameters
def determine_authenticator():
"""Returns a valid IAuthenticator."""
auths = []
try:
return configurator.ApacheConfigurator()
auths.append(configurator.ApacheConfigurator())
except errors.LetsEncryptNoInstallationError:
logging.info("Unable to determine a way to authenticate the server")
if len(auths) > 1:
return ops.choose_authenticator(auths)
def determine_installer():
@ -484,7 +439,7 @@ def revoke(server):
installer = None
revoc = revoker.Revoker(server, installer)
revoc.list_certs_keys()
revoc.display_menu()
def view_config_changes():

View file

@ -0,0 +1,378 @@
"""Lets Encrypt display."""
import os
import textwrap
import dialog
import zope.interface
from letsencrypt.client import interfaces
WIDTH = 72
HEIGHT = 20
# Display exit codes
OK = "ok"
"""Display exit code indicating user acceptance"""
CANCEL = "cancel"
"""Display exit code for a user canceling the display"""
HELP = "help"
"""Display exit code when for when the user requests more help."""
class NcursesDisplay(object):
"""Ncurses-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, width=WIDTH, height=HEIGHT):
super(NcursesDisplay, self).__init__()
self.dialog = dialog.Dialog()
self.width = width
self.height = height
def notification(self, message, height=10, pause=False):
"""Display a notification to the user and wait for user acceptance.
:param str message: Message to display
:param int height: Height of the dialog box
:param bool pause: Not applicable to NcursesDisplay
"""
self.dialog.msgbox(message, height, width=self.width)
def menu(self, message, choices,
ok_label="OK", cancel_label="Cancel", help_label=""):
"""Display a menu.
:param str message: title of menu
:param choices: menu lines
:type choices: list of tuples (tag, item) or
list of items (tags will be enumerated)
:param str ok_label: label of the OK button
:param str help_label: label of the help button
:returns: tuple of the form (`code`, `tag`) where
`code` - `str` display_util exit code
`tag` - `str` or `int` index corresponding to the item chosen
:rtype: tuple
"""
if help_label:
help_button = True
else:
help_button = False
# Can accept either tuples or just the actual choices
if choices and isinstance(choices[0], tuple):
code, selection = self.dialog.menu(
message, choices=choices, ok_label=ok_label,
cancel_label=cancel_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
return code, str(selection)
else:
choices = [
(str(i), choice) for i, choice in enumerate(choices, 1)
]
code, tag = self.dialog.menu(
message, choices=choices, ok_label=ok_label,
cancel_label=cancel_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
return code, int(tag) - 1
def input(self, message):
"""Display an input box to the user.
:param str message: Message to display that asks for input.
:returns: tuple of the form (code, string) where
`code` - int display exit code
`string` - input entered by the user
"""
return self.dialog.inputbox(message)
def yesno(self, message, yes_label="Yes", no_label="No"):
"""Display a Yes/No dialog box
:param str message: message to display to user
:param str yes_label: label on the "yes" button
:param str no_label: label on the "no" button
:returns: if yes_label was selected
:rtype: bool
"""
return self.dialog.DIALOG_OK == self.dialog.yesno(
message, self.height, self.width,
yes_label=yes_label, no_label=no_label)
def checklist(self, message, tags):
"""Displays a checklist.
:param message: Message to display before choices
:param list tags: where each is of type :class:`str`
:returns: tuple of the form (code, list_tags) where
`code` - int display exit code
`list_tags` - list of str tags selected by the user
"""
choices = [(tag, "", False) for tag in tags]
return self.dialog.checklist(
message, width=self.width, height=self.height, choices=choices)
class FileDisplay(object):
"""File-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, outfile):
super(FileDisplay, self).__init__()
self.outfile = outfile
def notification(self, message, height=10, pause=True):
"""Displays a notification and waits for user acceptance.
:param str message: Message to display
:param int height: No effect for FileDisplay
:param bool pause: Whether or not the program should pause for the
user's confirmation
"""
side_frame = "-" * 79
message = self._wrap_lines(message)
self.outfile.write(
"{line}{frame}{line}{msg}{line}{frame}{line}".format(
line=os.linesep, frame=side_frame, msg=message))
if pause:
raw_input("Press Enter to Continue")
def menu(
self, message, choices, ok_label="", cancel_label="", help_label=""):
"""Display a menu.
:param str message: title of menu
:param choices: Menu lines
:type choices: list of tuples (tag, item) or
list of descriptions (tags will be enumerated)
:returns: tuple of the form (code, tag) where
code - int display exit code
tag - str corresponding to the item chosen
:rtype: tuple
"""
self._print_menu(message, choices)
code, selection = self._get_valid_int_ans(len(choices))
return code, str(selection - 1)
def input(self, message):
# pylint: disable=no-self-use
"""Accept input from the user
:param str message: message to display to the user
:returns: tuple of (`code`, `input`) where
`code` - str display exit code
`input` - str of the user's input
:rtype: tuple
"""
ans = raw_input(
textwrap.fill("%s (Enter c to cancel): " % message, 80))
if ans == "c" or ans == "C":
return CANCEL, "-1"
else:
return OK, ans
def yesno(self, message, yes_label="Yes", no_label="No"):
"""Query the user with a yes/no question.
:param str message: question for the user
:param str yes_label: Label of the "Yes" parameter
:param str no_label: Label of the "No" parameter
:returns: True for "Yes", False for "No"
:rtype: bool
"""
side_frame = ("-" * 79) + os.linesep
message = self._wrap_lines(message)
self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
os.linesep, frame=side_frame, msg=message))
yes_label = _parens_around_char(yes_label)
no_label = _parens_around_char(no_label)
ans = raw_input("{yes}/{no}: ".format(yes=yes_label, no=no_label))
return (ans.startswith(yes_label[0].lower()) or
ans.startswith(yes_label[0].upper()))
def checklist(self, message, tags):
"""Display a checklist.
:param str message: Message to display to user
:param list tags: `str` tags to select
:returns: tuple of (`code`, `tags`) where
`code` - str display exit code
`tags` - list of selected tags
:rtype: tuple
"""
while True:
self._print_menu(message, tags)
code, ans = self.input("Select the appropriate numbers "
"separated by commas and/or spaces ")
if code == OK:
indices = separate_list_input(ans)
selected_tags = self._scrub_checklist_input(indices, tags)
if selected_tags:
return code, selected_tags
else:
self.outfile.write(
"** Error - Invalid selection **%s" % os.linesep)
else:
return code, []
def _scrub_checklist_input(self, indices, tags):
"""Validate input and transform indices to appropriate tags.
:param list indices: Checklist input
:param list tags: Original tags of the checklist
:returns: tags the user selected
:rtype: :class:`list` of :class:`str`
"""
# They should all be of type int
try:
indices = [int(index) for index in indices]
except TypeError:
return []
# Remove duplicates
indices = list(set(indices))
# Check all input is within range
for index in indices:
if index < 1 or index > len(tags):
return []
# Transform indices to appropriate tags
return [tags[index-1] for index in indices]
def _print_menu(self, message, choices):
"""Print a menu on the screen.
:param str message: title of menu
:param choices: Menu lines
:type choices: list of tuples (tag, item) or
list of descriptions (tags will be enumerated)
"""
# Can take either tuples or single items in choices list
if choices and isinstance(choices[0], tuple):
choices = ["%s - %s" % (c[0], c[1]) for c in choices]
self.outfile.write(
"{new}{msg}{new}".format(new=os.linesep, msg=message))
side_frame = ("-" * 79) + os.linesep
self.outfile.write(side_frame)
for i, tag in enumerate(choices, 1):
self.outfile.write(
textwrap.fill("{num}: {tag}".format(num=i, tag=tag), 80))
# Keep this outside of the textwrap
self.outfile.write(os.linesep)
self.outfile.write(side_frame)
def _wrap_lines(self, msg): # pylint: disable=no-self-use
"""Format lines nicely to 80 chars
:param str msg: Original message
:returns: Formatted message
:rtype: str
"""
lines = msg.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(line, 80))
return os.linesep.join(fixed_l)
def _get_valid_int_ans(self, max):
"""Get a numerical selection.
:param int max: The maximum entry (len of choices)
:returns: tuple of the form (`code`, `selection`) where
`code` - str display exit code ('ok' or cancel')
`selection` - int user's selection
:rtype: tuple
"""
selection = -1
if max > 1:
input_msg = ("Select the appropriate number "
"[1-{max}] then [enter] (press 'c' to "
"cancel){end}".format(max=max, end=os.linesep))
else:
input_msg = ("Press 1 [enter] to confirm the selection "
"(press 'c' to cancel){0}".format(os.linesep))
while selection < 1:
ans = raw_input(input_msg)
if ans.startswith("c") or ans.startswith("C"):
return CANCEL, -1
try:
selection = int(ans)
if selection < 1 or selection > max:
raise ValueError
except ValueError:
self.outfile.write(
"{0}** Invalid input **{0}".format(os.linesep))
return OK, selection
def separate_list_input(input):
"""Separate a comma or space separated list.
:param str input: input from the user
:returns: strings
:rtype: list
"""
no_commas = input.replace(",", " ")
return [string for string in no_commas.split()]
def _parens_around_char(label):
"""Place parens around first character of label.
:param str label: Must contain at least one character
"""
return "({first}){rest}".format(first=label[0], rest=label[1:])

View file

@ -0,0 +1,68 @@
"""Let's Encrypt Enhancement Display"""
import logging
import zope.component
from letsencrypt.client import errors
from letsencrypt.client import interfaces
from letsencrypt.client.display import display_util
class EnhanceDisplay(object):
"""Class used to display various enhancements.
.. note::This is not a subclass of Display. It merely uses Display as a
component.
:ivar displayer: Display singleton
:type displayer: :class:`letsencrypt.client.interfaces.IDisplay
:ivar dict dispatch: Dict mapping enhancements to functions
"""
def __init__(self):
self.displayer = zope.component.getUtility(interfaces.IDisplay)
self.dispatch = {
"redirect": self.redirect_by_default,
}
def ask(self, enhancement):
"""Display the enhancement to the user.
:param str enhancement: One of the
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS` enhancements
:returns: True if feature is desired, False otherwise
:rtype: bool
:raises :class:`letsencrypt.client.errors.LetsEncryptClientError`: If
the enhancement provided is not supported.
"""
try:
return self.dispatch[enhancement]
except KeyError:
logging.error("Unsupported enhancement given to ask()")
raise errors.LetsEncryptClientError("Unsupported Enhancement")
def redirect_by_default(self):
"""Determines whether the user would like to redirect to HTTPS.
:returns: True if redirect is desired, False otherwise
:rtype: bool
"""
choices = [
("Easy", "Allow both HTTP and HTTPS access to these sites"),
("Secure", "Make all requests redirect to secure HTTPS access")]
result = self.displayer.menu(
"Please choose whether HTTPS access is required or optional.",
choices, "Please enter the appropriate number")
if result[0] != display_util.OK:
return False
# different answer for each type of display
return str(result[1]) == "Secure" or result[1] == 1

View file

@ -0,0 +1,124 @@
import logging
import os
import sys
import zope.component
from letsencrypt.client import interfaces
from letsencrypt.client.display import display_util
# Define a helper function to avoid verbose code
util = zope.component.getUtility
def choose_authenticator(auths):
"""Allow the user to choose their authenticator.
:param list auths: Where each is a
:class:`letsencrypt.client.interfaces.IAuthenticator` object
:returns: Authenticator selected
:rtype: :class:`letsencrypt.client.interfaces.IAuthenticator`
"""
code, index = util(interfaces.IDisplay).menu(
"How would you like to authenticate with the Let's Encrypt CA?",
[str(auth.__class__) for auth in auths])
if code == display_util.OK:
return auths[index]
else:
sys.exit(0)
def choose_names(installer):
"""Display screen to select domains to validate.
:param installer: An installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
if installer is None:
return _choose_names_manually()
names = list(installer.get_all_names())
if not names:
manual = util(interfaces.IDisplay).yesno(
"No names were found in your configuration files.{0}You should "
"specify ServerNames in your config files in order to allow for "
"accurate installation of your certificate.{0}"
"If you do use the default vhost, you may specify the name "
"manually. Would you like to continue?{0}".format(os.linesep))
if manual:
return _choose_names_manually()
else:
sys.exit(0)
code, names = _filter_names(names)
if code == display_util.OK and names:
return names
else:
sys.exit(0)
def _filter_names(names):
"""Determine which names the user would like to select from a list.
:param list names: domain names
:returns: tuple of the form (`code`, `names`) where
`code` - str display exit code
`names` - list of names selected
:rtype: tuple
"""
choices = [(n, "", 0) for n in names]
code, names = util(interfaces.IDisplay).checklist(
"Which names would you like to activate HTTPS for?",
choices=choices)
return code, [str(s) for s in names]
def _choose_names_manually():
"""Manualy input names for those without an installer."""
code, input = util(interfaces.IDisplay).input(
"Please enter in your domain name(s) (comma and/or space separated) ")
if code == display_util.OK:
return display_util.separate_list_input(input)
sys.exit(0)
def success_installation(domains):
"""Display a box confirming the installation of HTTPS.
:param list domains: domain names which were enabled
"""
util(interfaces.IDisplay).notification(
"Congratulations! You have successfully enabled "
"%s!" % _gen_https_names(domains), pause=True)
def _gen_https_names(domains):
"""Returns a string of the https domains.
Domains are formatted nicely with https:// prepended to each.
:param list domains: Each domain is a 'str'
"""
if len(domains) == 1:
return "https://{0}".format(domains[0])
elif len(domains) == 2:
return "https://{dom[0]} and https://{dom[1]}".format(dom=domains)
elif len(domains) > 2:
return "{0}{1}{2}".format(
", ".join("https://" + dom for dom in domains[:-1]),
", and https://",
domains[-1])
return ""

View file

@ -0,0 +1,106 @@
import os
import zope.component
from letsencrypt.client import interfaces
from letsencrypt.client.display import display_util
util = zope.component.getUtility
def choose_certs(certs):
"""Display choose certificates menu.
:param list certs: List of cert dicts.
:returns: cert to revoke
:rtype: :class:`letsencrypt.client.revoker.Cert`
"""
code, tag = display_certs(certs)
if code == display_util.OK:
cert = certs[tag]
if confirm_revocation(cert):
return cert
else:
choose_certs(certs)
elif code == display_util.HELP:
cert = certs[tag]
more_info_cert(cert)
choose_certs(certs)
else:
exit(0)
def display_certs(certs):
"""Display the certificates in a menu for revocation.
:param list certs: each is a :class:`letsencrypt.client.revoker.Cert`
:returns: tuple of the form (code, selection) where
code is a display exit code
selection is the user's int selection
:rtype: tuple
"""
list_choices = [
("%s | %s | %s" %
(str(cert.get_cn().ljust(display_util.WIDTH - 39)),
cert.get_not_before().strftime("%m-%d-%y"),
"Installed" if cert.installed and cert.installed != ["Unknown"]
else "")
for cert in enumerate(certs))
]
code, tag = util(interfaces.IDisplay).menu(
"Which certificates would you like to revoke?",
"Revoke number (c to cancel): ",
choices=list_choices, help_button=True,
help_label="More Info", ok_label="Revoke",
cancel_label="Exit")
if not tag:
tag = -1
return code, (int(tag) - 1)
def confirm_revocation(self, cert):
"""Confirm revocation screen.
:param cert: certificate object
:type cert: :class:
:returns: True if user would like to revoke, False otherwise
:rtype: bool
"""
text = ("{0}Are you sure you would like to revoke the following "
"certificate:{0}".format(os.linesep))
text += cert.pretty_print()
text += "This action cannot be reversed!"
return display_util.OK == util(interfaces.IDisplay).yesno(
text, width=display_util.WIDTH, height=display_util.HEIGHT)
def more_info_cert(cert):
"""Displays more info about the cert.
:param dict cert: cert dict used throughout revoker.py
"""
text = "{0}Certificate Information:{0}".format(os.linesep)
text += cert.pretty_print()
util(interfaces.IDisplay).notification(text, height=display_util.HEIGHT)
def success_revocation(cert):
"""Display a success message.
:param cert: cert that was revoked
:type cert: :class:`letsencrypt.client.revoker.Cert`
"""
util(interfaces.IDisplay).notification(
"You have successfully revoked the certificate for "
"%s" % cert.get_cn())

View file

@ -45,19 +45,6 @@ class IAuthenticator(zope.interface.Interface):
"""Revert changes and shutdown after challenges complete."""
class IChallenge(zope.interface.Interface):
"""Let's Encrypt challenge."""
def perform():
"""Perform the challenge."""
def generate_response():
"""Generate response."""
def cleanup():
"""Cleanup."""
class IInstaller(zope.interface.Interface):
"""Generic Let's Encrypt Installer Interface.
@ -144,14 +131,17 @@ class IInstaller(zope.interface.Interface):
class IDisplay(zope.interface.Interface):
"""Generic display."""
def generic_notification(message):
def notification(message, height, pause):
"""Displays a string message
:param str message: Message to display
:param int height: Height of dialog box if applicable
:param bool pause: Whether or not the application should pause for
confirmation (if available)
"""
def generic_menu(message, choices, input_text=""):
def menu(message, choices, input_text="", ok_label="OK", help_label=""):
"""Displays a generic menu.
:param str message: message to display
@ -163,29 +153,37 @@ class IDisplay(zope.interface.Interface):
"""
def generic_input(message):
"""Accept input from the user."""
def input(message):
"""Accept input from the user
def generic_yesno(message, yes_label="Yes", no_label="No"):
"""A yes/no dialog."""
:param str message: message to display to the user
def filter_names(names):
"""Allow the user to select which names they would like to activate."""
:returns: tuple of (`code`, `input`) where
`code` - str display exit code
`input` - str of the user's input
:rtype: tuple
def success_installation(domains):
"""Display a congratulations message for new https domains."""
"""
def display_certs(certs):
"""Display a list of certificates."""
def yesno(message, yes_label="Yes", no_label="No"):
"""Query the user with a yes/no question.
def confirm_revocation(cert):
"""Confirmation of revocation screen."""
:param str message: question for the user
def more_info_cert(cert):
"""Print out all information for a given certificate dict."""
:returns: True for "Yes", False for "No"
:rtype: bool
def redirect_by_default():
"""Ask the user whether they would like to redirect to HTTPS."""
"""
def checkbox(message, choices):
"""Allow for multiple selections from a menu.
:param str message: message to display to the user
:param choices: :param choices: choices
:type choices: :class:`list` of :func:`tuple`
"""
class IValidator(zope.interface.Interface):

View file

@ -3,7 +3,7 @@ import logging
import dialog
from letsencrypt.client import display
from letsencrypt.client.display import display_util
class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods
@ -19,8 +19,8 @@ class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods
PADDING_HEIGHT = 2
PADDING_WIDTH = 4
def __init__(self, level=logging.NOTSET, height=display.HEIGHT,
width=display.WIDTH - 4, d=None):
def __init__(self, level=logging.NOTSET, height=display_util.HEIGHT,
width=display_util.WIDTH - 4, d=None):
# Handler not new-style -> no super
logging.Handler.__init__(self, level)
self.height = height

View file

@ -7,10 +7,10 @@ import time
import zope.component
from letsencrypt.client import CONFIG
from letsencrypt.client import display
from letsencrypt.client import errors
from letsencrypt.client import interfaces
from letsencrypt.client import le_util
from letsencrypt.client.display import display_util
class Reverter(object):
@ -127,7 +127,7 @@ class Reverter(object):
output.append(os.linesep)
zope.component.getUtility(interfaces.IDisplay).generic_notification(
os.linesep.join(output), display.HEIGHT)
os.linesep.join(output), display_util.HEIGHT)
def add_to_temp_checkpoint(self, save_files, save_notes):
"""Add files to temporary checkpoint

View file

@ -6,60 +6,74 @@ import os
import shutil
import M2Crypto
import zope.component
from letsencrypt.client import acme
from letsencrypt.client import CONFIG
from letsencrypt.client import display
from letsencrypt.client import interfaces
from letsencrypt.client import le_util
from letsencrypt.client import network
from letsencrypt.client.display import display_util
from letsencrypt.client.display import revocation
class Revoker(object):
"""A revocation class for LE."""
list_path = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
def __init__(self, server, installer):
self.network = network.Network(server)
self.installer = installer
self.displayer = zope.component.getUtility(interfaces.IDisplay)
def acme_revocation(self, cert):
"""Handle ACME "revocation" phase.
:param dict cert: TODO
:param cert: cert intended to be revoked
:type cert: :class:`letsencrypt.client.revoker.Cert`
:returns: ACME "revocation" message.
:rtype: dict
"""
cert_der = M2Crypto.X509.load_cert(cert["backup_cert_file"]).as_der()
with open(cert["backup_key_file"], "rU") as backup_key_file:
with open(cert.backup_key_path, "rU") as backup_key_file:
key = backup_key_file.read()
revocation = self.network.send_and_receive_expected(
revoc = self.network.send_and_receive_expected(
acme.revocation_request(cert_der, key), "revocation")
self.displayer.notification(
"You have successfully revoked the certificate for "
"%s" % cert["cn"])
revocation.success_revocation(cert)
self.remove_cert_key(cert)
self.list_certs_keys()
self.display_menu()
return revocation
return revoc
def list_certs_keys(self):
def display_menu(self):
"""List trusted Let's Encrypt certificates."""
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
certs = []
if not os.path.isfile(list_file):
if not os.path.isfile(Revoker.list_path):
logging.info(
"You don't have any certificates saved from letsencrypt")
return
csha1_vhlist = self._get_installed_locations()
certs = self._populate_saved_certs(csha1_vhlist)
with open(list_file, "rb") as csvfile:
if certs:
self._insert_installed_status(certs)
cert = self.choose_certs(certs)
self.acme_revocation(cert)
else:
logging.info(
"There are not any trusted Let's Encrypt "
"certificates for this server.")
def _populate_saved_certs(self, csha1_vhlist):
"""Populate a list of all the saved certs."""
certs = []
with open(Revoker.list_path, "rb") as csvfile:
csvreader = csv.reader(csvfile)
# idx, orig_cert, orig_key
for row in csvreader:
@ -78,16 +92,16 @@ class Revoker(object):
cert.get_fingerprint, [])
certs.append(cert)
if certs:
self._insert_installed_status(certs)
self.choose_certs(certs)
else:
self.displayer.notification(
"There are not any trusted Let's Encrypt "
"certificates for this server.")
return certs
def _get_installed_locations(self):
"""Get installed locations of certificates"""
"""Get installed locations of certificates
:returns: cert sha1 fingerprint -> :class:`list` of vhosts where
the certificate is installed.
"""
csha1_vhlist = {}
if self.installer is None:
@ -106,40 +120,27 @@ class Revoker(object):
return csha1_vhlist
def choose_certs(self, certs):
"""Display choose certificates menu.
:param list certs: List of cert dicts.
"""
code, tag = self.display_certs(certs)
if code == display.OK:
cert = certs[tag]
if self.confirm_revocation(cert):
self.acme_revocation(cert)
else:
self.choose_certs(certs)
elif code == display.HELP:
cert = certs[tag]
self.displayer.more_info_cert(cert)
self.choose_certs(certs)
else:
exit(0)
def remove_cert_key(self, cert): # pylint: disable=no-self-use
"""Remove certificate and key.
:param cert: Cert dict used throughout revocation
:param cert: cert object
:type cert: :class:`letsencrypt.client.revoker.Cert`
"""
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
list_file2 = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST.tmp")
self._remove_cert_from_list(cert)
with open(list_file, "rb") as orgfile:
# Remove files
os.remove(cert["backup_cert_file"])
os.remove(cert["backup_key_file"])
def _remove_cert_from_list(self, cert):
"""Remove a certificate from the LIST file."""
list_path2 = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST.tmp")
with open(Revoker.list_path, "rb") as orgfile:
csvreader = csv.reader(orgfile)
with open(list_file2, "wb") as newfile:
with open(list_path2, "wb") as newfile:
csvwriter = csv.writer(newfile)
for row in csvreader:
@ -148,67 +149,62 @@ class Revoker(object):
row[2] == cert.orig_key.path):
csvwriter.writerow(row)
shutil.copy2(list_file2, list_file)
os.remove(list_file2)
os.remove(cert["backup_cert_file"])
os.remove(cert["backup_key_file"])
shutil.copy2(list_path2, Revoker.list_path)
os.remove(list_path2)
def display_certs(self, certs):
"""Display the certificates in a menu for revocation.
@classmethod
def store_cert_key(cls, cert_path, key_path, encrypt=False):
"""Store certificate key. (Used to allow quick revocation)
:param list certs: `list` of :class:`letsencrypt.client.
:param str cert_path: Path to a certificate file.
:param key_path: Authorized key for certificate
:type key_path: :class:`letsencrypt.client.le_util.Key`
:returns: tuple of the form (code, selection) where
code is a display exit code
selection is the user's int selection
:rtype: tuple
:param bool encrypt: Should the certificate key be encrypted?
"""
list_choices = [
("%s | %s | %s" %
(str(cert.get_cn().ljust(display.WIDTH - 39)),
cert.get_not_before().strftime("%m-%d-%y"),
"Installed" if cert.installed and cert.installed != ["Unknown"]
else "")
for cert in enumerate(certs))
]
code, tag = self.displayer.menu(
"Which certificates would you like to revoke?",
"Revoke number (c to cancel): ",
choices=list_choices, help_button=True,
help_label="More Info", ok_label="Revoke")
if not tag:
tag = -1
return code, (int(tag) - 1)
def confirm_revocation(self, cert):
"""Confirm revocation screen.
:param cert: certificate object
:type cert: :class:
:returns: True if user would like to revoke, False otherwise
:returns: True if key file was stored successfully, False otherwise.
:rtype: bool
"""
text = ("{0}Are you sure you would like to revoke the following "
"certificate:{0}".format(os.linesep))
text += cert.pretty_print()
text += "This action cannot be reversed!"
return display.OK == self.dialog.yesno(
text, width=self.width, height=self.height)
le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700)
idx = 0
def more_info_cert(self, cert):
"""Displays more info about the cert.
if encrypt:
logging.error(
"Unfortunately securely storing the certificates/"
"keys is not yet available. Stay tuned for the "
"next update!")
return False
:param dict cert: cert dict used throughout revoker.py
cls._append_index_file(cert_path, key_path)
"""
text = "{0}Certificate Information:{0}".format(os.linesep)
text += cert.pretty_print()
self.notification(text, height=self.height)
shutil.copy2(key_path,
os.path.join(
CONFIG.CERT_KEY_BACKUP,
os.path.basename(key_path) + "_" + str(idx)))
shutil.copy2(cert_path,
os.path.join(
CONFIG.CERT_KEY_BACKUP,
os.path.basename(cert_path) + "_" + str(idx)))
return True
@classmethod
def _append_index_file(cls, cert_path, key_path):
if os.path.isfile(Revoker.list_path):
with open(Revoker.list_path, 'r+b') as csvfile:
csvreader = csv.reader(csvfile)
# Find the highest index in the file
for row in csvreader:
idx = int(row[0]) + 1
csvwriter = csv.writer(csvfile)
csvwriter.writerow([str(idx), cert_path, key_path])
else:
with open(Revoker.list_path, 'wb') as csvfile:
csvwriter = csv.writer(csvfile)
csvwriter.writerow(["0", cert_path, key_path])
class Cert(object):
@ -232,7 +228,7 @@ class Cert(object):
PathStatus = collections.namedtuple("PathStatus", "path status")
"""Convenience container to hold path and status info"""
def __init__(self, cert_filepath):
def __init__(self, cert_path):
"""Cert initialization
:param str cert_filepath: Name of file containing certificate in
@ -240,7 +236,7 @@ class Cert(object):
"""
try:
self.cert = M2Crypto.X509.load_cert(cert_filepath)
self.cert = M2Crypto.X509.load_cert(cert_path)
except (IOError, M2Crypto.X509.X509Error):
self.cert = None
@ -329,7 +325,6 @@ class Cert(object):
return ""
def __str__(self):
"""Turn a Certinto a string."""
text = []
text.append("Subject: %s" % self.get_subject())
text.append("SAN: %s" % self.get_san())
@ -344,8 +339,8 @@ class Cert(object):
def pretty_print(self):
"""Nicely frames a cert str"""
text = "-" * (display.WIDTH - 4) + os.linesep
text = "-" * (display_util.WIDTH - 4) + os.linesep
text += str(self)
text += "-" * (display.WIDTH - 4)
text += "-" * (display_util.WIDTH - 4)
return text

View file

@ -8,7 +8,7 @@ import augeas
import mock
import zope.component
from letsencrypt.client import display
from letsencrypt.client.display import display_util
from letsencrypt.client import errors
from letsencrypt.client.apache import parser
@ -21,7 +21,7 @@ class ApacheParserTest(util.ApacheTest):
def setUp(self):
super(ApacheParserTest, self).setUp()
zope.component.provideUtility(display.FileDisplay(sys.stdout))
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
self.parser = parser.ApacheParser(
augeas.Augeas(flags=augeas.Augeas.NONE),

View file

@ -22,7 +22,7 @@ class DvsniGenCertTest(unittest.TestCase):
r_b64 = le_util.jose_b64encode(dvsni_r)
pem = pkg_resources.resource_string(
__name__, os.path.join("testdata", "rsa256_key.pem"))
key = client.Client.Key("path", pem)
key = le_util.Client.Key("path", pem)
nonce = "12345ABCDE"
cert_pem, s_b64 = self._call(domain, r_b64, nonce, key)

View file

@ -31,7 +31,7 @@ class RollbackTest(unittest.TestCase):
def test_misconfiguration_fixed(self, mock_det, mock_rev, mock_input):
mock_det.side_effect = [errors.LetsEncryptMisconfigurationError,
self.m_install]
self.m_input().yesno.return_value = True
mock_input().yesno.return_value = True
self._call(1)
@ -50,7 +50,7 @@ class RollbackTest(unittest.TestCase):
self, mock_det, mock_rev, mock_warn, mock_input):
mock_det.side_effect = errors.LetsEncryptMisconfigurationError
self.m_input().yesno.return_value = True
mock_input().yesno.return_value = True
self._call(1)
@ -70,7 +70,7 @@ class RollbackTest(unittest.TestCase):
self, mock_det, mock_rev, mock_input):
mock_det.side_effect = errors.LetsEncryptMisconfigurationError
self.m_input().yesno.return_value = False
mock_input().yesno.return_value = False
self._call(1)

View file

@ -1,5 +1,4 @@
"""Tests for letsencrypt.client.crypto_util."""
import datetime
import os
import pkg_resources
import unittest
@ -132,42 +131,42 @@ class MakeSSCertTest(unittest.TestCase):
make_ss_cert(RSA256_KEY, ['example.com', 'www.example.com'])
class GetCertInfoTest(unittest.TestCase):
"""Tests for letsencrypt.client.crypto_util.get_cert_info."""
def setUp(self):
self.cert_info = {
'not_before': datetime.datetime(
2014, 12, 11, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC),
'not_after': datetime.datetime(
2014, 12, 18, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC),
'subject': 'C=US, ST=Michigan, L=Ann Arbor, O=University '
'of Michigan and the EFF, CN=example.com',
'cn': 'example.com',
'issuer': 'C=US, ST=Michigan, L=Ann Arbor, O=University '
'of Michigan and the EFF, CN=example.com',
'serial': 1337L,
'pub_key': 'RSA 512',
}
def _call(self, name):
from letsencrypt.client.crypto_util import get_cert_info
self.assertEqual(get_cert_info(pkg_resources.resource_filename(
__name__, os.path.join('testdata', name))), self.cert_info)
def test_single_domain(self):
self.cert_info.update({
'san': '',
'fingerprint': '9F8CE01450D288467C3326AC0457E351939C72E',
})
self._call('cert.pem')
def test_san(self):
self.cert_info.update({
'san': 'DNS:example.com, DNS:www.example.com',
'fingerprint': '62F7110431B8E8F55905DBE5592518F9634AC50A',
})
self._call('cert-san.pem')
# class GetCertInfoTest(unittest.TestCase):
# """Tests for letsencrypt.client.crypto_util.get_cert_info."""
#
# def setUp(self):
# self.cert_info = {
# 'not_before': datetime.datetime(
# 2014, 12, 11, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC),
# 'not_after': datetime.datetime(
# 2014, 12, 18, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC),
# 'subject': 'C=US, ST=Michigan, L=Ann Arbor, O=University '
# 'of Michigan and the EFF, CN=example.com',
# 'cn': 'example.com',
# 'issuer': 'C=US, ST=Michigan, L=Ann Arbor, O=University '
# 'of Michigan and the EFF, CN=example.com',
# 'serial': 1337L,
# 'pub_key': 'RSA 512',
# }
#
# def _call(self, name):
# from letsencrypt.client.crypto_util import get_cert_info
# self.assertEqual(get_cert_info(pkg_resources.resource_filename(
# __name__, os.path.join('testdata', name))), self.cert_info)
#
# def test_single_domain(self):
# self.cert_info.update({
# 'san': '',
# 'fingerprint': '9F8CE01450D288467C3326AC0457E351939C72E',
# })
# self._call('cert.pem')
#
# def test_san(self):
# self.cert_info.update({
# 'san': 'DNS:example.com, DNS:www.example.com',
# 'fingerprint': '62F7110431B8E8F55905DBE5592518F9634AC50A',
# })
# self._call('cert-san.pem')
class B64CertToPEMTest(unittest.TestCase):

View file

@ -0,0 +1,105 @@
import sys
import unittest
import mock
from letsencrypt.client.display import display_util
class DisplayT(unittest.TestCase):
def setUp(self):
self.choices = [("First", "Description1"), ("Second", "Description2")]
self.tags = ["tag1", "tag2", "tag3"]
def test_visual(displayer, choices):
"""Visually test all of the display functions."""
displayer.notification("Random notification!")
displayer.menu("Question?", choices,
ok_label="O", cancel_label="Can", help_label="??")
displayer.menu("Question?", [choice[1] for choice in choices],
ok_label="O", cancel_label="Can", help_label="??")
displayer.input("Input Message")
displayer.yesno(
"Yes/No Message", yes_label="Yessir", no_label="Nosir")
displayer.checklist(
"Checklist Message", [choice[0] for choice in choices])
class NcursesDisplayTest(DisplayT):
"""Test ncurses display."""
def setUp(self):
super(NcursesDisplayTest, self).setUp()
self.displayer = display_util.NcursesDisplay()
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.msgbox")
def test_notification(self, mock_msgbox):
"""Kind of worthless... one liner."""
self.displayer.notification("message")
self.assertEqual(mock_msgbox.call_count, 1)
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.menu")
def test_menu(self, mock_menu):
pass
def test_visual(self):
test_visual(self.displayer, self.choices)
class FileOutputDisplayTest(DisplayT):
"""Test stdout display."""
def setUp(self):
super(FileOutputDisplayTest, self).setUp()
self.displayer = display_util.FileDisplay(sys.stdout)
def test_visual(self):
test_visual(self.displayer, self.choices)
class SeparateListInputTest(unittest.TestCase):
"""Test Module functions."""
def setUp(self):
self.exp = ["a", "b", "c", "test"]
@classmethod
def _call(cls, input):
from letsencrypt.client.display.display_util import separate_list_input
return separate_list_input(input)
def test_commas(self):
actual = self._call("a,b,c,test")
self.assertEqual(actual, self.exp)
def test_spaces(self):
actual = self._call("a b c test")
self.assertEqual(actual, self.exp)
def test_both(self):
actual = self._call("a, b, c, test")
self.assertEqual(actual, self.exp)
def test_mess(self):
actual = [self._call(" a , b c \t test")]
actual.append(self._call(",a, ,, , b c test "))
for act in actual:
self.assertEqual(act, self.exp)
class PlaceParensTest(unittest.TestCase):
@classmethod
def _call(cls, label):
from letsencrypt.client.display.display_util import _parens_around_char
return _parens_around_char(label)
def test_single_letter(self):
ret = self._call("a")
self.assertEqual("(a)", ret)
def test_multiple(self):
ret = self._call("Label")
self.assertEqual("(L)abel", ret)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,159 @@
import sys
import unittest
import mock
import zope.component
from letsencrypt.client.display import display_util
class ChooseAuthenticatorTest(unittest.TestCase):
"""Test choose_authenticator function."""
def setUp(self):
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
@classmethod
def _call(cls, auths):
from letsencrypt.client.display.ops import choose_authenticator
return choose_authenticator(auths)
@mock.patch("letsencrypt.client.display.ops.util")
def test_successful_choice(self, mock_util):
mock_util().menu.return_value = (display_util.OK, 0)
ret = self._call(["authenticator1", "auth2"])
self.assertEqual(ret, "authenticator1")
@mock.patch("letsencrypt.client.display.ops.util")
def test_no_choice(self, mock_util):
mock_util().menu.return_value = (display_util.CANCEL, 0)
self.assertRaises(SystemExit, self._call, ["authenticator1"])
class GenHttpsNamesTest(unittest.TestCase):
"""Test _gen_https_names"""
def setUp(self):
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
@classmethod
def _call(cls, domains):
from letsencrypt.client.display.ops import _gen_https_names
return _gen_https_names(domains)
def test_zero(self):
self.assertEqual(self._call([]), "")
def test_one(self):
dom = "example.com"
self.assertEqual(self._call([dom]), "https://%s" % dom)
def test_two(self):
doms = ["foo.bar.org", "bar.org"]
self.assertEqual(
self._call(doms),
"https://{dom[0]} and https://{dom[1]}".format(dom=doms))
def test_three(self):
doms = ["a.org", "b.org", "c.org"]
# We use an oxford comma
self.assertEqual(
self._call(doms),
"https://{dom[0]}, https://{dom[1]}, and https://{dom[2]}".format(
dom=doms))
def test_four(self):
doms = ["a.org", "b.org", "c.org", "d.org"]
exp = ("https://{dom[0]}, https://{dom[1]}, https://{dom[2]}, "
"and https://{dom[3]}".format(dom=doms))
self.assertEqual(self._call(doms), exp)
class ChooseNamesTest(unittest.TestCase):
"""Test choose names."""
def setUp(self):
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
self.mock_install = mock.MagicMock()
@classmethod
def _call(cls, installer):
from letsencrypt.client.display.ops import choose_names
return choose_names(installer)
@mock.patch("letsencrypt.client.display.ops._choose_names_manually")
def test_no_installer(self, mock_manual):
self._call(None)
self.assertEqual(mock_manual.call_count, 1)
@mock.patch("letsencrypt.client.display.ops.util")
def test_no_installer_cancel(self, mock_util):
mock_util().input.return_value = (display_util.CANCEL, [])
self.assertRaises(SystemExit, self._call, None)
@mock.patch("letsencrypt.client.display.ops.util")
def test_no_names_choose(self, mock_util):
self.mock_install().get_all_names.return_value = set()
mock_util().yesno.return_value = True
domain = "example.com"
mock_util().input.return_value = (display_util.OK, domain)
actual_doms = self._call(self.mock_install)
self.assertEqual(mock_util().input.call_count, 1)
self.assertEqual(actual_doms, [domain])
@mock.patch("letsencrypt.client.display.ops.util")
def test_no_names_quit(self, mock_util):
self.mock_install().get_all_names.return_value = set()
mock_util().yesno.return_value = False
self.assertRaises(SystemExit, self._call, self.mock_install)
@mock.patch("letsencrypt.client.display.ops.util")
def test_filter_names_valid_return(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.OK, ["example.com"])
names = self._call(self.mock_install)
self.assertEqual(names, ["example.com"])
self.assertEqual(mock_util().checklist.call_count, 1)
@mock.patch("letsencrypt.client.display.ops.util")
def test_filter_names_nothing_selected(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.OK, [])
self.assertRaises(SystemExit, self._call, self.mock_install)
@mock.patch("letsencrypt.client.display.ops.util")
def test_filter_names_cancel(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.CANCEL, ["example.com"])
self.assertRaises(SystemExit, self._call, self.mock_install)
class SuccessInstallationTest(unittest.TestCase):
@classmethod
def _call(cls, names):
from letsencrypt.client.display.ops import success_installation
success_installation(names)
@mock.patch("letsencrypt.client.display.ops.util")
def test_success_installation(self, mock_util):
mock_util().notification.return_value = None
names = ["example.com", "abc.com"]
self._call(names)
self.assertEqual(mock_util().notification.call_count, 1)
arg = mock_util().notification.call_args_list[0][0][0]
for name in names:
self.assertTrue(name in arg)
if __name__ == "__main__":
unittest.main()

View file

@ -15,11 +15,12 @@ import zope.interface
import letsencrypt
from letsencrypt.client import CONFIG
from letsencrypt.client import client
from letsencrypt.client import display
from letsencrypt.client import errors
from letsencrypt.client import interfaces
from letsencrypt.client import le_util
from letsencrypt.client import log
from letsencrypt.client.display import display_util
from letsencrypt.client.display import ops
def main(): # pylint: disable=too-many-statements,too-many-branches
@ -77,9 +78,9 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
logger.setLevel(logging.INFO)
if args.use_curses:
logger.addHandler(log.DialogHandler())
displayer = display.NcursesDisplay()
displayer = display_util.NcursesDisplay()
else:
displayer = display.FileDisplay(sys.stdout)
displayer = display_util.FileDisplay(sys.stdout)
zope.component.provideUtility(displayer)
if args.view_config_changes:
@ -100,11 +101,11 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
# Make sure we actually get an installer that is functioning properly
# before we begin to try to use it.
try:
installer = client.determine_installer()
installer = client.determine_authenticator()
except errors.LetsEncryptMisconfigurationError as err:
logging.fatal("Please fix your configuration before proceeding. "
"The Installer exited with the following message: "
"%s", err)
logging.fatal("Please fix your configuration before proceeding.{0}"
"The Authenticator exited with the following message: "
"{1}".format(os.linesep, err))
sys.exit(1)
# Use the same object if possible
@ -113,7 +114,7 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
else:
auth = client.determine_authenticator()
domains = choose_names(installer) if args.domains is None else args.domains
domains = ops.choose_names(installer) if args.domains is None else args.domains
# Prepare for init of Client
if args.privkey is None:
@ -146,42 +147,6 @@ def display_eula():
sys.exit(0)
def choose_names(installer):
"""Display screen to select domains to validate.
:param installer: An installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
# This function adds all names found in the installer configuration
# Then filters them based on user selection
code, names = zope.component.getUtility(
interfaces.IDisplay).filter_names(get_all_names(installer))
if code == display.OK and names:
return names
else:
sys.exit(0)
def get_all_names(installer):
"""Return all valid names in the configuration.
:param installer: An installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
names = list(installer.get_all_names())
if not names:
logging.fatal("No domain names were found in your installation")
logging.fatal("Either specify which names you would like "
"letsencrypt to validate or add server names "
"to your virtual hosts")
sys.exit(1)
return names
def read_file(filename):
"""Returns the given file's contents with universal new line support.

View file

@ -61,8 +61,10 @@ setup(
'letsencrypt',
'letsencrypt.client',
'letsencrypt.client.apache',
'letsencrypt.client.display',
'letsencrypt.client.tests',
'letsencrypt.client.tests.apache',
'letsencrypt.client.tests.display',
'letsencrypt.scripts',
],
install_requires=install_requires,