Source code for letsencrypt.display.ops

"""Contains UI methods for LE user operations."""
import logging
import os

import zope.component

from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
from letsencrypt.display import util as display_util

logger = logging.getLogger(__name__)

# Define a helper function to avoid verbose code
z_util = zope.component.getUtility


[docs]def get_email(more=False, invalid=False): """Prompt for valid email address. :param bool more: explain why the email is strongly advisable, but how to skip it :param bool invalid: true if the user just typed something, but it wasn't a valid-looking email :returns: Email or ``None`` if cancelled by user. :rtype: str """ msg = "Enter email address (used for urgent notices and lost key recovery)" if invalid: msg = "There seem to be problems with that address. " + msg if more: msg += ('\n\nIf you really want to skip this, you can run the client with ' '--register-unsafely-without-email but make sure you backup your ' 'account key from /etc/letsencrypt/accounts\n\n') try: code, email = zope.component.getUtility(interfaces.IDisplay).input(msg) except errors.MissingCommandlineFlag: msg = ("You should register before running non-interactively, or provide --agree-tos" " and --email <email_address> flags") raise errors.MissingCommandlineFlag(msg) if code == display_util.OK: if le_util.safe_email(email): return email else: # TODO catch the server's ACME invalid email address error, and # make a similar call when that happens return get_email(more=True, invalid=(email != "")) else: return None
[docs]def choose_account(accounts): """Choose an account. :param list accounts: Containing at least one :class:`~letsencrypt.account.Account` """ # Note this will get more complicated once we start recording authorizations labels = [acc.slug for acc in accounts] code, index = z_util(interfaces.IDisplay).menu( "Please choose an account", labels) if code == display_util.OK: return accounts[index] else: return None
[docs]def choose_names(installer): """Display screen to select domains to validate. :param installer: An installer object :type installer: :class:`letsencrypt.interfaces.IInstaller` :returns: List of selected names :rtype: `list` of `str` """ if installer is None: logger.debug("No installer, picking names manually") return _choose_names_manually() domains = list(installer.get_all_names()) names = get_valid_domains(domains) if not names: manual = z_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), default=True) if manual: return _choose_names_manually() else: return [] code, names = _filter_names(names) if code == display_util.OK and names: return names else: return []
[docs]def get_valid_domains(domains): """Helper method for choose_names that implements basic checks on domain names :param list domains: Domain names to validate :return: List of valid domains :rtype: list """ valid_domains = [] for domain in domains: try: valid_domains.append(le_util.enforce_domain_sanity(domain)) except errors.ConfigurationError: continue return valid_domains
[docs]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 """ code, names = z_util(interfaces.IDisplay).checklist( "Which names would you like to activate HTTPS for?", tags=names, cli_flag="--domains") return code, [str(s) for s in names]
[docs]def _choose_names_manually(): """Manually input names for those without an installer.""" code, input_ = z_util(interfaces.IDisplay).input( "Please enter in your domain name(s) (comma and/or space separated) ", cli_flag="--domains") if code == display_util.OK: invalid_domains = dict() retry_message = "" try: domain_list = display_util.separate_list_input(input_) except UnicodeEncodeError: domain_list = [] retry_message = ( "Internationalized domain names are not presently " "supported.{0}{0}Would you like to re-enter the " "names?{0}").format(os.linesep) for i, domain in enumerate(domain_list): try: domain_list[i] = le_util.enforce_domain_sanity(domain) except errors.ConfigurationError as e: invalid_domains[domain] = e.message if len(invalid_domains): retry_message = ( "One or more of the entered domain names was not valid:" "{0}{0}").format(os.linesep) for domain in invalid_domains: retry_message = retry_message + "{1}: {2}{0}".format( os.linesep, domain, invalid_domains[domain]) retry_message = retry_message + ( "{0}Would you like to re-enter the names?{0}").format( os.linesep) if retry_message: # We had error in input retry = z_util(interfaces.IDisplay).yesno(retry_message) if retry: return _choose_names_manually() else: return domain_list return []
[docs]def success_installation(domains): """Display a box confirming the installation of HTTPS. .. todo:: This should be centered on the screen :param list domains: domain names which were enabled """ z_util(interfaces.IDisplay).notification( "Congratulations! You have successfully enabled {0}{1}{1}" "You should test your configuration at:{1}{2}".format( _gen_https_names(domains), os.linesep, os.linesep.join(_gen_ssl_lab_urls(domains))), height=(10 + len(domains)), pause=False)
[docs]def success_renewal(domains, action): """Display a box confirming the renewal of an existing certificate. .. todo:: This should be centered on the screen :param list domains: domain names which were renewed :param str action: can be "reinstall" or "renew" """ z_util(interfaces.IDisplay).notification( "Your existing certificate has been successfully {3}ed, and the " "new certificate has been installed.{1}{1}" "The new certificate covers the following domains: {0}{1}{1}" "You should test your configuration at:{1}{2}".format( _gen_https_names(domains), os.linesep, os.linesep.join(_gen_ssl_lab_urls(domains)), action), height=(14 + len(domains)), pause=False)
[docs]def _gen_ssl_lab_urls(domains): """Returns a list of urls. :param list domains: Each domain is a 'str' """ return ["https://www.ssllabs.com/ssltest/analyze.html?d=%s" % dom for dom in domains]
[docs]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://%s" % dom for dom in domains[:-1]), ", and https://", domains[-1]) return ""