From 8faa877c45e319b53e82258338ba7e07320856b1 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 30 Mar 2015 12:33:49 +0000 Subject: [PATCH 01/76] plugins.disco --- letsencrypt/client/constants.py | 3 + letsencrypt/client/display/ops.py | 22 +++++ letsencrypt/client/interfaces.py | 50 +++++------ letsencrypt/client/plugins/disco.py | 123 ++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 letsencrypt/client/plugins/disco.py diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 43cf5e8a0..8f2d083ef 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -4,6 +4,9 @@ import pkg_resources from letsencrypt.acme import challenges +SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" +"""Setuptools entry point group name for plugins.""" + S_SIZE = 32 """Size (in bytes) of secret base64-encoded octet string "s" used in challenges.""" diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 1cffe2846..8c97aadd5 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -10,6 +10,28 @@ from letsencrypt.client.display import util as display_util util = zope.component.getUtility # pylint: disable=invalid-name +def choose_plugin(prepared, question): + descs = [plugin.description if error is None + else "%s (Misconfigured)" % plugin.description + for (plugin, error) in prepared] + + while True: + code, index = util(interfaces.IDisplay).menu( + question, descs, help_label="More Info") + + if code == display_util.OK: + return prepared[index][0] + elif code == display_util.HELP: + if prepared[index][1] is not None: + msg = "Reported Error: %s" % prepared[index][1] + else: + msg = prepared[index][0].more_info() + util(interfaces.IDisplay).notification( + msg, height=display_util.HEIGHT) + else: + return + + def choose_authenticator(auths, errs): """Allow the user to choose their authenticator. diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 6779d4e1e..2390330b6 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -5,16 +5,13 @@ import zope.interface # pylint: disable=too-few-public-methods -class IAuthenticator(zope.interface.Interface): - """Generic Let's Encrypt Authenticator. +class IPlugin(zope.interface.Interface): + """Let's Encrypt plugin.""" - Class represents all possible tools processes that have the - ability to perform challenges and attain a certificate. - - """ + description = zope.interface.Attribute("Short plugin description") def prepare(): - """Prepare the authenticator. + """Prepare the plugin. Finish up any additional initialization. @@ -25,6 +22,23 @@ class IAuthenticator(zope.interface.Interface): """ + def more_info(): + """Human-readable string to help the user. + + Should describe the steps taken and any relevant info to help the user + decide which plugin to use. + + """ + + +class IAuthenticator(IPlugin): + """Generic Let's Encrypt Authenticator. + + Class represents all possible tools processes that have the + ability to perform challenges and attain a certificate. + + """ + def get_chall_pref(domain): """Return list of challenge preferences. @@ -70,14 +84,6 @@ class IAuthenticator(zope.interface.Interface): """ - def more_info(): - """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): """Let's Encrypt user-supplied configuration. @@ -124,25 +130,13 @@ class IConfig(zope.interface.Interface): "Contains standard Apache SSL directives.") -class IInstaller(zope.interface.Interface): +class IInstaller(IPlugin): """Generic Let's Encrypt Installer Interface. Represents any server that an X509 certificate can be placed. """ - def prepare(): - """Prepare the installer. - - Finish up any additional initialization. - - :raises letsencrypt.client.errors.LetsEncryptMisconfigurationError`: - when full initialization cannot be completed. - :raises letsencrypt.errors.LetsEncryptNoInstallationError`: - when the necessary programs/files cannot be located. - - """ - def get_all_names(): """Returns all names that may be authenticated.""" diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py new file mode 100644 index 000000000..d2526d583 --- /dev/null +++ b/letsencrypt/client/plugins/disco.py @@ -0,0 +1,123 @@ +"""Utilities for plugins discovery and selection.""" +import collections +import logging +import pkg_resources + +import zope.interface + +from letsencrypt.client import constants +from letsencrypt.client import errors +from letsencrypt.client import interfaces + +from letsencrypt.client.display import ops as display_ops + + +def name_plugins(plugins): + # TODO: actually make it unambiguous... + names = {} + for plugin_cls, entry_points in plugins.iteritems(): + entry_point = next(iter(entry_points)) # entry_points.peek() + names[plugin_cls] = entry_point.name + return names + + +def find_plugins(): + """Find plugins using setuptools entry points.""" + plugins = collections.defaultdict(set) + for entry_point in pkg_resources.iter_entry_points( + constants.SETUPTOOLS_PLUGINS_ENTRY_POINT): + plugin_cls = entry_point.load() + plugins[plugin_cls].add(entry_point) + return plugins + + +def filter_plugins(plugins, *ifaces_groups): + """Filter plugins based on interfaces.""" + return dict( + (plugin_cls, entry_points) + for plugin_cls, entry_points in plugins.iteritems() + if not ifaces_groups or any( + all(iface.implementedBy(plugin_cls) for iface in ifaces) + for ifaces in ifaces_groups)) + + +def verify_plugins(initialized, ifaces): + """Verify plugin objects.""" + verified = {} + for plugin_cls, plugin in initialized.iteritems(): + verifies = True + for iface in ifaces: # zope.interface.providedBy(plugin) + try: + zope.interface.verify.verifyObject(iface, plugin) + except zope.interface.exceptions.BrokenImplementation: + if iface.implementedBy(plugin_cls): + logging.debug( + "%s implements %s but object does " + "not verify", plugin_cls, iface.__name__) + verifies = False + break + if verifies: + verified[plugin_cls] = plugin + return verified + + +def prepare_plugins(initialized): + """Prepare plugins.""" + prepared = {} + + for plugin_cls, plugin in initialized.iteritems(): + error = None + try: + plugin.prepare() + except errors.LetsEncryptMisconfigurationError as error: + logging.debug("Misconfigured %s: %s", plugin, error) + except errors.LetsEncryptNoInstallationError as error: + logging.debug("No installation (%s): %s", plugin, error) + continue + prepared[plugin_cls] = (plugin, error) + + return prepared # succefully prepared + misconfigured + + +def pick_plugin(config, default, ifaces, question): + plugins = find_plugins() + names = name_plugins(plugins) + + if default is not None: + filtered = [names[default]] + else: + filtered = filter_plugins(plugins, ifaces) + + initialized = dict((plugin_cls, plugin_cls(config)) + for plugin_cls in filtered) + verified = verify_plugins(initialized, ifaces) + prepared = prepare_plugins(initialized) + + if len(prepared) > 1: + logging.debug("Multiple candidate plugins: %s", prepared) + return display_ops.choose_plugin(prepared.values(), question) + elif len(prepared) == 1: + logging.debug("Single candidate plugin: %s", prepared) + return prepared.values()[0] + else: + logging.debug("No candidate plugin") + return None + + +def pick_authenticator(config, default): + """Pick authentication plugin.""" + return pick_plugin( + config, default, (interfaces.IAuthenticator,), + "How would you like to authenticate with Let's Encrypt CA?") + + +def pick_installer(config, default): + """Pick installer plugin.""" + return pick_plugin(config, default, (interfaces.IInstaller,), + "How would you like to install certificates?") + +def pick_configurator(config, default): + """Pick configurator plugin.""" + return pick_plugin( + config, default, (interfaces.IAuthenticator, interfaces.IInstaller), + "How would you like to install certificates?") From 8bc55899e64a7b20ba6d8e2e00063865ee9f2d88 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 30 Mar 2015 12:34:22 +0000 Subject: [PATCH 02/76] Subparsers CLI --- examples/plugins/setup.py | 2 +- letsencrypt/client/constants.py | 4 + letsencrypt/scripts/main.py | 378 +++++++++++++++++++++----------- setup.py | 2 +- 4 files changed, 254 insertions(+), 132 deletions(-) diff --git a/examples/plugins/setup.py b/examples/plugins/setup.py index 845d6eb66..599d57020 100644 --- a/examples/plugins/setup.py +++ b/examples/plugins/setup.py @@ -9,7 +9,7 @@ setup( 'zope.interface', ], entry_points={ - 'letsencrypt.authenticators': [ + 'letsencrypt.plugins': [ 'example = letsencrypt_example_plugins:Authenticator', ], }, diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 8f2d083ef..9541aacac 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -1,4 +1,5 @@ """Let's Encrypt constants.""" +import logging import pkg_resources from letsencrypt.acme import challenges @@ -7,6 +8,9 @@ from letsencrypt.acme import challenges SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" +DEFAULT_VERBOSE_COUNT = -(logging.WARNING / 10) + + S_SIZE = 32 """Size (in bytes) of secret base64-encoded octet string "s" used in challenges.""" diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 3b4b7c10d..cc89510cf 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -1,11 +1,8 @@ -"""Parse command line and call the appropriate functions. - -.. todo:: Sanity check all input. Be sure to avoid shell code etc... - -""" +"""Let's Encrypt Client.""" +# TODO: Sanity check all input. Be sure to avoid shell code etc... import argparse +import collections import logging -import os import pkg_resources import sys @@ -17,46 +14,254 @@ import zope.interface.verify import letsencrypt from letsencrypt.client import configuration +from letsencrypt.client import constants from letsencrypt.client import client 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 util as display_util from letsencrypt.client.display import ops as display_ops - -SETUPTOOLS_AUTHENTICATORS_ENTRY_POINT = "letsencrypt.authenticators" -"""Setuptools entry point group name for Authenticator plugins.""" +from letsencrypt.client.plugins import disco as plugins_disco -def init_auths(config): - """Find (setuptools entry points) and initialize Authenticators.""" - auths = {} - for entrypoint in pkg_resources.iter_entry_points( - SETUPTOOLS_AUTHENTICATORS_ENTRY_POINT): - auth_cls = entrypoint.load() - auth = auth_cls(config) - try: - zope.interface.verify.verifyObject(interfaces.IAuthenticator, auth) - except zope.interface.exceptions.BrokenImplementation: - logging.debug( - "%r object does not provide IAuthenticator, skipping", - entrypoint.name) +def _common_run(args, config, authenticator, installer): + if args.domains is None: + doms = display_ops.choose_names(installer) + else: + doms = args.domains + + if not doms: + return + + # Prepare for init of Client + if args.authkey is None: + authkey = client.init_key(config.rsa_key_size, config.key_dir) + else: + authkey = le_util.Key(args.authkey[0], args.authkey[1]) + + acme = client.Client(config, authkey, authenticator, installer) + + # Validate the key and csr + client.validate_key_csr(authkey) + + return acme, doms, authkey + + +def run(args, config): + """Obtain a certificate and install.""" + if not args.eula: + display_eula() + + if args.configurator is not None and (args.installer is not None or + args.authenticator is not None): + return ("Either --configurator or --authenticator/--installer" + "pair, but not both, is allowed") + + if args.authenticator is not None or args.installer is not None: + installer = plugins_disco.pick_installer( + config, args.installer) + authenticator = plugins_disco.pick_authenticator( + config, args.authenticator) + else: + authenticator = installer = plugins_disco.pick_configurator( + config, args.configurator) + + if installer is None or authenticator is None: + return "Configurator could not be determined" + + acme, auth, installer, doms, auth_key = _common_run(args, config) + cert_file, chain_file = acme.obtain_certificate(doms) + acme.deploy_certificate(doms, authkey, cert_file, chain_file) + acme.enhance_config(doms, args.redirect) + + +def auth(args, config): + """Obtain a certificate (no install).""" + authenticator = plugins_disco.pick_authenticator(config, args.authenticator) + if authenticator is None: + return "Authenticator could not be determined" + + if args.installer is not None: + installer = plugins_disco.pick_installer(config, args.installer) + else: + installer = None + + if args.domains is None: + if args.installer is not None: + return ("--domains not set and provided --installer does not " + "help in autodiscovery") else: - auths[auth] = entrypoint.name - return auths + return ("Please specify --domains, or --installer that will " + "help in domain names autodiscovery") + + acme, doms, _ = _common_run( + args, config, authenticator=authenticator, installer=None) + acme.obtain_certificate(doms) + + +def install(args, config): + """Install (no auth).""" + installer = plugins_disco.pick_installer(config, args.installer) + if installer is None: + return "Installer could not be determined" + acme, doms, authkey = _common_run( + args, config, authenticator=None, installer=installer) + assert args.cert_file is not None and args.chain_file is not None + acme.deploy_certificate(doms, authkey, args.cert_file, args.chain_file) + acme.enhance_config(doms, args.redirect) + + +def revoke(args, config): + """Revoke.""" + if args.rev_cert is None and args.rev_key is None: + return "At least one of --certificate or --key is required" + client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) + + +def rollback(args, config): + """Rollback.""" + client.rollback(args.checkpoints, config) + + +def config_changes(args, config): + """View config changes. + + View checkpoints and associated configuration changes. + + """ + print args, config + client.config_changes(config) + + +def _print_plugins(filtered, plugins, names): + if not filtered: + print "No plugins found" + + for plugin_cls, content in filtered.iteritems(): + print "* {0}".format(names[plugin_cls]) + print "Description: {0}".format(plugin_cls.description) + print "Interfaces: {0}".format(", ".join( + iface.__name__ for iface in zope.interface.implementedBy( + plugin_cls))) + print "Entry points:" + for entry_point in plugins[plugin_cls]: + print "- {0.dist}: {0}".format(entry_point) + + # if filtered == prepared: + if isinstance(content, tuple) and content[1] is not None: + print content[1] # error + print + + +def plugins(args, config): + """List plugins.""" + plugins = plugins_disco.find_plugins() + logging.debug("Discovered plugins: %s", plugins) + + names = plugins_disco.name_plugins(plugins) + + ifaces = [] if args.ifaces is None else args.ifaces + filtered = plugins_disco.filter_plugins( + plugins, *((iface,) for iface in ifaces)) + logging.debug("Filtered plugins: %s", filtered) + + if not args.init and not args.prepare: + return _print_plugins(filtered, plugins, names) + + initialized = dict((plugin_cls, plugin_cls(config)) + for plugin_cls in filtered) + verified = plugins_disco.verify_plugins(initialized, ifaces) + logging.debug("Verified plugins: %s", initialized) + + if not args.prepare: + return _print_plugins(initialized, plugins, names) + + prepared = plugins_disco.prepare_plugins(initialized) + logging.debug("Prepared plugins: %s", plugins) + + _print_plugins(prepared, plugins, names) + plugins_disco + + +def display_eula(): + """Displays the end user agreement.""" + eula = pkg_resources.resource_string("letsencrypt", "EULA") + if not zope.component.getUtility(interfaces.IDisplay).yesno( + eula, "Agree", "Cancel"): + sys.exit(0) + + +def read_file(filename): + """Returns the given file's contents with universal new line support. + + :param str filename: Filename + + :returns: A tuple of filename and its contents + :rtype: tuple + + :raises argparse.ArgumentTypeError: File does not exist or is not readable. + + """ + try: + return filename, open(filename, "rU").read() + except IOError as exc: + raise argparse.ArgumentTypeError(exc.strerror) def create_parser(): """Create parser.""" - parser = confargparse.ConfArgParser( - description="letsencrypt client %s" % letsencrypt.__version__) + parser = confargparse.ConfArgParser(description=__doc__) + + # --help is automatically provided by argparse + parser.add_argument( + "--version", action="version", version="%(prog)s {0}".format( + letsencrypt.__version__)) + parser.add_argument( + "-v", "--verbose", dest="verbose_count", action="count", + default=constants.DEFAULT_VERBOSE_COUNT) + + subparsers = parser.add_subparsers(metavar="SUBCOMMAND") + def add_subparser(name, func): + subparser = subparsers.add_parser( + name, help=func.__doc__.splitlines()[0], description=func.__doc__) + subparser.set_defaults(func=func) + return subparser + + parser_run = add_subparser("run", run) + parser_auth = add_subparser("auth", auth) + parser_install = add_subparser("install", install) + parser_revoke = add_subparser("revoke", revoke) + parser_rollback = add_subparser("rollback", rollback) + parrser_config_changes = add_subparser("config_changes", config_changes) + + parser_plugins = add_subparser("plugins", plugins) + parser_plugins.add_argument("--init", action="store_true") + parser_plugins.add_argument("--prepare", action="store_true") + parser_plugins.add_argument( + "--authenticators", action="append_const", dest="ifaces", + const=interfaces.IAuthenticator) + parser_plugins.add_argument( + "--installers", action="append_const", dest="ifaces", + const=interfaces.IInstaller) add = parser.add_argument config_help = lambda name: interfaces.IConfig[name].__doc__ - add("-d", "--domains", metavar="DOMAIN", nargs="+") + parser_run.add_argument("--configurator") + for subparser in parser_run, parser_auth: + subparser.add_argument("-a", "--authenticator") + for subparser in parser_run, parser_auth, parser_install: + # parser_auth uses --installer for domains autodiscovery + subparser.add_argument("-i", "--installer") + # positional arg shadows --domains, instead of appending, and + # --domains is useful, because it can be stored in config + #for subparser in parser_run, parser_auth, parser_install: + # subparser.add_argument("domains", nargs="*", metavar="domain") + + add("-d", "--domains", metavar="DOMAIN", action="append") add("-s", "--server", default="letsencrypt-demo.org:443", help=config_help("server")) @@ -65,17 +270,16 @@ def create_parser(): add("-B", "--rsa-key-size", type=int, default=2048, metavar="N", help=config_help("rsa_key_size")) - add("-R", "--revoke", action="store_true", - help="Revoke a certificate from a menu.") - add("--revoke-certificate", dest="rev_cert", type=read_file, + parser_revoke.add_argument( + "--certificate", dest="rev_cert", type=read_file, metavar="CERT_PATH", help="Revoke a specific certificate.") - add("--revoke-key", dest="rev_key", type=read_file, + parser_revoke.add_argument( + "--key", dest="rev_key", type=read_file, metavar="KEY_PATH", help="Revoke all certs generated by the provided authorized key.") - add("-b", "--rollback", type=int, default=0, metavar="N", + parser_rollback.add_argument( + "--checkpoints", type=int, default=0, metavar="N", help="Revert configuration N number of checkpoints.") - add("-v", "--view-config-changes", action="store_true", - help="View checkpoints and associated configuration changes.") # TODO: resolve - assumes binary logic while client.py assumes ternary. add("-r", "--redirect", action="store_true", @@ -127,112 +331,26 @@ def main(): # pylint: disable=too-many-branches, too-many-statements config = configuration.NamespaceConfig(args) # note: check is done after arg parsing as --help should work w/o root also. - if not os.geteuid() == 0: - sys.exit( - "{0}Root is required to run letsencrypt. Please use sudo.{0}" - .format(os.linesep)) + #if not os.geteuid() == 0: + # return ( + # "{0}Root is required to run letsencrypt. Please use sudo.{0}" + # .format(os.linesep)) # Set up logging + level = -args.verbose_count * 10 logger = logging.getLogger() - logger.setLevel(logging.INFO) + logger.setLevel(level) + logging.debug("Logging level set at %d", level) + # displayer if args.use_curses: logger.addHandler(log.DialogHandler()) displayer = display_util.NcursesDisplay() else: displayer = display_util.FileDisplay(sys.stdout) - zope.component.provideUtility(displayer) - if args.view_config_changes: - client.view_config_changes(config) - sys.exit() - - if args.revoke or args.rev_cert is not None or args.rev_key is not None: - client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) - sys.exit() - - if args.rollback > 0: - client.rollback(args.rollback, config) - sys.exit() - - if not args.eula: - display_eula() - - all_auths = init_auths(config) - logging.debug('Initialized authenticators: %s', all_auths.values()) - try: - auth = client.determine_authenticator(all_auths.keys()) - except errors.LetsEncryptClientError: - logging.critical("No authentication mechanisms were found on your " - "system.") - sys.exit(1) - - if auth is None: - sys.exit(0) - - # Use the same object if possible - if interfaces.IInstaller.providedBy(auth): # pylint: disable=no-member - installer = auth - else: - # This is simple and avoids confusion right now. - installer = None - - if args.domains is None: - doms = display_ops.choose_names(installer) - else: - doms = args.domains - - if not doms: - sys.exit(0) - - # Prepare for init of Client - if args.authkey is None: - authkey = client.init_key(args.rsa_key_size, config.key_dir) - else: - authkey = le_util.Key(args.authkey[0], args.authkey[1]) - - acme = client.Client(config, authkey, auth, installer) - - # Validate the key and csr - client.validate_key_csr(authkey) - - # This more closely mimics the capabilities of the CLI - # It should be possible for reconfig only, install-only, no-install - # I am not sure the best way to handle all of the unimplemented abilities, - # but this code should be safe on all environments. - cert_file = None - if auth is not None: - cert_file, chain_file = acme.obtain_certificate(doms) - if installer is not None and cert_file is not None: - acme.deploy_certificate(doms, authkey, cert_file, chain_file) - if installer is not None: - acme.enhance_config(doms, args.redirect) - - -def display_eula(): - """Displays the end user agreement.""" - eula = pkg_resources.resource_string("letsencrypt", "EULA") - if not zope.component.getUtility(interfaces.IDisplay).yesno( - eula, "Agree", "Cancel"): - sys.exit(0) - - -def read_file(filename): - """Returns the given file's contents with universal new line support. - - :param str filename: Filename - - :returns: A tuple of filename and its contents - :rtype: tuple - - :raises argparse.ArgumentTypeError: File does not exist or is not readable. - - """ - try: - return filename, open(filename, "rU").read() - except IOError as exc: - raise argparse.ArgumentTypeError(exc.strerror) + return args.func(args, config) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/setup.py b/setup.py index ca7de3abb..413345125 100644 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ setup( 'letsencrypt = letsencrypt.scripts.main:main', 'jws = letsencrypt.acme.jose.jws:CLI.run', ], - 'letsencrypt.authenticators': [ + 'letsencrypt.plugins': [ 'apache = letsencrypt.client.plugins.apache.configurator' ':ApacheConfigurator', 'standalone = letsencrypt.client.plugins.standalone.authenticator' From b76542afb3acf1903a4a993f7c9207f090af63a2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 2 Apr 2015 06:55:50 +0000 Subject: [PATCH 03/76] constants:DEFAULT_* --- letsencrypt/client/constants.py | 17 +++++++++++++++++ letsencrypt/scripts/main.py | 34 ++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 9541aacac..727404030 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -8,7 +8,24 @@ from letsencrypt.acme import challenges SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" + +# CLI/IConfig defaults DEFAULT_VERBOSE_COUNT = -(logging.WARNING / 10) +DEFAULT_SERVER = "letsencrypt-demo.org:443" +DEFAULT_RSA_KEY_SIZE = 2048 +DEFAULT_ROLLBACK_CHECKPOINTS = 0 +DEFAULT_CONFIG_DIR = "/etc/letsencrypt" +DEFAULT_WORK_DIR = "/var/lib/letsencrypt" +DEFAULT_BACKUP_DIR = "/var/lib/letsencrypt/backups" +DEFAULT_KEY_DIR = "/etc/letsencrypt/keys" +DEFAULT_CERTS_DIR = "/etc/letsencrypt/certs" +DEFAULT_CERT_PATH = "/etc/letsencrypt/certs/cert-letsencrypt.pem" +DEFAULT_CHAIN_PATH = "/etc/letsencrypt/certs/chain-letsencrypt.pem" +DEFAULT_APACHE_SERVER_ROOT = "/etc/apache2" +DEFAULT_APACHE_MOD_SSL_CONF = "/etc/letsencrypt/options-ssl.conf" +DEFAULT_APACHE_CTL = "apache2ctl" +DEFAULT_APACHE_ENMOD = "a2enmod" +DEFAULT_APACHE_INIT_SCRIPT = "/etc/init.d/apache2" S_SIZE = 32 diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index cc89510cf..e421e7ac2 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -262,12 +262,13 @@ def create_parser(): # subparser.add_argument("domains", nargs="*", metavar="domain") add("-d", "--domains", metavar="DOMAIN", action="append") - add("-s", "--server", default="letsencrypt-demo.org:443", + add("-s", "--server", default=constants.DEFAULT_SERVER, help=config_help("server")) add("-k", "--authkey", type=read_file, help="Path to the authorized key file") - add("-B", "--rsa-key-size", type=int, default=2048, metavar="N", + add("-B", "--rsa-key-size", type=int, metavar="N", + default=constants.DEFAULT_RSA_KEY_SIZE, help=config_help("rsa_key_size")) parser_revoke.add_argument( @@ -278,7 +279,8 @@ def create_parser(): help="Revoke all certs generated by the provided authorized key.") parser_rollback.add_argument( - "--checkpoints", type=int, default=0, metavar="N", + "--checkpoints", type=int, metavar="N", + default=constants.DEFAULT_ROLLBACK_CHECKPOINTS, help="Revert configuration N number of checkpoints.") # TODO: resolve - assumes binary logic while client.py assumes ternary. @@ -294,31 +296,33 @@ def create_parser(): add("-t", "--text", dest="use_curses", action="store_false", help="Use the text output instead of the curses UI.") - add("--config-dir", default="/etc/letsencrypt", + add("--config-dir", default=constants.DEFAULT_CONFIG_DIR, help=config_help("config_dir")) - add("--work-dir", default="/var/lib/letsencrypt", + add("--work-dir", default=constants.DEFAULT_WORK_DIR, help=config_help("work_dir")) - add("--backup-dir", default="/var/lib/letsencrypt/backups", + add("--backup-dir", default=constants.DEFAULT_BACKUP_DIR, help=config_help("backup_dir")) - add("--key-dir", default="/etc/letsencrypt/keys", + add("--key-dir", default=constants.DEFAULT_KEY_DIR, help=config_help("key_dir")) - add("--cert-dir", default="/etc/letsencrypt/certs", + add("--cert-dir", default=constants.DEFAULT_CERTS_DIR, help=config_help("cert_dir")) add("--le-vhost-ext", default="-le-ssl.conf", help=config_help("le_vhost_ext")) - add("--cert-path", default="/etc/letsencrypt/certs/cert-letsencrypt.pem", + add("--cert-path", default=constants.DEFAULT_CERT_PATH, help=config_help("cert_path")) - add("--chain-path", default="/etc/letsencrypt/certs/chain-letsencrypt.pem", + add("--chain-path", default=constants.DEFAULT_CHAIN_PATH, help=config_help("chain_path")) - add("--apache-server-root", default="/etc/apache2", + add("--apache-server-root", default=constants.DEFAULT_APACHE_SERVER_ROOT, help=config_help("apache_server_root")) - add("--apache-mod-ssl-conf", default="/etc/letsencrypt/options-ssl.conf", + add("--apache-mod-ssl-conf", default=constants.DEFAULT_APACHE_MOD_SSL_CONF, help=config_help("apache_mod_ssl_conf")) - add("--apache-ctl", default="apache2ctl", help=config_help("apache_ctl")) - add("--apache-enmod", default="a2enmod", help=config_help("apache_enmod")) - add("--apache-init-script", default="/etc/init.d/apache2", + add("--apache-ctl", default=constants.DEFAULT_APACHE_CTL, + help=config_help("apache_ctl")) + add("--apache-enmod", default=constants.DEFAULT_APACHE_ENMOD, + help=config_help("apache_enmod")) + add("--apache-init-script", default=constants.DEFAULT_APACHE_INIT_SCRIPT, help=config_help("apache_init_script")) return parser From 975fe1c65b0f5e7c6b6b97b04de646a0115e59c9 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 06:23:25 +0000 Subject: [PATCH 04/76] Move scripts/main to client/cli.py --- letsencrypt/{scripts/main.py => client/cli.py} | 2 +- letsencrypt/scripts/__init__.py | 1 - setup.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) rename letsencrypt/{scripts/main.py => client/cli.py} (99%) delete mode 100644 letsencrypt/scripts/__init__.py diff --git a/letsencrypt/scripts/main.py b/letsencrypt/client/cli.py similarity index 99% rename from letsencrypt/scripts/main.py rename to letsencrypt/client/cli.py index e421e7ac2..47bd9189a 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/client/cli.py @@ -1,4 +1,4 @@ -"""Let's Encrypt Client.""" +"""Let's Encrypt CLI.""" # TODO: Sanity check all input. Be sure to avoid shell code etc... import argparse import collections diff --git a/letsencrypt/scripts/__init__.py b/letsencrypt/scripts/__init__.py deleted file mode 100644 index 3860534ca..000000000 --- a/letsencrypt/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt scripts.""" diff --git a/setup.py b/setup.py index 413345125..2f9e85526 100644 --- a/setup.py +++ b/setup.py @@ -104,7 +104,6 @@ setup( 'letsencrypt.client.plugins.standalone.tests', 'letsencrypt.client.tests', 'letsencrypt.client.tests.display', - 'letsencrypt.scripts', ], install_requires=install_requires, @@ -119,7 +118,7 @@ setup( entry_points={ 'console_scripts': [ - 'letsencrypt = letsencrypt.scripts.main:main', + 'letsencrypt = letsencrypt.client.cli:main', 'jws = letsencrypt.acme.jose.jws:CLI.run', ], 'letsencrypt.plugins': [ From 1001b027cb47096a2fc7a78ad812679beafafd3d Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 06:47:27 +0000 Subject: [PATCH 05/76] ConfArgParse -> ConfigArgParse ConfArgParse + subparsers = config scoped only to subcommand ConfArgParse doesn't seem to respect formatter_class (no defaults) ConfigArgParse works with ENV variables https://github.com/bw2/ConfigArgParse#design-notes --- letsencrypt/client/cli.py | 7 +++++-- setup.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 47bd9189a..d3465469e 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -6,7 +6,7 @@ import logging import pkg_resources import sys -import confargparse +import configargparse import zope.component import zope.interface.exceptions import zope.interface.verify @@ -213,7 +213,10 @@ def read_file(filename): def create_parser(): """Create parser.""" - parser = confargparse.ConfArgParser(description=__doc__) + parser = configargparse.ArgParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + args_for_setting_config_path=["-c", "--config"]) # --help is automatically provided by argparse parser.add_argument( diff --git a/setup.py b/setup.py index 2f9e85526..6e0ce0aa6 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ changes = read_file(os.path.join(here, 'CHANGES.rst')) install_requires = [ 'argparse', - 'ConfArgParse', + 'ConfigArgParse', 'jsonschema', 'mock', 'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304) From 048876a1dff47646ae996071b8f7620d5475c5a9 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 07:09:23 +0000 Subject: [PATCH 06/76] default_config_files --- letsencrypt/client/cli.py | 3 ++- letsencrypt/client/constants.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index d3465469e..600d6791b 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -216,7 +216,8 @@ def create_parser(): parser = configargparse.ArgParser( description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter, - args_for_setting_config_path=["-c", "--config"]) + args_for_setting_config_path=["-c", "--config"], + default_config_files=constants.DEFAULT_CONFIG_FILES) # --help is automatically provided by argparse parser.add_argument( diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 727404030..5c13ea1a3 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -10,6 +10,7 @@ SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" # CLI/IConfig defaults +DEFAULT_CONFIG_FILES = ["/etc/letsencrypt/cli.ini"] DEFAULT_VERBOSE_COUNT = -(logging.WARNING / 10) DEFAULT_SERVER = "letsencrypt-demo.org:443" DEFAULT_RSA_KEY_SIZE = 2048 From 0e3504cecc33a5f691865c87e3fd94cf12da6b59 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 07:20:45 +0000 Subject: [PATCH 07/76] stub cli_test --- letsencrypt/client/cli.py | 2 +- letsencrypt/client/tests/cli_test.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 letsencrypt/client/tests/cli_test.py diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 600d6791b..1be10d75b 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -361,4 +361,4 @@ def main(): # pylint: disable=too-many-branches, too-many-statements if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) # pragma: no cover diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py new file mode 100644 index 000000000..8afede3bc --- /dev/null +++ b/letsencrypt/client/tests/cli_test.py @@ -0,0 +1,11 @@ +import unittest + + +class CLITest(unittest.TestCase): + + def test_it(self): + from letsencrypt.client import cli + + +if __name__ == '__main__': + unittest.main() From bad3a959d43bee869f3ad6021e1e2312a1526fc1 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 07:22:15 +0000 Subject: [PATCH 08/76] random cli cleanup --- letsencrypt/client/cli.py | 85 +++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 1be10d75b..f49918bf2 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -3,6 +3,7 @@ import argparse import collections import logging +import os import pkg_resources import sys @@ -210,6 +211,8 @@ def read_file(filename): except IOError as exc: raise argparse.ArgumentTypeError(exc.strerror) +def config_help(name): + return interfaces.IConfig[name].__doc__ def create_parser(): """Create parser.""" @@ -218,14 +221,19 @@ def create_parser(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, args_for_setting_config_path=["-c", "--config"], default_config_files=constants.DEFAULT_CONFIG_FILES) + add = parser.add_argument # --help is automatically provided by argparse - parser.add_argument( - "--version", action="version", version="%(prog)s {0}".format( + add("--version", action="version", version="%(prog)s {0}".format( letsencrypt.__version__)) - parser.add_argument( - "-v", "--verbose", dest="verbose_count", action="count", + add("-v", "--verbose", dest="verbose_count", action="count", default=constants.DEFAULT_VERBOSE_COUNT) + add("--no-confirm", dest="no_confirm", action="store_true", + help="Turn off confirmation screens, currently used for --revoke") + add("-e", "--agree-tos", dest="eula", action="store_true", + help="Skip the end user license agreement screen.") + add("-t", "--text", dest="use_curses", action="store_false", + help="Use the text output instead of the curses UI.") subparsers = parser.add_subparsers(metavar="SUBCOMMAND") def add_subparser(name, func): @@ -251,9 +259,6 @@ def create_parser(): "--installers", action="append_const", dest="ifaces", const=interfaces.IInstaller) - add = parser.add_argument - config_help = lambda name: interfaces.IConfig[name].__doc__ - parser_run.add_argument("--configurator") for subparser in parser_run, parser_auth: subparser.add_argument("-a", "--authenticator") @@ -268,12 +273,15 @@ def create_parser(): add("-d", "--domains", metavar="DOMAIN", action="append") add("-s", "--server", default=constants.DEFAULT_SERVER, help=config_help("server")) - add("-k", "--authkey", type=read_file, help="Path to the authorized key file") add("-B", "--rsa-key-size", type=int, metavar="N", default=constants.DEFAULT_RSA_KEY_SIZE, help=config_help("rsa_key_size")) + # TODO: resolve - assumes binary logic while client.py assumes ternary. + add("-r", "--redirect", action="store_true", + help="Automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost.") parser_revoke.add_argument( "--certificate", dest="rev_cert", type=read_file, metavar="CERT_PATH", @@ -287,19 +295,13 @@ def create_parser(): default=constants.DEFAULT_ROLLBACK_CHECKPOINTS, help="Revert configuration N number of checkpoints.") - # TODO: resolve - assumes binary logic while client.py assumes ternary. - add("-r", "--redirect", action="store_true", - help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost.") + paths_parser(parser.add_argument_group("paths")) + apache_parser(parser.add_argument_group("apache")) + return parser - add("--no-confirm", dest="no_confirm", action="store_true", - help="Turn off confirmation screens, currently used for --revoke") - - add("-e", "--agree-tos", dest="eula", action="store_true", - help="Skip the end user license agreement screen.") - add("-t", "--text", dest="use_curses", action="store_false", - help="Use the text output instead of the curses UI.") +def paths_parser(parser): + add = parser.add_argument add("--config-dir", default=constants.DEFAULT_CONFIG_DIR, help=config_help("config_dir")) add("--work-dir", default=constants.DEFAULT_WORK_DIR, @@ -318,6 +320,13 @@ def create_parser(): add("--chain-path", default=constants.DEFAULT_CHAIN_PATH, help=config_help("chain_path")) + return parser + + +def apache_parser(parser): + # TODO: this should probably be moved to plugins/apache, in + # general all plugins should be able to inject config options + add = parser.add_argument add("--apache-server-root", default=constants.DEFAULT_APACHE_SERVER_ROOT, help=config_help("apache_server_root")) add("--apache-mod-ssl-conf", default=constants.DEFAULT_APACHE_MOD_SSL_CONF, @@ -328,35 +337,41 @@ def create_parser(): help=config_help("apache_enmod")) add("--apache-init-script", default=constants.DEFAULT_APACHE_INIT_SCRIPT, help=config_help("apache_init_script")) - return parser -def main(): # pylint: disable=too-many-branches, too-many-statements +def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) - args = create_parser().parse_args() + args = create_parser().parse_args(args) config = configuration.NamespaceConfig(args) - # note: check is done after arg parsing as --help should work w/o root also. - #if not os.geteuid() == 0: - # return ( - # "{0}Root is required to run letsencrypt. Please use sudo.{0}" - # .format(os.linesep)) - - # Set up logging - level = -args.verbose_count * 10 - logger = logging.getLogger() - logger.setLevel(level) - logging.debug("Logging level set at %d", level) - # displayer + # Displayer if args.use_curses: - logger.addHandler(log.DialogHandler()) displayer = display_util.NcursesDisplay() else: displayer = display_util.FileDisplay(sys.stdout) zope.component.provideUtility(displayer) + # Logging + level = -args.verbose_count * 10 + logger = logging.getLogger() + logger.setLevel(level) + logging.debug("Logging level set at %d", level) + if args.use_curses: + logger.addHandler(log.DialogHandler()) + + if not os.geteuid() == 0: + logging.warning( + "Root (sudo) is required to run most of letsencrypt functionality.") + # check must be done after arg parsing as --help should work + # w/o root; on the other hand, e.g. "letsencrypt run + # --authenticator dns" or "letsencrypt plugins" does not + # require root as well + #return ( + # "{0}Root is required to run letsencrypt. Please use sudo.{0}" + # .format(os.linesep)) + return args.func(args, config) From 88dc56186ef931a998e9741bd1f8c9e08a18514e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 08:32:34 +0000 Subject: [PATCH 09/76] Move apache constants/args/IConfig to its subdirectory --- letsencrypt/client/cli.py | 27 +++++++++---------- letsencrypt/client/constants.py | 15 ----------- letsencrypt/client/interfaces.py | 12 --------- .../client/plugins/apache/configurator.py | 26 +++++++++++++----- .../client/plugins/apache/constants.py | 20 ++++++++++++++ .../client/plugins/apache/tests/util.py | 4 +-- 6 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 letsencrypt/client/plugins/apache/constants.py diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index f49918bf2..221c51969 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -27,6 +27,8 @@ from letsencrypt.client.display import ops as display_ops from letsencrypt.client.plugins import disco as plugins_disco +from letsencrypt.client.plugins.apache import configurator as apache_configurator + def _common_run(args, config, authenticator, installer): if args.domains is None: @@ -296,7 +298,11 @@ def create_parser(): help="Revert configuration N number of checkpoints.") paths_parser(parser.add_argument_group("paths")) - apache_parser(parser.add_argument_group("apache")) + + # TODO: plugin_parser should be called for every detected plugin + plugin_parser( + parser.add_argument_group("apache"), prefix="apache", + plugin_cls=apache_configurator.ApacheConfigurator) return parser @@ -323,20 +329,11 @@ def paths_parser(parser): return parser -def apache_parser(parser): - # TODO: this should probably be moved to plugins/apache, in - # general all plugins should be able to inject config options - add = parser.add_argument - add("--apache-server-root", default=constants.DEFAULT_APACHE_SERVER_ROOT, - help=config_help("apache_server_root")) - add("--apache-mod-ssl-conf", default=constants.DEFAULT_APACHE_MOD_SSL_CONF, - help=config_help("apache_mod_ssl_conf")) - add("--apache-ctl", default=constants.DEFAULT_APACHE_CTL, - help=config_help("apache_ctl")) - add("--apache-enmod", default=constants.DEFAULT_APACHE_ENMOD, - help=config_help("apache_enmod")) - add("--apache-init-script", default=constants.DEFAULT_APACHE_INIT_SCRIPT, - help=config_help("apache_init_script")) +def plugin_parser(parser, prefix, plugin_cls): + def add(arg_name_no_prefix, *args, **kwargs): + parser.add_argument( + "--{0}-{1}".format(prefix, arg_name_no_prefix), *args, **kwargs) + plugin_cls.add_parser_arguments(add) return parser diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 5c13ea1a3..6c8519196 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -22,11 +22,6 @@ DEFAULT_KEY_DIR = "/etc/letsencrypt/keys" DEFAULT_CERTS_DIR = "/etc/letsencrypt/certs" DEFAULT_CERT_PATH = "/etc/letsencrypt/certs/cert-letsencrypt.pem" DEFAULT_CHAIN_PATH = "/etc/letsencrypt/certs/chain-letsencrypt.pem" -DEFAULT_APACHE_SERVER_ROOT = "/etc/apache2" -DEFAULT_APACHE_MOD_SSL_CONF = "/etc/letsencrypt/options-ssl.conf" -DEFAULT_APACHE_CTL = "apache2ctl" -DEFAULT_APACHE_ENMOD = "a2enmod" -DEFAULT_APACHE_INIT_SCRIPT = "/etc/init.d/apache2" S_SIZE = 32 @@ -55,16 +50,6 @@ List of expected options parameters: """ -APACHE_MOD_SSL_CONF = pkg_resources.resource_filename( - "letsencrypt.client.plugins.apache", "options-ssl.conf") -"""Path to the Apache mod_ssl config file found in the Let's Encrypt -distribution.""" - -APACHE_REWRITE_HTTPS_ARGS = [ - "^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"] -"""Apache rewrite rule arguments used for redirections to https vhost""" - - DVSNI_CHALLENGE_PORT = 443 """Port to perform DVSNI challenge.""" diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 2390330b6..80a43f885 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -117,18 +117,6 @@ class IConfig(zope.interface.Interface): cert_path = zope.interface.Attribute("Let's Encrypt certificate file.") chain_path = zope.interface.Attribute("Let's Encrypt chain file.") - apache_server_root = zope.interface.Attribute( - "Apache server root directory.") - apache_ctl = zope.interface.Attribute( - "Path to the 'apache2ctl' binary, used for 'configtest' and " - "retrieving Apache2 version number.") - apache_enmod = zope.interface.Attribute( - "Path to the Apache 'a2enmod' binary.") - apache_init_script = zope.interface.Attribute( - "Path to the Apache init script (used for server reload/restart).") - apache_mod_ssl_conf = zope.interface.Attribute( - "Contains standard Apache SSL directives.") - class IInstaller(IPlugin): """Generic Let's Encrypt Installer Interface. diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index e6104a559..55f7e2875 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -13,11 +13,11 @@ from letsencrypt.acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import augeas_configurator -from letsencrypt.client import constants from letsencrypt.client import errors from letsencrypt.client import interfaces from letsencrypt.client import le_util +from letsencrypt.client.plugins.apache import constants from letsencrypt.client.plugins.apache import dvsni from letsencrypt.client.plugins.apache import obj from letsencrypt.client.plugins.apache import parser @@ -82,6 +82,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): description = "Apache Web Server" + @classmethod + def add_parser_arguments(cls, add): + add("server-root", default=constants.DEFAULT_SERVER_ROOT, + help="Apache server root directory.") + add("mod-ssl-conf", default=constants.DEFAULT_MOD_SSL_CONF, + help="Contains standard Apache SSL directives.") + add("ctl", default=constants.DEFAULT_CTL, + help="Path to the 'apache2ctl' binary, used for 'configtest' and " + "retrieving Apache2 version number.") + add("enmod", default=constants.DEFAULT_ENMOD, + help="Path to the Apache 'a2enmod' binary.") + add("init-script", default=constants.DEFAULT_INIT_SCRIPT, + help="Path to the Apache init script (used for server reload/restart).") + def __init__(self, config, version=None): """Initialize an Apache Configurator. @@ -599,7 +613,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Add directives to server self.parser.add_dir(general_v.path, "RewriteEngine", "On") self.parser.add_dir(general_v.path, "RewriteRule", - constants.APACHE_REWRITE_HTTPS_ARGS) + constants.REWRITE_HTTPS_ARGS) self.save_notes += ("Redirecting host in %s to ssl vhost in %s\n" % (general_v.filep, ssl_vhost.filep)) self.save() @@ -638,10 +652,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if not rewrite_path: # "No existing redirection for virtualhost" return False, -1 - if len(rewrite_path) == len(constants.APACHE_REWRITE_HTTPS_ARGS): + if len(rewrite_path) == len(constants.REWRITE_HTTPS_ARGS): for idx, match in enumerate(rewrite_path): if (self.aug.get(match) != - constants.APACHE_REWRITE_HTTPS_ARGS[idx]): + constants.REWRITE_HTTPS_ARGS[idx]): # Not a letsencrypt https rewrite return True, 2 # Existing letsencrypt https rewrite rule is in place @@ -693,7 +707,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "LogLevel warn\n" "\n" % (servername, serveralias, - " ".join(constants.APACHE_REWRITE_HTTPS_ARGS))) + " ".join(constants.REWRITE_HTTPS_ARGS))) # Write out the file # This is the default name @@ -1160,4 +1174,4 @@ def temp_install(options_ssl): # Check to make sure options-ssl.conf is installed if not os.path.isfile(options_ssl): - shutil.copyfile(constants.APACHE_MOD_SSL_CONF, options_ssl) + shutil.copyfile(constants.MOD_SSL_CONF, options_ssl) diff --git a/letsencrypt/client/plugins/apache/constants.py b/letsencrypt/client/plugins/apache/constants.py new file mode 100644 index 000000000..63b3bd148 --- /dev/null +++ b/letsencrypt/client/plugins/apache/constants.py @@ -0,0 +1,20 @@ +"""Apache plugin constants.""" +import pkg_resources + + +# CLI/IConfig defaults +DEFAULT_SERVER_ROOT = "/etc/apache2" +DEFAULT_MOD_SSL_CONF = "/etc/letsencrypt/options-ssl.conf" +DEFAULT_CTL = "apache2ctl" +DEFAULT_ENMOD = "a2enmod" +DEFAULT_INIT_SCRIPT = "/etc/init.d/apache2" + + +MOD_SSL_CONF = pkg_resources.resource_filename( + "letsencrypt.client.plugins.apache", "options-ssl.conf") +"""Path to the Apache mod_ssl config file found in the Let's Encrypt +distribution.""" + +REWRITE_HTTPS_ARGS = [ + "^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"] +"""Apache rewrite rule arguments used for redirections to https vhost""" diff --git a/letsencrypt/client/plugins/apache/tests/util.py b/letsencrypt/client/plugins/apache/tests/util.py index d1ba17f5a..feeb9490e 100644 --- a/letsencrypt/client/plugins/apache/tests/util.py +++ b/letsencrypt/client/plugins/apache/tests/util.py @@ -7,8 +7,8 @@ import unittest import mock -from letsencrypt.client import constants from letsencrypt.client.plugins.apache import configurator +from letsencrypt.client.plugins.apache import constants from letsencrypt.client.plugins.apache import obj @@ -49,7 +49,7 @@ def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"): def setup_apache_ssl_options(config_dir): """Move the ssl_options into position and return the path.""" option_path = os.path.join(config_dir, "options-ssl.conf") - shutil.copyfile(constants.APACHE_MOD_SSL_CONF, option_path) + shutil.copyfile(constants.MOD_SSL_CONF, option_path) return option_path From 4f0d0936af099321f581262c1b956fd10db76df5 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 08:44:51 +0000 Subject: [PATCH 10/76] IPluginFactory --- letsencrypt/client/interfaces.py | 20 +++++++++++++++++++ .../client/plugins/apache/configurator.py | 1 + .../plugins/standalone/authenticator.py | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 80a43f885..3ce0e990a 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -5,6 +5,26 @@ import zope.interface # pylint: disable=too-few-public-methods +class IPluginFactory(zope.interface.Interface): + + def __call__(config): + """Create new `IPlugin`. + + :param IConfig config: Configuration. + + """ + + def add_parser_arguments(add): + """Add plugin arguments to the CLI argument parser. + + :param callable add: Function that proxies calls to + `argparse.ArgumentParser.add_argument` prepending options + with unique plugin name prefix. + + """ + # TODO: move to IPlugin? + + class IPlugin(zope.interface.Interface): """Let's Encrypt plugin.""" diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 55f7e2875..34a57b2b0 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -79,6 +79,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller) + zope.interface.classProvides(interfaces.IPluginFactory) description = "Apache Web Server" diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index e0b06aa30..fa4e62d7f 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -30,9 +30,14 @@ class StandaloneAuthenticator(object): """ zope.interface.implements(interfaces.IAuthenticator) + zope.interface.classProvides(interfaces.IPluginFactory) description = "Standalone Authenticator" + @classmethod + def add_parser_arguments(cls, add): + pass + def __init__(self, unused_config): self.child_pid = None self.parent_pid = os.getpid() From cfe95323f6c8a51fedbaf47efc8e7f4b96ba3e89 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 09:02:38 +0000 Subject: [PATCH 11/76] Revert "Unit tests for setting authenticator via cmd line" This reverts commit 0d7f32fa984e2e82918d644dfe6913bfe765055f. --- letsencrypt/client/client.py | 7 ++-- letsencrypt/client/tests/client_test.py | 50 +++++++------------------ letsencrypt/scripts/main.py | 5 ++- 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 91b271784..19b982502 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -397,10 +397,11 @@ def determine_authenticator(all_auths, config): try: auth = avail_auths[config.authenticator] except KeyError: - logging.info(list_available_authenticators(avail_auths)) - raise errors.LetsEncryptClientError( - "The specified authenticator '%s' could not be found" % + logging.error( + "The specified authenticator '%s' could not be found", config.authenticator) + logging.info(list_available_authenticators(avail_auths)) + return elif len(avail_auths) > 1: auth = display_ops.choose_authenticator(avail_auths.values(), errs) elif len(avail_auths.keys()) == 1: diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index 63170b517..2310dbe87 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -1,9 +1,9 @@ """letsencrypt.client.client.py tests.""" +from collections import namedtuple import unittest import mock -from letsencrypt.client import configuration from letsencrypt.client import errors @@ -19,8 +19,7 @@ class DetermineAuthenticatorTest(unittest.TestCase): self.mock_apache = mock.MagicMock( spec=ApacheConfigurator, description="Standalone Authenticator") - self.mock_config = mock.MagicMock( - spec=configuration.NamespaceConfig, authenticator=None) + self.mock_config = mock.Mock() self.all_auths = { 'apache': self.mock_apache, @@ -28,30 +27,29 @@ class DetermineAuthenticatorTest(unittest.TestCase): } @classmethod - def _call(cls, all_auths, config): + def _call(cls, all_auths): from letsencrypt.client.client import determine_authenticator - return determine_authenticator(all_auths, config) + # TODO: add tests for setting the authenticator via the command line + mock_config = namedtuple("Config", ['authenticator']) + return determine_authenticator(all_auths, + mock_config(authenticator=None)) @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") def test_accept_two(self, mock_choose): mock_choose.return_value = self.mock_stand() - self.assertEqual(self._call(self.all_auths, self.mock_config), - self.mock_stand()) + self.assertEqual(self._call(self.all_auths), self.mock_stand()) def test_accept_one(self): self.mock_apache.prepare.return_value = self.mock_apache - one_avail_auth = { - 'apache': self.mock_apache - } - self.assertEqual(self._call(one_avail_auth, self.mock_config), - self.mock_apache) + self.assertEqual( + self._call(dict(apache=self.all_auths['apache'])), + self.mock_apache) def test_no_installation_one(self): self.mock_apache.prepare.side_effect = ( errors.LetsEncryptNoInstallationError) - self.assertEqual(self._call(self.all_auths, self.mock_config), - self.mock_stand) + self.assertEqual(self._call(self.all_auths), self.mock_stand) def test_no_installations(self): self.mock_apache.prepare.side_effect = ( @@ -61,8 +59,7 @@ class DetermineAuthenticatorTest(unittest.TestCase): self.assertRaises(errors.LetsEncryptClientError, self._call, - self.all_auths, - self.mock_config) + self.all_auths) @mock.patch("letsencrypt.client.client.logging") @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") @@ -71,26 +68,7 @@ class DetermineAuthenticatorTest(unittest.TestCase): errors.LetsEncryptMisconfigurationError) mock_choose.return_value = self.mock_apache - self.assertTrue(self._call(self.all_auths, self.mock_config) is None) - - def test_choose_valid_auth_from_cmd_line(self): - standalone_config = mock.MagicMock(spec=configuration.NamespaceConfig, - authenticator='standalone') - self.assertEqual(self._call(self.all_auths, standalone_config), - self.mock_stand) - - apache_config = mock.MagicMock(spec=configuration.NamespaceConfig, - authenticator='apache') - self.assertEqual(self._call(self.all_auths, apache_config), - self.mock_apache) - - def test_choose_invalid_auth_from_cmd_line(self): - invalid_config = mock.MagicMock(spec=configuration.NamespaceConfig, - authenticator='foobar') - self.assertRaises(errors.LetsEncryptClientError, - self._call, - self.all_auths, - invalid_config) + self.assertTrue(self._call(self.all_auths) is None) class RollbackTest(unittest.TestCase): diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 9da8c30b0..ae8eafc47 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -178,8 +178,9 @@ def main(): # pylint: disable=too-many-branches, too-many-statements try: auth = client.determine_authenticator(all_auths, config) logging.debug("Selected authenticator: %s", auth) - except errors.LetsEncryptClientError as err: - logging.critical(str(err)) + except errors.LetsEncryptClientError: + logging.critical("No authentication mechanisms were found on your " + "system.") sys.exit(1) if auth is None: From b76e8b6c412ab48de3d57b09013d58af152f6030 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 09:02:39 +0000 Subject: [PATCH 12/76] Revert "Update unit tests for determine_authenticator" This reverts commit 79f5ebe734d18ddbc70dfbd22de4ce76f995a20a. --- .gitignore | 2 +- letsencrypt/client/tests/client_test.py | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 51164db97..2e0578223 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ m3 *~ .vagrant *.swp -\#*# +\#*# \ No newline at end of file diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index 2310dbe87..1c1a0d68a 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -1,5 +1,4 @@ """letsencrypt.client.client.py tests.""" -from collections import namedtuple import unittest import mock @@ -21,18 +20,12 @@ class DetermineAuthenticatorTest(unittest.TestCase): self.mock_config = mock.Mock() - self.all_auths = { - 'apache': self.mock_apache, - 'standalone': self.mock_stand - } + self.all_auths = [self.mock_apache, self.mock_stand] @classmethod def _call(cls, all_auths): from letsencrypt.client.client import determine_authenticator - # TODO: add tests for setting the authenticator via the command line - mock_config = namedtuple("Config", ['authenticator']) - return determine_authenticator(all_auths, - mock_config(authenticator=None)) + return determine_authenticator(all_auths) @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") def test_accept_two(self, mock_choose): @@ -42,8 +35,7 @@ class DetermineAuthenticatorTest(unittest.TestCase): def test_accept_one(self): self.mock_apache.prepare.return_value = self.mock_apache self.assertEqual( - self._call(dict(apache=self.all_auths['apache'])), - self.mock_apache) + self._call(self.all_auths[:1]), self.mock_apache) def test_no_installation_one(self): self.mock_apache.prepare.side_effect = ( From f26549dc58016c004d3dbcfc56b2588ea4403957 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 22 Apr 2015 09:02:41 +0000 Subject: [PATCH 13/76] Revert "Add cmd line arg for the authenticator" This reverts commit 5d2abc30f0700366196cebb39a5a1a2275fb9d01. --- letsencrypt/client/client.py | 46 +++++++------------------------- letsencrypt/client/interfaces.py | 6 ----- letsencrypt/scripts/main.py | 15 +++-------- 3 files changed, 13 insertions(+), 54 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 19b982502..2fcb45d40 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -349,29 +349,13 @@ def init_csr(privkey, names, cert_dir): return le_util.CSR(csr_filename, csr_der, "der") -def list_available_authenticators(avail_auths): - """Return a pretty-printed list of authenticators. - - This is used to provide helpful feedback in the case where a user - specifies an invalid authenticator on the command line. - - """ - output_lines = ["Available authenticators:"] - for auth_name, auth in avail_auths.iteritems(): - output_lines.append(" - %s : %s" % (auth_name, auth.description)) - return '\n'.join(output_lines) - - # This should be controlled by commandline parameters -def determine_authenticator(all_auths, config): +def determine_authenticator(all_auths): """Returns a valid IAuthenticator. :param list all_auths: Where each is a :class:`letsencrypt.client.interfaces.IAuthenticator` object - :param config: Used if an authenticator was specified on the command line. - :type config: :class:`letsencrypt.client.interfaces.IConfig` - :returns: Valid Authenticator object or None :raises letsencrypt.client.errors.LetsEncryptClientError: If no @@ -379,33 +363,23 @@ def determine_authenticator(all_auths, config): """ # Available Authenticator objects - avail_auths = {} + avail_auths = [] # Error messages for misconfigured authenticators errs = {} - for auth_name, auth in all_auths.iteritems(): + for pot_auth in all_auths: try: - auth.prepare() + pot_auth.prepare() except errors.LetsEncryptMisconfigurationError as err: - errs[auth] = err + errs[pot_auth] = err except errors.LetsEncryptNoInstallationError: continue - avail_auths[auth_name] = auth + avail_auths.append(pot_auth) - # If an authenticator was specified on the command line, try to use it - if config.authenticator: - try: - auth = avail_auths[config.authenticator] - except KeyError: - logging.error( - "The specified authenticator '%s' could not be found", - config.authenticator) - logging.info(list_available_authenticators(avail_auths)) - return - elif len(avail_auths) > 1: - auth = display_ops.choose_authenticator(avail_auths.values(), errs) - elif len(avail_auths.keys()) == 1: - auth = avail_auths[avail_auths.keys()[0]] + if len(avail_auths) > 1: + auth = display_ops.choose_authenticator(avail_auths, errs) + elif len(avail_auths) == 1: + auth = avail_auths[0] else: raise errors.LetsEncryptClientError("No Authenticators available.") diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 3d3001377..9c0e5553d 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -13,10 +13,6 @@ class IAuthenticator(zope.interface.Interface): """ - description = zope.interface.Attribute( - "Short description of this authenticator. " - "Used in interactive configuration.") - def prepare(): """Prepare the authenticator. @@ -93,8 +89,6 @@ class IConfig(zope.interface.Interface): server = zope.interface.Attribute( "CA hostname (and optionally :port). The server certificate must " "be trusted in order to avoid further modifications to the client.") - authenticator = zope.interface.Attribute( - "Authenticator to use for responding to challenges.") rsa_key_size = zope.interface.Attribute("Size of the RSA key.") config_dir = zope.interface.Attribute("Configuration directory.") diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index ae8eafc47..1b50e2cda 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -32,8 +32,6 @@ SETUPTOOLS_AUTHENTICATORS_ENTRY_POINT = "letsencrypt.authenticators" def init_auths(config): """Find (setuptools entry points) and initialize Authenticators.""" - # TODO: handle collisions in authenticator names. Or is this - # already handled for us by pkg_resources? auths = {} for entrypoint in pkg_resources.iter_entry_points( SETUPTOOLS_AUTHENTICATORS_ENTRY_POINT): @@ -46,7 +44,7 @@ def init_auths(config): "%r object does not provide IAuthenticator, skipping", entrypoint.name) else: - auths[entrypoint.name] = auth + auths[auth] = entrypoint.name return auths @@ -62,12 +60,6 @@ def create_parser(): add("-s", "--server", default="letsencrypt-demo.org:443", help=config_help("server")) - # TODO: we should generate the list of choices from the set of - # available authenticators, but that is tricky due to the - # dependency between init_auths and config. Hardcoding it for now. - add("-a", "--authenticator", dest="authenticator", - help=config_help("authenticator")) - add("-k", "--authkey", type=read_file, help="Path to the authorized key file") add("-B", "--rsa-key-size", type=int, default=2048, metavar="N", @@ -174,10 +166,9 @@ def main(): # pylint: disable=too-many-branches, too-many-statements display_eula() all_auths = init_auths(config) - logging.debug('Initialized authenticators: %s', all_auths.keys()) + logging.debug('Initialized authenticators: %s', all_auths.values()) try: - auth = client.determine_authenticator(all_auths, config) - logging.debug("Selected authenticator: %s", auth) + auth = client.determine_authenticator(all_auths.keys()) except errors.LetsEncryptClientError: logging.critical("No authentication mechanisms were found on your " "system.") From 17e8ddcb5cca97d6f3568ebbcc3cd755363b59da Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 1 May 2015 20:19:26 +0000 Subject: [PATCH 14/76] Assert CLI test (--help) raises SystemExit --- letsencrypt/client/tests/cli_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py index 8afede3bc..bb50715e5 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/client/tests/cli_test.py @@ -5,6 +5,7 @@ class CLITest(unittest.TestCase): def test_it(self): from letsencrypt.client import cli + self.assertRaises(SystemExit, cli.main, ['--help']) if __name__ == '__main__': From 19cff0083556288fb64b4dbb38d2f3a7949d43e4 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 07:01:44 +0000 Subject: [PATCH 15/76] common.Plugin (with .conf(var), PluginEntryPoint, PluginRegistry --- .../plugins/letsencrypt_example_plugins.py | 21 ++-- examples/plugins/setup.py | 3 +- letsencrypt/client/augeas_configurator.py | 9 +- letsencrypt/client/cli.py | 65 +++++------ letsencrypt/client/interfaces.py | 51 +++++++-- .../client/plugins/apache/configurator.py | 29 +++-- .../client/plugins/apache/tests/util.py | 5 +- letsencrypt/client/plugins/common.py | 62 +++++++++++ letsencrypt/client/plugins/disco.py | 101 +++++++++++++----- .../client/plugins/nginx/configurator.py | 25 +++-- .../client/plugins/nginx/tests/util.py | 5 +- .../plugins/standalone/authenticator.py | 13 +-- .../standalone/tests/authenticator_test.py | 24 ++--- 13 files changed, 278 insertions(+), 135 deletions(-) create mode 100644 letsencrypt/client/plugins/common.py diff --git a/examples/plugins/letsencrypt_example_plugins.py b/examples/plugins/letsencrypt_example_plugins.py index 987a2b33b..11baf35a7 100644 --- a/examples/plugins/letsencrypt_example_plugins.py +++ b/examples/plugins/letsencrypt_example_plugins.py @@ -1,18 +1,27 @@ -"""Example Let's Encrypt plugins.""" +"""Example Let's Encrypt plugins. + +For full examples, see `letsencrypt.client.plugins`. + +""" import zope.interface from letsencrypt.client import interfaces +from letsencrypt.client.plugins import common -class Authenticator(object): +class Authenticator(common.Plugin): zope.interface.implements(interfaces.IAuthenticator) description = 'Example Authenticator plugin' - def __init__(self, config): - self.config = config - # Implement all methods from IAuthenticator, remembering to add # "self" as first argument, e.g. def prepare(self)... - # For full examples, see letsencrypt.client.plugins + +class Installer(common.Plugins): + zope.interface.implements(interfaces.IInstaller) + + description = 'Example Installer plugin' + + # Implement all methods from IInstaller, remembering to add + # "self" as first argument, e.g. def get_all_names(self)... diff --git a/examples/plugins/setup.py b/examples/plugins/setup.py index 599d57020..71bb95333 100644 --- a/examples/plugins/setup.py +++ b/examples/plugins/setup.py @@ -10,7 +10,8 @@ setup( ], entry_points={ 'letsencrypt.plugins': [ - 'example = letsencrypt_example_plugins:Authenticator', + 'example_authenticator = letsencrypt_example_plugins:Authenticator', + 'example_installer = letsencrypt_example_plugins:Installer', ], }, ) diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index 8854fef09..713d49291 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -4,9 +4,10 @@ import logging import augeas from letsencrypt.client import reverter +from letsencrypt.client.plugins import common -class AugeasConfigurator(object): +class AugeasConfigurator(common.Plugin): """Base Augeas Configurator class. :ivar config: Configuration. @@ -21,8 +22,8 @@ class AugeasConfigurator(object): """ - def __init__(self, config): - self.config = config + def __init__(self, *args, **kwargs): + super(AugeasConfigurator, self).__init__(*args, **kwargs) # Set Augeas flags to not save backup (we do it ourselves) # Set Augeas to not load anything by default @@ -34,7 +35,7 @@ class AugeasConfigurator(object): # This needs to occur before VirtualHost objects are setup... # because this will change the underlying configuration and potential # vhosts - self.reverter = reverter.Reverter(config) + self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() def check_parsing_errors(self, lens): diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 3a8faf481..148562e4c 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -171,48 +171,49 @@ def config_changes(args, config): client.config_changes(config) -def _print_plugins(filtered, plugins, names): - if not filtered: +def _print_plugins(plugins): + # TODO: this functions should use IDisplay rather than printing + + if not plugins: print "No plugins found" - for plugin_cls, content in filtered.iteritems(): - print "* {0}".format(names[plugin_cls]) - print "Description: {0}".format(plugin_cls.description) + for plugin_ep in plugins.itervalues(): + print "* {0}".format(plugin_ep.name) + print "Description: {0}".format(plugin_ep.plugin_cls.description) print "Interfaces: {0}".format(", ".join( iface.__name__ for iface in zope.interface.implementedBy( - plugin_cls))) - print "Entry points:" - for entry_point in plugins[plugin_cls]: - print "- {0.dist}: {0}".format(entry_point) + plugin_ep.plugin_cls))) + print "Entry point: {0}".format(plugin_ep.entry_point) + + if plugin_ep.initialized: + print "Initialized: {0}".format(plugin_ep.init()) # if filtered == prepared: - if isinstance(content, tuple) and content[1] is not None: - print content[1] # error - print + #if isinstance(content, tuple) and content[1] is not None: + # print content[1] # error + + print # whitespace between plugins def plugins(args, config): """List plugins.""" - plugins = plugins_disco.find_plugins() + plugins = plugins_disco.PluginRegistry.find_all() logging.debug("Discovered plugins: %s", plugins) - names = plugins_disco.name_plugins(plugins) - ifaces = [] if args.ifaces is None else args.ifaces - filtered = plugins_disco.filter_plugins( - plugins, *((iface,) for iface in ifaces)) + filtered = plugins.filter(*((iface,) for iface in ifaces)) logging.debug("Filtered plugins: %s", filtered) if not args.init and not args.prepare: - return _print_plugins(filtered, plugins, names) + return _print_plugins(filtered) - initialized = dict((plugin_cls, plugin_cls(config)) - for plugin_cls in filtered) - verified = plugins_disco.verify_plugins(initialized, ifaces) - logging.debug("Verified plugins: %s", initialized) + for plugin_ep in filtered.itervalues(): + plugin_ep.init(config) + #verified = plugins_disco.verify_plugins(initialized, ifaces) + #logging.debug("Verified plugins: %s", initialized) if not args.prepare: - return _print_plugins(initialized, plugins, names) + return _print_plugins(filtered) prepared = plugins_disco.prepare_plugins(initialized) logging.debug("Prepared plugins: %s", plugins) @@ -327,12 +328,10 @@ def create_parser(): paths_parser(parser.add_argument_group("paths")) # TODO: plugin_parser should be called for every detected plugin - plugin_parser( - parser.add_argument_group("apache"), prefix="apache", - plugin_cls=apache_configurator.ApacheConfigurator) - plugin_parser( - parser.add_argument_group("nginx"), prefix="nginx", - plugin_cls=nginx_configurator.NginxConfigurator) + for name, plugin_cls in [ + ("apache", apache_configurator.ApacheConfigurator), + ("nginx", nginx_configurator.NginxConfigurator)]: + plugin_cls.inject_parser_options(parser.add_argument_group(name), name) return parser @@ -360,14 +359,6 @@ def paths_parser(parser): return parser -def plugin_parser(parser, prefix, plugin_cls): - def add(arg_name_no_prefix, *args, **kwargs): - parser.add_argument( - "--{0}-{1}".format(prefix, arg_name_no_prefix), *args, **kwargs) - plugin_cls.add_parser_arguments(add) - return parser - - def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 85bfa7256..df8b616a6 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -6,30 +6,63 @@ import zope.interface class IPluginFactory(zope.interface.Interface): + """IPlugin factory. - def __call__(config): + Objects providing this interface will be called without satisfying + any entry point "extras" (extra dependencies) you might have defined + for your plugin, e.g (excerpt from ``setup.py`` script):: + + setup( + ... + entry_points={ + 'letsencrypt.plugins': [ + 'name=example_project.plugin[plugin_deps]', + ], + }, + extras_require={ + 'plugin_deps': ['dep1', 'dep2'], + } + ) + + Therefore, make sure such objects are importable and usable without + extras. This is necessary, because CLI does the following operations + (in order): + + - loads an entry point, + - calls `inject_parser_options`, + - requires an entry point, + - creates plugin instance (`__call__`). + + """ + + description = zope.interface.Attribute("Short plugin description") + + def __call__(config, name): """Create new `IPlugin`. :param IConfig config: Configuration. + :param str name: Unique plugin name. """ - def add_parser_arguments(add): - """Add plugin arguments to the CLI argument parser. + def inject_parser_options(parser, name): + """Inject argument parser options (flags). - :param callable add: Function that proxies calls to - `argparse.ArgumentParser.add_argument` prepending options - with unique plugin name prefix. + 1. Be nice and prepend all options and destinations with + `~.option_namespace` and `~.dest_namespace`. + + 2. Inject options (flags) only. Positional arguments are not + allowed, as this would break the CLI. + + :param ArgumentParser parser: (Almost) top-level CLI parser. + :param str name: Unique plugin name. """ - # TODO: move to IPlugin? class IPlugin(zope.interface.Interface): """Let's Encrypt plugin.""" - description = zope.interface.Attribute("Short plugin description") - def prepare(): """Prepare the plugin. diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 26e81e641..d014e6c2e 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -97,14 +97,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): add("init-script", default=constants.DEFAULT_INIT_SCRIPT, help="Path to the Apache init script (used for server reload/restart).") - def __init__(self, config, version=None): + def __init__(self, *args, **kwargs): """Initialize an Apache Configurator. :param tup version: version of Apache as a tuple (2, 4, 7) (used mostly for unittesting) """ - super(ApacheConfigurator, self).__init__(config) + version = kwargs.pop('version', None) + super(ApacheConfigurator, self).__init__(*args, **kwargs) # Verify that all directories and files exist with proper permissions if os.geteuid() == 0: @@ -124,8 +125,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def prepare(self): """Prepare the authenticator/installer.""" self.parser = parser.ApacheParser( - self.aug, self.config.apache_server_root, - self.config.apache_mod_ssl_conf) + self.aug, self.conf('server-root'), self.conf('mod-ssl-conf')) # Check for errors in parsing files with Augeas self.check_parsing_errors("httpd.aug") @@ -143,7 +143,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # on initialization self._prepare_server_https() - temp_install(self.config.apache_mod_ssl_conf) + temp_install(self.conf('mod-ssl-conf')) def deploy_cert(self, domain, cert, key, cert_chain=None): """Deploys certificate to specified virtual host. @@ -401,10 +401,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): is appropriately listening on port 443. """ - if not mod_loaded("ssl_module", self.config.apache_ctl): + if not mod_loaded("ssl_module", self.conf('ctl')): logging.info("Loading mod_ssl into Apache Server") - enable_mod("ssl", self.config.apache_init_script, - self.config.apache_enmod) + enable_mod("ssl", self.conf('init-script'), + self.conf('enmod')) # Check for Listen 443 # Note: This could be made to also look for ip:443 combo @@ -587,9 +587,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`) """ - if not mod_loaded("rewrite_module", self.config.apache_ctl): - enable_mod("rewrite", self.config.apache_init_script, - self.config.apache_enmod) + if not mod_loaded("rewrite_module", self.conf('ctl')): + enable_mod("rewrite", self.conf('init-script'), self.conf('enmod')) general_v = self._general_vhost(ssl_vhost) if general_v is None: @@ -912,7 +911,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :rtype: bool """ - return apache_restart(self.config.apache_init_script) + return apache_restart(self.conf('init-script')) def config_test(self): # pylint: disable=no-self-use """Check the configuration of Apache for errors. @@ -923,7 +922,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ try: proc = subprocess.Popen( - ["sudo", self.config.apache_ctl, "configtest"], # TODO: sudo? + ["sudo", self.conf('ctl'), "configtest"], # TODO: sudo? stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() @@ -970,13 +969,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ try: proc = subprocess.Popen( - [self.config.apache_ctl, "-v"], + [self.conf('ctl'), "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) text = proc.communicate()[0] except (OSError, ValueError): raise errors.LetsEncryptConfiguratorError( - "Unable to run %s -v" % self.config.apache_ctl) + "Unable to run %s -v" % self.conf('ctl')) regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE) matches = regex.findall(text) diff --git a/letsencrypt/client/plugins/apache/tests/util.py b/letsencrypt/client/plugins/apache/tests/util.py index feeb9490e..95c7928dd 100644 --- a/letsencrypt/client/plugins/apache/tests/util.py +++ b/letsencrypt/client/plugins/apache/tests/util.py @@ -64,7 +64,7 @@ def get_apache_configurator( # This just states that the ssl module is already loaded mock_popen().communicate.return_value = ("ssl_module", "") config = configurator.ApacheConfigurator( - mock.MagicMock( + config=mock.MagicMock( apache_server_root=config_path, apache_mod_ssl_conf=ssl_options, le_vhost_ext="-le-ssl.conf", @@ -73,7 +73,8 @@ def get_apache_configurator( temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), in_progress_dir=os.path.join(backups, "IN_PROGRESS"), work_dir=work_dir), - version) + name="apache", + version=version) config.prepare() diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/client/plugins/common.py new file mode 100644 index 000000000..ba6efeccc --- /dev/null +++ b/letsencrypt/client/plugins/common.py @@ -0,0 +1,62 @@ +"""Plugin common functions.""" +import zope.interface + +from letsencrypt.acme.jose import util as jose_util + +from letsencrypt.client import interfaces + + +def option_namespace(name): + """ArgumentParser options namespace (prefix of all options).""" + return name + '-' + +def dest_namespace(name): + """ArgumentParser dest namespace (prefix of all destinations).""" + return name + '_' + + +class Plugin(object): + """Generic plugin.""" + zope.interface.implements(interfaces.IPlugin) + zope.interface.classProvides(interfaces.IPluginFactory) + + def __init__(self, config, name): + self.config = config + self.name = name + + @property + def option_namespace(self): + return option_namespace(self.name) + + @property + def dest_namespace(self): + return dest_namespace(self.name) + + def dest(self, var): + """Find a destination for given variable ``var``.""" + # this should do exactly the same what ArgumentParser(arg), + # does to "arg" to compute "dest" + return self.dest_namespace + var.replace('-', '_') + + def conf(self, var): + """Find a configuration value for variable ``var``.""" + return getattr(self.config, self.dest(var)) + + @classmethod + def inject_parser_options(cls, parser, name): + # dummy function, doesn't check if dest.startswith(self.dest_namespace) + def add(arg_name_no_prefix, *args, **kwargs): + return parser.add_argument( + "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), + *args, **kwargs) + cls.add_parser_arguments(add) + + @jose_util.abstractclassmethod + def add_parser_arguments(cls, add): + """Add plugin arguments to the CLI argument parser. + + :param callable add: Function that proxies calls to + `argparse.ArgumentParser.add_argument` prepending options + with unique plugin name prefix. + + """ diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index d2526d583..350929641 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -12,52 +12,97 @@ from letsencrypt.client import interfaces from letsencrypt.client.display import ops as display_ops -def name_plugins(plugins): - # TODO: actually make it unambiguous... - names = {} - for plugin_cls, entry_points in plugins.iteritems(): - entry_point = next(iter(entry_points)) # entry_points.peek() - names[plugin_cls] = entry_point.name - return names +class PluginEntryPoint(object): + """Plugin entry point.""" + + PREFIX_FREE_DISTRIBUTIONS = ['letsencrypt'] + """Distributions for which prefix will be omitted.""" + + def __init__(self, entry_point): + self.name = self.entry_point_to_plugin_name(entry_point) + self.plugin_cls = entry_point.load() + self.entry_point = entry_point + self._initialized = None + + @property + def initialized(self): + return self._initialized is not None + + @classmethod + def entry_point_to_plugin_name(cls, entry_point): + if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: + return entry_point.name + return entry_point.dist.key + ':' + entry_point.name + + def init(self, config=None): + """Memoized plugin inititialization.""" + if not self.initialized: + self.entry_point.require() # fetch extras! + self._initialized = self.plugin_cls(config, self.name) + return self._initialized + + def __repr__(self): + return 'PluginEntryPoint#{0}'.format(self.name) -def find_plugins(): - """Find plugins using setuptools entry points.""" - plugins = collections.defaultdict(set) - for entry_point in pkg_resources.iter_entry_points( - constants.SETUPTOOLS_PLUGINS_ENTRY_POINT): - plugin_cls = entry_point.load() - plugins[plugin_cls].add(entry_point) - return plugins +class PluginRegistry(collections.Mapping): + """Plugin registry.""" + def __init__(self, plugins): + self.plugins = plugins -def filter_plugins(plugins, *ifaces_groups): - """Filter plugins based on interfaces.""" - return dict( - (plugin_cls, entry_points) - for plugin_cls, entry_points in plugins.iteritems() - if not ifaces_groups or any( - all(iface.implementedBy(plugin_cls) for iface in ifaces) - for ifaces in ifaces_groups)) + @classmethod + def find_all(cls): + """Find plugins using setuptools entry points.""" + plugins = {} + for entry_point in pkg_resources.iter_entry_points( + constants.SETUPTOOLS_PLUGINS_ENTRY_POINT): + plugin_ep = PluginEntryPoint(entry_point) + assert plugin_ep.name not in plugins, ( + 'PREFIX_FREE_DISTRIBTIONS messed up') + plugins[plugin_ep.name] = plugin_ep + return cls(plugins) + + def filter(self, *ifaces_groups): + """Filter plugins based on interfaces.""" + return type(self)(dict( + plugin_ep + for plugin_ep in self.plugins.iteritems() + if not ifaces_groups or any( + all(iface.implementedBy(plugin_ep.plugin_cls) + for iface in ifaces) + for ifaces in ifaces_groups))) + + def __repr__(self): + return '{0}({1!r})'.format(self.__class__.__name__, self.plugins) + + def __getitem__(self, name): + return self.plugins[name] + + def __iter__(self): + return iter(self.plugins) + + def __len__(self): + return len(self.plugins) def verify_plugins(initialized, ifaces): """Verify plugin objects.""" verified = {} - for plugin_cls, plugin in initialized.iteritems(): + for name, plugin_ep in initialized.iteritems(): verifies = True for iface in ifaces: # zope.interface.providedBy(plugin) try: - zope.interface.verify.verifyObject(iface, plugin) + zope.interface.verify.verifyObject(iface, plugin_ep.init()) except zope.interface.exceptions.BrokenImplementation: - if iface.implementedBy(plugin_cls): + if iface.implementedBy(plugin_ep.plugin_cls): logging.debug( "%s implements %s but object does " - "not verify", plugin_cls, iface.__name__) + "not verify", plugin_ep.plugin_cls, iface.__name__) verifies = False break if verifies: - verified[plugin_cls] = plugin + verified[name] = plugin_ep return verified diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 4a0ae9657..edeb4adb0 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -17,12 +17,14 @@ from letsencrypt.client import interfaces from letsencrypt.client import le_util from letsencrypt.client import reverter +from letsencrypt.client.plugins import common + from letsencrypt.client.plugins.nginx import constants from letsencrypt.client.plugins.nginx import dvsni from letsencrypt.client.plugins.nginx import parser -class NginxConfigurator(object): +class NginxConfigurator(common.Plugin): # pylint: disable=too-many-instance-attributes,too-many-public-methods """Nginx configurator. @@ -60,14 +62,15 @@ class NginxConfigurator(object): "'nginx' binary, used for 'configtest' and retrieving nginx " "version number.") - def __init__(self, config, version=None): + def __init__(self, *args, **kwargs): """Initialize an Nginx Configurator. :param tup version: version of Nginx as a tuple (1, 4, 7) (used mostly for unittesting) """ - self.config = config + version = kwargs.pop("version", None) + super(NginxConfigurator, self).__init__(*args, **kwargs) # Verify that all directories and files exist with proper permissions if os.geteuid() == 0: @@ -85,21 +88,21 @@ class NginxConfigurator(object): self._enhance_func = {} # TODO: Support at least redirects # Set up reverter - self.reverter = reverter.Reverter(config) + self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() # This is called in determine_authenticator and determine_installer def prepare(self): """Prepare the authenticator/installer.""" self.parser = parser.NginxParser( - self.config.nginx_server_root, - self.config.nginx_mod_ssl_conf) + self.conf('server-root'), + self.conf('mod-ssl-conf')) # Set Version if self.version is None: self.version = self.get_version() - temp_install(self.config.nginx_mod_ssl_conf) + temp_install(self.conf('mod-ssl-conf')) # Entry point in main.py for installing cert def deploy_cert(self, domain, cert, key, cert_chain=None): @@ -323,7 +326,7 @@ class NginxConfigurator(object): :rtype: bool """ - return nginx_restart(self.config.nginx_ctl) + return nginx_restart(self.conf('ctl')) def config_test(self): # pylint: disable=no-self-use """Check the configuration of Nginx for errors. @@ -334,7 +337,7 @@ class NginxConfigurator(object): """ try: proc = subprocess.Popen( - [self.config.nginx_ctl, "-t"], + [self.conf('ctl'), "-t"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() @@ -381,13 +384,13 @@ class NginxConfigurator(object): """ try: proc = subprocess.Popen( - [self.config.nginx_ctl, "-V"], + [self.conf('ctl'), "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) text = proc.communicate()[1] # nginx prints output to stderr except (OSError, ValueError): raise errors.LetsEncryptConfiguratorError( - "Unable to run %s -V" % self.config.nginx_ctl) + "Unable to run %s -V" % self.conf('ctl')) version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE) version_matches = version_regex.findall(text) diff --git a/letsencrypt/client/plugins/nginx/tests/util.py b/letsencrypt/client/plugins/nginx/tests/util.py index 8acfa8ff6..dc112af16 100644 --- a/letsencrypt/client/plugins/nginx/tests/util.py +++ b/letsencrypt/client/plugins/nginx/tests/util.py @@ -65,12 +65,13 @@ def get_nginx_configurator( backups = os.path.join(work_dir, "backups") config = configurator.NginxConfigurator( - mock.MagicMock( + config=mock.MagicMock( nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options, le_vhost_ext="-le-ssl.conf", backup_dir=backups, config_dir=config_dir, work_dir=work_dir, temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), in_progress_dir=os.path.join(backups, "IN_PROGRESS")), - version) + name="nginx", + version=version) config.prepare() return config diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index aaefd09da..668b3f716 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -17,8 +17,10 @@ from letsencrypt.acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import interfaces +from letsencrypt.client.plugins import common -class StandaloneAuthenticator(object): + +class StandaloneAuthenticator(common.Plugin): # pylint: disable=too-many-instance-attributes """Standalone authenticator. @@ -29,15 +31,10 @@ class StandaloneAuthenticator(object): """ zope.interface.implements(interfaces.IAuthenticator) - zope.interface.classProvides(interfaces.IPluginFactory) - description = "Standalone Authenticator" - @classmethod - def add_parser_arguments(cls, add): - pass - - def __init__(self, unused_config): + def __init__(self, *args, **kwargs): + super(StandaloneAuthenticator, self).__init__(*args, **kwargs) self.child_pid = None self.parent_pid = os.getpid() self.subproc_state = None diff --git a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py index 23cd43bd5..e13452ccc 100644 --- a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py @@ -53,7 +53,7 @@ class ChallPrefTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) def test_chall_pref(self): self.assertEqual(self.authenticator.get_chall_pref("example.com"), @@ -65,7 +65,7 @@ class SNICallbackTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) test_key = pkg_resources.resource_string( "letsencrypt.client.tests", "testdata/rsa256_key.pem") key = le_util.Key("foo", test_key) @@ -108,7 +108,7 @@ class ClientSignalHandlerTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} self.authenticator.child_pid = 12345 @@ -137,7 +137,7 @@ class SubprocSignalHandlerTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} self.authenticator.child_pid = 12345 self.authenticator.parent_pid = 23456 @@ -189,7 +189,7 @@ class AlreadyListeningTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) @mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil." "net_connections") @@ -296,7 +296,7 @@ class PerformTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) test_key = pkg_resources.resource_string( "letsencrypt.client.tests", "testdata/rsa256_key.pem") @@ -375,7 +375,7 @@ class StartListenerTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) @mock.patch("letsencrypt.client.plugins.standalone.authenticator." "Crypto.Random.atfork") @@ -410,7 +410,7 @@ class DoParentProcessTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) @mock.patch("letsencrypt.client.plugins.standalone.authenticator." "signal.signal") @@ -464,7 +464,7 @@ class DoChildProcessTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) test_key = pkg_resources.resource_string( "letsencrypt.client.tests", "testdata/rsa256_key.pem") key = le_util.Key("foo", test_key) @@ -562,7 +562,7 @@ class CleanupTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) self.achall = achallenges.DVSNI( challb=acme_util.chall_to_challb( challenges.DVSNI(r="whee", nonce="foononce"), "pending"), @@ -595,7 +595,7 @@ class MoreInfoTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import ( StandaloneAuthenticator) - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) def test_more_info(self): """Make sure exceptions aren't raised.""" @@ -607,7 +607,7 @@ class InitTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.standalone.authenticator import ( StandaloneAuthenticator) - self.authenticator = StandaloneAuthenticator(None) + self.authenticator = StandaloneAuthenticator(config=None, name=None) def test_prepare(self): """Make sure exceptions aren't raised. From c9a7172388f39fbca478d2aaee771ab5d957e300 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 08:53:06 +0000 Subject: [PATCH 16/76] Somewhat usable CLI+disco --- letsencrypt/client/cli.py | 91 +++++++++---------- letsencrypt/client/display/ops.py | 14 +-- .../client/plugins/apache/configurator.py | 7 +- letsencrypt/client/plugins/disco.py | 56 ++++++------ .../client/plugins/nginx/configurator.py | 7 +- 5 files changed, 89 insertions(+), 86 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 148562e4c..167ed4a1d 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -59,7 +59,8 @@ def _common_run(args, config, acc, authenticator, installer): doms = args.domains if not doms: - return None + sys.exit("Please specify --domains, or --installer that will " + "help in domain names autodiscovery") acme = client.Client(config, acc, authenticator, installer) @@ -73,10 +74,10 @@ def _common_run(args, config, acc, authenticator, installer): except errors.LetsEncryptClientError: return None - return acme, doms, authkey + return acme, doms -def run(args, config): +def run(args, config, plugins): """Obtain a certificate and install.""" acc = _account_init(args, config) if acc is None: @@ -89,63 +90,61 @@ def run(args, config): if args.authenticator is not None or args.installer is not None: installer = plugins_disco.pick_installer( - config, args.installer) + config, args.installer, plugins) authenticator = plugins_disco.pick_authenticator( - config, args.authenticator) + config, args.authenticator, plugins) else: + # TODO: this assume that user doesn't want to pick authenticator + # and installer separately... authenticator = installer = plugins_disco.pick_configurator( - config, args.configurator) + config, args.configurator, plugins) if installer is None or authenticator is None: return "Configurator could not be determined" - acme, auth, installer, doms, auth_key = _common_run(args, config, acc) - cert_file, chain_file = acme.obtain_certificate(doms) - acme.deploy_certificate(doms, authkey, cert_file, chain_file) + acme, doms = _common_run(args, config, acc, authenticator, installer) + cert_path, chain_path = acme.obtain_certificate(doms) + acme.deploy_certificate(doms, acc.key, cert_path, chain_path) acme.enhance_config(doms, args.redirect) -def auth(args, config): +def auth(args, config, plugins): """Obtain a certificate (no install).""" acc = _account_init(args, config) if acc is None: return None - authenticator = plugins_disco.pick_authenticator(config, args.authenticator) + authenticator = plugins_disco.pick_authenticator(config, args.authenticator, plugins) if authenticator is None: return "Authenticator could not be determined" if args.installer is not None: - installer = plugins_disco.pick_installer(config, args.installer) + installer = plugins_disco.pick_installer(config, args.installer, plugins) else: installer = None - if args.domains is None: - if args.installer is not None: - return ("--domains not set and provided --installer does not " - "help in autodiscovery") - else: - return ("Please specify --domains, or --installer that will " - "help in domain names autodiscovery") - - acme, doms, _ = _common_run( - args, config, authenticator=authenticator, installer=None) + acme, doms = _common_run( + args, config, acc, authenticator=authenticator, installer=None) acme.obtain_certificate(doms) -def install(args, config): +def install(args, config, plugins): """Install (no auth).""" - installer = plugins_disco.pick_installer(config, args.installer) + acc = _account_init(args, config) + if acc is None: + return None + + installer = plugins_disco.pick_installer(config, args.installer, plugins) if installer is None: return "Installer could not be determined" - acme, doms, authkey = _common_run( - args, config, authenticator=None, installer=installer) - assert args.cert_file is not None and args.chain_file is not None - acme.deploy_certificate(doms, authkey, args.cert_file, args.chain_file) + acme, doms = _common_run( + args, config, acc, authenticator=None, installer=installer) + assert args.cert_path is not None and args.chain_path is not None + acme.deploy_certificate(doms, acc.key, args.cert_path, args.chain_path) acme.enhance_config(doms, args.redirect) -def revoke(args, config): +def revoke(args, config, plugins): """Revoke.""" if args.rev_cert is None and args.rev_key is None: return "At least one of --certificate or --key is required" @@ -156,18 +155,17 @@ def revoke(args, config): #client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) -def rollback(args, config): +def rollback(args, config, plugins): """Rollback.""" client.rollback(args.checkpoints, config) -def config_changes(args, config): +def config_changes(args, config, plugins): """View config changes. View checkpoints and associated configuration changes. """ - print args, config client.config_changes(config) @@ -195,9 +193,8 @@ def _print_plugins(plugins): print # whitespace between plugins -def plugins(args, config): +def plugins_cmd(args, config, plugins): """List plugins.""" - plugins = plugins_disco.PluginRegistry.find_all() logging.debug("Discovered plugins: %s", plugins) ifaces = [] if args.ifaces is None else args.ifaces @@ -243,7 +240,7 @@ def config_help(name): return interfaces.IConfig[name].__doc__ -def create_parser(): +def create_parser(plugins): """Create parser.""" parser = configargparse.ArgParser( description=__doc__, @@ -278,7 +275,7 @@ def create_parser(): parser_rollback = add_subparser("rollback", rollback) parrser_config_changes = add_subparser("config_changes", config_changes) - parser_plugins = add_subparser("plugins", plugins) + parser_plugins = add_subparser("plugins", plugins_cmd) parser_plugins.add_argument("--init", action="store_true") parser_plugins.add_argument("--prepare", action="store_true") parser_plugins.add_argument( @@ -288,12 +285,10 @@ def create_parser(): "--installers", action="append_const", dest="ifaces", const=interfaces.IInstaller) - parser_run.add_argument("--configurator") - for subparser in parser_run, parser_auth: - subparser.add_argument("-a", "--authenticator") - for subparser in parser_run, parser_auth, parser_install: - # parser_auth uses --installer for domains autodiscovery - subparser.add_argument("-i", "--installer") + parser.add_argument("--configurator") + parser.add_argument("-a", "--authenticator") + parser.add_argument("-i", "--installer") + # positional arg shadows --domains, instead of appending, and # --domains is useful, because it can be stored in config #for subparser in parser_run, parser_auth, parser_install: @@ -328,10 +323,9 @@ def create_parser(): paths_parser(parser.add_argument_group("paths")) # TODO: plugin_parser should be called for every detected plugin - for name, plugin_cls in [ - ("apache", apache_configurator.ApacheConfigurator), - ("nginx", nginx_configurator.NginxConfigurator)]: - plugin_cls.inject_parser_options(parser.add_argument_group(name), name) + for name, plugin_ep in plugins.iteritems(): + plugin_ep.plugin_cls.inject_parser_options( + parser.add_argument_group(name), name) return parser @@ -362,7 +356,8 @@ def paths_parser(parser): def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) - args = create_parser().parse_args(args) + plugins = plugins_disco.PluginRegistry.find_all() + args = create_parser(plugins).parse_args(args) config = configuration.NamespaceConfig(args) # Displayer @@ -391,7 +386,7 @@ def main(args=sys.argv[1:]): # "{0}Root is required to run letsencrypt. Please use sudo.{0}" # .format(os.linesep)) - return args.func(args, config) + return args.func(args, config, plugins) if __name__ == "__main__": diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 4243d6b4a..6582035bd 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -1,4 +1,5 @@ """Contains UI methods for LE user operations.""" +import logging import os import zope.component @@ -11,13 +12,13 @@ util = zope.component.getUtility # pylint: disable=invalid-name def choose_plugin(prepared, question): - descs = [plugin.description if error is None - else "%s (Misconfigured)" % plugin.description - for (plugin, error) in prepared] + opts = [plugin_ep.name_with_description if error is None + else "%s (Misconfigured)" % plugin_ep.name_with_description + for (plugin_ep, error) in prepared] while True: code, index = util(interfaces.IDisplay).menu( - question, descs, help_label="More Info") + question, opts, help_label="More Info") if code == display_util.OK: return prepared[index][0] @@ -25,11 +26,11 @@ def choose_plugin(prepared, question): if prepared[index][1] is not None: msg = "Reported Error: %s" % prepared[index][1] else: - msg = prepared[index][0].more_info() + msg = prepared[index][0].init().more_info() util(interfaces.IDisplay).notification( msg, height=display_util.HEIGHT) else: - return + return None def choose_authenticator(auths, errs): @@ -98,6 +99,7 @@ def choose_names(installer): """ if installer is None: + logging.debug("No installer, picking names manually") return _choose_names_manually() names = list(installer.get_all_names()) diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index d014e6c2e..0a5d91132 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -13,6 +13,7 @@ from letsencrypt.acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import augeas_configurator +from letsencrypt.client import constants as core_constants from letsencrypt.client import errors from letsencrypt.client import interfaces from letsencrypt.client import le_util @@ -949,11 +950,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ uid = os.geteuid() le_util.make_or_verify_dir( - self.config.config_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir( - self.config.work_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir( - self.config.backup_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) def get_version(self): """Return version of Apache Server. diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index 350929641..f684395a8 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -44,6 +44,10 @@ class PluginEntryPoint(object): def __repr__(self): return 'PluginEntryPoint#{0}'.format(self.name) + @property + def name_with_description(self): + return "{0} ({1})".format(self.name, self.plugin_cls.description) + class PluginRegistry(collections.Mapping): """Plugin registry.""" @@ -66,8 +70,8 @@ class PluginRegistry(collections.Mapping): def filter(self, *ifaces_groups): """Filter plugins based on interfaces.""" return type(self)(dict( - plugin_ep - for plugin_ep in self.plugins.iteritems() + (name, plugin_ep) + for name, plugin_ep in self.plugins.iteritems() if not ifaces_groups or any( all(iface.implementedBy(plugin_ep.plugin_cls) for iface in ifaces) @@ -110,59 +114,59 @@ def prepare_plugins(initialized): """Prepare plugins.""" prepared = {} - for plugin_cls, plugin in initialized.iteritems(): + for name, plugin_ep in initialized.iteritems(): error = None try: - plugin.prepare() + plugin_ep.init().prepare() except errors.LetsEncryptMisconfigurationError as error: - logging.debug("Misconfigured %s: %s", plugin, error) + logging.debug("Misconfigured %s: %s", plugin_ep, error) except errors.LetsEncryptNoInstallationError as error: - logging.debug("No installation (%s): %s", plugin, error) + logging.debug("No installation (%s): %s", plugin_ep, error) continue - prepared[plugin_cls] = (plugin, error) + prepared[name] = (plugin_ep, error) return prepared # succefully prepared + misconfigured -def pick_plugin(config, default, ifaces, question): - plugins = find_plugins() - names = name_plugins(plugins) - +def pick_plugin(config, default, plugins, ifaces, question): if default is not None: - filtered = [names[default]] + filtered = {default: plugins[default]} else: - filtered = filter_plugins(plugins, ifaces) + filtered = plugins.filter(ifaces) - initialized = dict((plugin_cls, plugin_cls(config)) - for plugin_cls in filtered) - verified = verify_plugins(initialized, ifaces) - prepared = prepare_plugins(initialized) + for plugin_ep in plugins.itervalues(): + plugin_ep.init(config) + verified = verify_plugins(filtered, ifaces) + prepared = prepare_plugins(filtered) if len(prepared) > 1: logging.debug("Multiple candidate plugins: %s", prepared) - return display_ops.choose_plugin(prepared.values(), question) + return display_ops.choose_plugin(prepared.values(), question).init() elif len(prepared) == 1: - logging.debug("Single candidate plugin: %s", prepared) - return prepared.values()[0] + plugin_ep = prepared.values()[0][0] + logging.debug("Single candidate plugin: %s", plugin_ep) + return plugin_ep.init() else: logging.debug("No candidate plugin") return None -def pick_authenticator(config, default): +def pick_authenticator(config, default, plugins): """Pick authentication plugin.""" return pick_plugin( - config, default, (interfaces.IAuthenticator,), + config, default, plugins, (interfaces.IAuthenticator,), "How would you like to authenticate with Let's Encrypt CA?") -def pick_installer(config, default): +def pick_installer(config, default, plugins): """Pick installer plugin.""" - return pick_plugin(config, default, (interfaces.IInstaller,), + return pick_plugin(config, default, plugins, (interfaces.IInstaller,), "How would you like to install certificates?") -def pick_configurator(config, default): + +def pick_configurator(config, default, plugins): """Pick configurator plugin.""" return pick_plugin( - config, default, (interfaces.IAuthenticator, interfaces.IInstaller), + config, default, plugins, + (interfaces.IAuthenticator, interfaces.IInstaller), "How would you like to install certificates?") diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index edeb4adb0..660653efa 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -12,6 +12,7 @@ import zope.interface from letsencrypt.acme import challenges from letsencrypt.client import achallenges +from letsencrypt.client import constants as core_constants from letsencrypt.client import errors from letsencrypt.client import interfaces from letsencrypt.client import le_util @@ -364,11 +365,11 @@ class NginxConfigurator(common.Plugin): """ uid = os.geteuid() le_util.make_or_verify_dir( - self.config.work_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir( - self.config.backup_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir( - self.config.config_dir, constants.CONFIG_DIRS_MODE, uid) + self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid) def get_version(self): """Return version of Nginx Server. From 5672916cb24a44024ea4ceba7a3998c0a71f3c81 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 11:19:46 +0000 Subject: [PATCH 17/76] Tests and lint for plugins.common --- letsencrypt/client/plugins/common.py | 16 ++++-- letsencrypt/client/plugins/common_test.py | 61 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 letsencrypt/client/plugins/common_test.py diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/client/plugins/common.py index ba6efeccc..e9ee4d573 100644 --- a/letsencrypt/client/plugins/common.py +++ b/letsencrypt/client/plugins/common.py @@ -8,11 +8,11 @@ from letsencrypt.client import interfaces def option_namespace(name): """ArgumentParser options namespace (prefix of all options).""" - return name + '-' + return name + "-" def dest_namespace(name): """ArgumentParser dest namespace (prefix of all destinations).""" - return name + '_' + return name + "_" class Plugin(object): @@ -26,17 +26,19 @@ class Plugin(object): @property def option_namespace(self): + """ArgumentParser options namespace (prefix of all options).""" return option_namespace(self.name) @property def dest_namespace(self): + """ArgumentParser dest namespace (prefix of all destinations).""" return dest_namespace(self.name) def dest(self, var): """Find a destination for given variable ``var``.""" # this should do exactly the same what ArgumentParser(arg), # does to "arg" to compute "dest" - return self.dest_namespace + var.replace('-', '_') + return self.dest_namespace + var.replace("-", "_") def conf(self, var): """Find a configuration value for variable ``var``.""" @@ -44,12 +46,18 @@ class Plugin(object): @classmethod def inject_parser_options(cls, parser, name): + """Inject parser options. + + See `~.IPlugin.inject_parser_options` for docs. + + """ # dummy function, doesn't check if dest.startswith(self.dest_namespace) def add(arg_name_no_prefix, *args, **kwargs): + # pylint: disable=missing-docstring return parser.add_argument( "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), *args, **kwargs) - cls.add_parser_arguments(add) + return cls.add_parser_arguments(add) @jose_util.abstractclassmethod def add_parser_arguments(cls, add): diff --git a/letsencrypt/client/plugins/common_test.py b/letsencrypt/client/plugins/common_test.py new file mode 100644 index 000000000..bdb5f7f3c --- /dev/null +++ b/letsencrypt/client/plugins/common_test.py @@ -0,0 +1,61 @@ +"""Tests for letsencrypt.client.plugins.common.""" +import unittest + +import mock + + +class NamespaceFunctionsTest(unittest.TestCase): + """Tests for letsencrypt.client.plugins.common.*_namespace functions.""" + + def test_option_namespace(self): + from letsencrypt.client.plugins.common import option_namespace + self.assertEqual("foo-", option_namespace("foo")) + + def test_dest_namespace(self): + from letsencrypt.client.plugins.common import dest_namespace + self.assertEqual("foo_", dest_namespace("foo")) + + +class PluginTest(unittest.TestCase): + """Test for letsencrypt.client.plugins.common.Plugin.""" + + def setUp(self): + from letsencrypt.client.plugins.common import Plugin + + class MockPlugin(Plugin): # pylint: disable=missing-docstring + @classmethod + def add_parser_arguments(cls, add): + add("foo-bar", dest="different_to_foo_bar", x=1, y=None) + + self.plugin_cls = MockPlugin + self.config = mock.MagicMock() + self.plugin = MockPlugin(config=self.config, name="mock") + + def test_init(self): + self.assertEqual("mock", self.plugin.name) + self.assertEqual(self.config, self.plugin.config) + + def test_option_namespace(self): + self.assertEqual("mock-", self.plugin.option_namespace) + + def test_dest_namespace(self): + self.assertEqual("mock_", self.plugin.dest_namespace) + + def test_dest(self): + self.assertEqual("mock_foo_bar", self.plugin.dest("foo-bar")) + self.assertEqual("mock_foo_bar", self.plugin.dest("foo_bar")) + + def test_conf(self): + self.assertEqual(self.config.mock_foo_bar, self.plugin.conf("foo-bar")) + + def test_inject_parser_options(self): + parser = mock.MagicMock() + self.plugin_cls.inject_parser_options(parser, "mock") + # note that inject_parser_options doesn't check if dest has + # correct prefix + parser.add_argument.assert_called_once_with( + "--mock-foo-bar", dest="different_to_foo_bar", x=1, y=None) + + +if __name__ == "__main__": + unittest.main() From fc059b62698602c505ad01b6d710eff0d79d66af Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 12:18:46 +0000 Subject: [PATCH 18/76] Add docs for plugins.common and disco --- docs/api/client/plugins/common.rst | 5 +++++ docs/api/client/plugins/disco.rst | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 docs/api/client/plugins/common.rst create mode 100644 docs/api/client/plugins/disco.rst diff --git a/docs/api/client/plugins/common.rst b/docs/api/client/plugins/common.rst new file mode 100644 index 000000000..9ee3e6b3e --- /dev/null +++ b/docs/api/client/plugins/common.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.client.plugins.common` +---------------------------------------- + +.. automodule:: letsencrypt.client.plugins.common + :members: diff --git a/docs/api/client/plugins/disco.rst b/docs/api/client/plugins/disco.rst new file mode 100644 index 000000000..2b877b654 --- /dev/null +++ b/docs/api/client/plugins/disco.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.client.plugins.disco` +--------------------------------------- + +.. automodule:: letsencrypt.client.plugins.disco + :members: From 9c6bb5e63cae09590e8391471e21b1cb06f96431 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 12:16:05 +0000 Subject: [PATCH 19/76] Tests and lint for plugins disco --- letsencrypt/client/cli.py | 2 +- letsencrypt/client/plugins/disco.py | 33 ++++---- letsencrypt/client/plugins/disco_test.py | 97 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 letsencrypt/client/plugins/disco_test.py diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 167ed4a1d..c0d83fb61 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -356,7 +356,7 @@ def paths_parser(parser): def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) - plugins = plugins_disco.PluginRegistry.find_all() + plugins = plugins_disco.PluginsRegistry.find_all() args = create_parser(plugins).parse_args(args) config = configuration.NamespaceConfig(args) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index f684395a8..37d60cbc8 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -15,7 +15,7 @@ from letsencrypt.client.display import ops as display_ops class PluginEntryPoint(object): """Plugin entry point.""" - PREFIX_FREE_DISTRIBUTIONS = ['letsencrypt'] + PREFIX_FREE_DISTRIBUTIONS = ["letsencrypt"] """Distributions for which prefix will be omitted.""" def __init__(self, entry_point): @@ -26,13 +26,15 @@ class PluginEntryPoint(object): @property def initialized(self): + """Has the plugin been initialized already?""" return self._initialized is not None @classmethod def entry_point_to_plugin_name(cls, entry_point): + """Unique plugin name for an ``entry_point``""" if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: return entry_point.name - return entry_point.dist.key + ':' + entry_point.name + return entry_point.dist.key + ":" + entry_point.name def init(self, config=None): """Memoized plugin inititialization.""" @@ -42,15 +44,16 @@ class PluginEntryPoint(object): return self._initialized def __repr__(self): - return 'PluginEntryPoint#{0}'.format(self.name) + return "PluginEntryPoint#{0}".format(self.name) @property def name_with_description(self): + """Name with description. Handy for UI.""" return "{0} ({1})".format(self.name, self.plugin_cls.description) -class PluginRegistry(collections.Mapping): - """Plugin registry.""" +class PluginsRegistry(collections.Mapping): + """Plugins registry.""" def __init__(self, plugins): self.plugins = plugins @@ -63,7 +66,7 @@ class PluginRegistry(collections.Mapping): constants.SETUPTOOLS_PLUGINS_ENTRY_POINT): plugin_ep = PluginEntryPoint(entry_point) assert plugin_ep.name not in plugins, ( - 'PREFIX_FREE_DISTRIBTIONS messed up') + "PREFIX_FREE_DISTRIBTIONS messed up") plugins[plugin_ep.name] = plugin_ep return cls(plugins) @@ -78,7 +81,7 @@ class PluginRegistry(collections.Mapping): for ifaces in ifaces_groups))) def __repr__(self): - return '{0}({1!r})'.format(self.__class__.__name__, self.plugins) + return "{0}({1!r})".format(self.__class__.__name__, self.plugins) def __getitem__(self, name): return self.plugins[name] @@ -128,7 +131,7 @@ def prepare_plugins(initialized): return prepared # succefully prepared + misconfigured -def pick_plugin(config, default, plugins, ifaces, question): +def _pick_plugin(config, default, plugins, ifaces, question): if default is not None: filtered = {default: plugins[default]} else: @@ -137,7 +140,7 @@ def pick_plugin(config, default, plugins, ifaces, question): for plugin_ep in plugins.itervalues(): plugin_ep.init(config) verified = verify_plugins(filtered, ifaces) - prepared = prepare_plugins(filtered) + prepared = prepare_plugins(verified) if len(prepared) > 1: logging.debug("Multiple candidate plugins: %s", prepared) @@ -153,20 +156,20 @@ def pick_plugin(config, default, plugins, ifaces, question): def pick_authenticator(config, default, plugins): """Pick authentication plugin.""" - return pick_plugin( - config, default, plugins, (interfaces.IAuthenticator,), - "How would you like to authenticate with Let's Encrypt CA?") + return _pick_plugin( + config, default, plugins, (interfaces.IAuthenticator,), + "How would you like to authenticate with Let's Encrypt CA?") def pick_installer(config, default, plugins): """Pick installer plugin.""" - return pick_plugin(config, default, plugins, (interfaces.IInstaller,), - "How would you like to install certificates?") + return _pick_plugin(config, default, plugins, (interfaces.IInstaller,), + "How would you like to install certificates?") def pick_configurator(config, default, plugins): """Pick configurator plugin.""" - return pick_plugin( + return _pick_plugin( config, default, plugins, (interfaces.IAuthenticator, interfaces.IInstaller), "How would you like to install certificates?") diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py new file mode 100644 index 000000000..dcfdb66f1 --- /dev/null +++ b/letsencrypt/client/plugins/disco_test.py @@ -0,0 +1,97 @@ +"""Tests for letsencrypt.client.plugins.disco.""" +import pkg_resources +import unittest + +import mock + +from letsencrypt.client.plugins.standalone import authenticator + + +class PluginEntryPointTest(unittest.TestCase): + """Tests for letsencrypt.client.plugins.disco.PluginEntryPoint.""" + + def setUp(self): + self.ep1 = pkg_resources.EntryPoint( + "ep1", "p1.ep1", dist=mock.MagicMock(key="p1")) + self.ep1prim = pkg_resources.EntryPoint( + "ep1", "p2.ep2", dist=mock.MagicMock(key="p2")) + # nested + self.ep2 = pkg_resources.EntryPoint( + "ep2", "p2.foo.ep2", dist=mock.MagicMock(key="p2")) + # project name != top-level package name + self.ep3 = pkg_resources.EntryPoint( + "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) + # something we can load()/require() + self.ep_sa = pkg_resources.EntryPoint( + "sa", "letsencrypt.client.plugins.standalone.authenticator", + attrs=('StandaloneAuthenticator',), + dist=mock.MagicMock(key="letsencrypt")) + + from letsencrypt.client.plugins.disco import PluginEntryPoint + self.plugin_ep = PluginEntryPoint(self.ep_sa) + + def test__init__(self): + self.assertFalse(self.plugin_ep.initialized) + self.assertTrue(self.plugin_ep.entry_point is self.ep_sa) + self.assertEqual("sa", self.plugin_ep.name) + + self.assertTrue( + self.plugin_ep.plugin_cls is authenticator.StandaloneAuthenticator) + + def test_init(self): + config = mock.MagicMock() + plugin = self.plugin_ep.init(config=config) + self.assertTrue(self.plugin_ep.initialized) + self.assertTrue(plugin.config is config) + # memoize! + self.assertTrue(self.plugin_ep.init() is plugin) + self.assertTrue(plugin.config is config) + # try to give different config + self.assertTrue(self.plugin_ep.init(123) is plugin) + self.assertTrue(plugin.config is config) + + def test_entry_point_to_plugin_name(self): + from letsencrypt.client.plugins.disco import PluginEntryPoint + + names = { + self.ep1: "p1:ep1", + self.ep1prim: "p2:ep1", + self.ep2: "p2:ep2", + self.ep3: "p3:ep3", + self.ep_sa: "sa", + } + + for entry_point, name in names.iteritems(): + self.assertEqual( + name, PluginEntryPoint.entry_point_to_plugin_name(entry_point)) + + def test_name_with_description(self): + self.assertTrue( + self.plugin_ep.name_with_description.startswith("sa (")) + + def test_repr(self): + self.assertEqual("PluginEntryPoint#sa", repr(self.plugin_ep)) + + +class PluginsRegistryTest(unittest.TestCase): + """Tests for letsencrypt.client.plugins.disco.PluginsRegistry.""" + + def setUp(self): + from letsencrypt.client.plugins.disco import PluginsRegistry + # TODO: mock out pkg_resources.iter_entry_points + self.plugins = PluginsRegistry.find_all() + + def test_init(self): + self.assertTrue(self.plugins["standalone"].plugin_cls + is authenticator.StandaloneAuthenticator) + + def test_filter(self): + filtered = self.plugins.filter() + self.assertEqual(len(self.plugins), len(filtered)) + + def test_repr(self): + repr(self.plugins) + + +if __name__ == "__main__": + unittest.main() From bd45d5ceeaab48473db9a1e43f59e8112d6bb2ef Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 12:38:33 +0000 Subject: [PATCH 20/76] constants.CLI_DEFAULTS / flag_default() --- letsencrypt/client/cli.py | 33 +++++++++++++++++++-------------- letsencrypt/client/constants.py | 28 +++++++++++++++------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index c0d83fb61..c4ede223f 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -114,7 +114,8 @@ def auth(args, config, plugins): if acc is None: return None - authenticator = plugins_disco.pick_authenticator(config, args.authenticator, plugins) + authenticator = plugins_disco.pick_authenticator( + config, args.authenticator, plugins) if authenticator is None: return "Authenticator could not be determined" @@ -236,7 +237,12 @@ def read_file(filename): raise argparse.ArgumentTypeError(exc.strerror) +def flag_default(name): + """Default value for CLI flag.""" + return constants.CLI_DEFAULTS[name] + def config_help(name): + """Help message for `.IConfig` attribute.""" return interfaces.IConfig[name].__doc__ @@ -246,14 +252,14 @@ def create_parser(plugins): description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter, args_for_setting_config_path=["-c", "--config"], - default_config_files=constants.DEFAULT_CONFIG_FILES) + default_config_files=flag_default("config_files")) add = parser.add_argument # --help is automatically provided by argparse add("--version", action="version", version="%(prog)s {0}".format( letsencrypt.__version__)) add("-v", "--verbose", dest="verbose_count", action="count", - default=constants.DEFAULT_VERBOSE_COUNT) + default=flag_default("verbose_count")) add("--no-confirm", dest="no_confirm", action="store_true", help="Turn off confirmation screens, currently used for --revoke") add("-e", "--agree-tos", dest="tos", action="store_true", @@ -295,14 +301,13 @@ def create_parser(plugins): # subparser.add_argument("domains", nargs="*", metavar="domain") add("-d", "--domains", metavar="DOMAIN", action="append") - add("-s", "--server", default=constants.DEFAULT_SERVER, + add("-s", "--server", default=flag_default("server"), help=config_help("server")) add("-k", "--authkey", type=read_file, help="Path to the authorized key file") add("-m", "--email", help=config_help("email")) add("-B", "--rsa-key-size", type=int, metavar="N", - default=constants.DEFAULT_RSA_KEY_SIZE, - help=config_help("rsa_key_size")) + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) # TODO: resolve - assumes binary logic while client.py assumes ternary. add("-r", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " @@ -317,7 +322,7 @@ def create_parser(plugins): parser_rollback.add_argument( "--checkpoints", type=int, metavar="N", - default=constants.DEFAULT_ROLLBACK_CHECKPOINTS, + default=flag_default("rollback_checkpoints"), help="Revert configuration N number of checkpoints.") paths_parser(parser.add_argument_group("paths")) @@ -332,22 +337,22 @@ def create_parser(plugins): def paths_parser(parser): add = parser.add_argument - add("--config-dir", default=constants.DEFAULT_CONFIG_DIR, + add("--config-dir", default=flag_default("config_dir"), help=config_help("config_dir")) - add("--work-dir", default=constants.DEFAULT_WORK_DIR, + add("--work-dir", default=flag_default("work_dir"), help=config_help("work_dir")) - add("--backup-dir", default=constants.DEFAULT_BACKUP_DIR, + add("--backup-dir", default=flag_default("backup_dir"), help=config_help("backup_dir")) - add("--key-dir", default=constants.DEFAULT_KEY_DIR, + add("--key-dir", default=flag_default("key_dir"), help=config_help("key_dir")) - add("--cert-dir", default=constants.DEFAULT_CERTS_DIR, + add("--cert-dir", default=flag_default("certs_dir"), help=config_help("cert_dir")) add("--le-vhost-ext", default="-le-ssl.conf", help=config_help("le_vhost_ext")) - add("--cert-path", default=constants.DEFAULT_CERT_PATH, + add("--cert-path", default=flag_default("cert_path"), help=config_help("cert_path")) - add("--chain-path", default=constants.DEFAULT_CHAIN_PATH, + add("--chain-path", default=flag_default("chain_path"), help=config_help("chain_path")) return parser diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 05f127662..d7ed55bb7 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -9,19 +9,21 @@ SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" -# CLI/IConfig defaults -DEFAULT_CONFIG_FILES = ["/etc/letsencrypt/cli.ini"] -DEFAULT_VERBOSE_COUNT = -(logging.WARNING / 10) -DEFAULT_SERVER = "www.letsencrypt-demo.org/acme/new-reg" -DEFAULT_RSA_KEY_SIZE = 2048 -DEFAULT_ROLLBACK_CHECKPOINTS = 0 -DEFAULT_CONFIG_DIR = "/etc/letsencrypt" -DEFAULT_WORK_DIR = "/var/lib/letsencrypt" -DEFAULT_BACKUP_DIR = "/var/lib/letsencrypt/backups" -DEFAULT_KEY_DIR = "/etc/letsencrypt/keys" -DEFAULT_CERTS_DIR = "/etc/letsencrypt/certs" -DEFAULT_CERT_PATH = "/etc/letsencrypt/certs/cert-letsencrypt.pem" -DEFAULT_CHAIN_PATH = "/etc/letsencrypt/certs/chain-letsencrypt.pem" +CLI_DEFAULTS = dict( + config_files=["/etc/letsencrypt/cli.ini"], + verbose_count=-(logging.WARNING / 10), + server="www.letsencrypt-demo.org/acme/new-reg", + rsa_key_size=2048, + rollback_checkpoints=0, + config_dir="/etc/letsencrypt", + work_dir="/var/lib/letsencrypt", + backup_dir="/var/lib/letsencrypt/backups", + key_dir="/etc/letsencrypt/keys", + certs_dir="/etc/letsencrypt/certs", + cert_path="/etc/letsencrypt/certs/cert-letsencrypt.pem", + chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", +) +"""Defaults for CLI flags and `.IConfig` attributes.""" EXCLUSIVE_CHALLENGES = frozenset([frozenset([ From 8a5be3ee3ab01d578812872d3c48e0ce687cded1 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 13:14:03 +0000 Subject: [PATCH 21/76] Remove determine_{authenticator,installer} --- letsencrypt/client/cli.py | 5 +- letsencrypt/client/client.py | 84 +++----------------- letsencrypt/client/display/ops.py | 38 ++------- letsencrypt/client/plugins/disco.py | 26 +++--- letsencrypt/client/tests/client_test.py | 74 ++--------------- letsencrypt/client/tests/display/ops_test.py | 44 ---------- 6 files changed, 42 insertions(+), 229 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index c4ede223f..b10868b3e 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -153,12 +153,13 @@ def revoke(args, config, plugins): # This depends on the renewal config and cannot be completed yet. zope.component.getUtility(interfaces.IDisplay).notification( "Revocation is not available with the new Boulder server yet.") - #client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) + #client.revoke(args.installer, config, plugins, args.no_confirm, + # args.rev_cert, args.rev_key) def rollback(args, config, plugins): """Rollback.""" - client.rollback(args.checkpoints, config) + client.rollback(args.installer, args.checkpoints, config, plugins) def config_changes(args, config, plugins): diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 9d1083e46..a98de272d 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -20,7 +20,10 @@ from letsencrypt.client import network2 from letsencrypt.client import reverter from letsencrypt.client import revoker +from letsencrypt.client.plugins import disco as plugins_disco + from letsencrypt.client.plugins.apache import configurator + from letsencrypt.client.display import ops as display_ops from letsencrypt.client.display import enhancements @@ -314,49 +317,6 @@ def validate_key_csr(privkey, csr=None): "The key and CSR do not match") -# This should be controlled by commandline parameters -def determine_authenticator(all_auths): - """Returns a valid IAuthenticator. - - :param list all_auths: Where each is a - :class:`letsencrypt.client.interfaces.IAuthenticator` object - - :returns: Valid Authenticator object or None - - :raises letsencrypt.client.errors.LetsEncryptClientError: If no - authenticator is available. - - """ - # Available Authenticator objects - avail_auths = [] - # Error messages for misconfigured authenticators - errs = {} - - for pot_auth in all_auths: - try: - pot_auth.prepare() - except errors.LetsEncryptMisconfigurationError as err: - errs[pot_auth] = err - except errors.LetsEncryptNoInstallationError: - continue - avail_auths.append(pot_auth) - - if len(avail_auths) > 1: - auth = display_ops.choose_authenticator(avail_auths, errs) - elif len(avail_auths) == 1: - auth = avail_auths[0] - else: - raise errors.LetsEncryptClientError("No Authenticators available.") - - if auth is not None and auth in errs: - logging.error("Please fix the configuration for the Authenticator. " - "The following error message was received: " - "%s", errs[auth]) - return - - return auth - - def determine_account(config): """Determine which account to use. @@ -379,29 +339,7 @@ def determine_account(config): return account.Account.from_prompts(config) -def determine_installer(config): - """Returns a valid installer if one exists. - - :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` - - :returns: IInstaller or `None` - :rtype: :class:`~letsencrypt.client.interfaces.IInstaller` or `None` - - """ - installer = configurator.ApacheConfigurator(config) - try: - installer.prepare() - return installer - except errors.LetsEncryptNoInstallationError: - logging.info("Unable to find a way to install the certificate.") - return - except errors.LetsEncryptMisconfigurationError: - # This will have to be changed in the future... - return installer - - -def rollback(checkpoints, config): +def rollback(default_installer, checkpoints, config, plugins): """Revert configuration the specified number of checkpoints. :param int checkpoints: Number of checkpoints to revert. @@ -411,7 +349,9 @@ def rollback(checkpoints, config): """ # Misconfigurations are only a slight problems... allow the user to rollback - installer = determine_installer(config) + installer = plugins_disco.pick_installer( + config, default_installer, plugins, question="Which installer " + "should be used for rollback?") # No Errors occurred during init... proceed normally # If installer is None... couldn't find an installer... there shouldn't be @@ -421,18 +361,16 @@ def rollback(checkpoints, config): installer.restart() -def revoke(config, no_confirm, cert, authkey): +def revoke(default_installer, config, plugins, no_confirm, cert, authkey): """Revoke certificates. :param config: Configuration. :type config: :class:`letsencrypt.client.interfaces.IConfig` """ - # Misconfigurations don't really matter. Determine installer better choose - # correctly though. - # This will need some better prepared or properly configured parameter... - # I will figure it out later... - installer = determine_installer(config) + installer = plugins_disco.pick_installer( + config, default_installer, plugins, question="Which installer " + "should be used for certificate revocation?") revoc = revoker.Revoker(installer, config, no_confirm) # Cert is most selective, so it is chosen first. diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 6582035bd..d59c1ceb1 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -12,6 +12,11 @@ util = zope.component.getUtility # pylint: disable=invalid-name def choose_plugin(prepared, question): + """Allow the user to choose ther plugin. + + :param list prepared: + + """ opts = [plugin_ep.name_with_description if error is None else "%s (Misconfigured)" % plugin_ep.name_with_description for (plugin_ep, error) in prepared] @@ -33,39 +38,6 @@ def choose_plugin(prepared, question): return None -def choose_authenticator(auths, errs): - """Allow the user to choose their authenticator. - - :param list auths: Where each of type - :class:`letsencrypt.client.interfaces.IAuthenticator` object - :param dict errs: Mapping IAuthenticator objects to error messages - - :returns: Authenticator selected - :rtype: :class:`letsencrypt.client.interfaces.IAuthenticator` or `None` - - """ - descs = [auth.description if auth not in errs - else "%s (Misconfigured)" % auth.description - for auth in auths] - - while True: - code, index = util(interfaces.IDisplay).menu( - "How would you like to authenticate with the Let's Encrypt CA?", - descs, help_label="More Info") - - if code == display_util.OK: - return auths[index] - elif code == display_util.HELP: - if auths[index] in errs: - msg = "Reported Error: %s" % errs[auths[index]] - else: - msg = auths[index].more_info() - util(interfaces.IDisplay).notification( - msg, height=display_util.HEIGHT) - else: - return - - def choose_account(accounts): """Choose an account. diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index 37d60cbc8..b60c836d6 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -131,7 +131,7 @@ def prepare_plugins(initialized): return prepared # succefully prepared + misconfigured -def _pick_plugin(config, default, plugins, ifaces, question): +def _pick_plugin(config, default, plugins, question, ifaces): if default is not None: filtered = {default: plugins[default]} else: @@ -154,22 +154,26 @@ def _pick_plugin(config, default, plugins, ifaces, question): return None -def pick_authenticator(config, default, plugins): +def pick_authenticator( + config, default, plugins, question="How would you " + "like to authenticate with Let's Encrypt CA?"): """Pick authentication plugin.""" return _pick_plugin( - config, default, plugins, (interfaces.IAuthenticator,), - "How would you like to authenticate with Let's Encrypt CA?") + config, default, plugins, question, (interfaces.IAuthenticator,)) -def pick_installer(config, default, plugins): +def pick_installer(config, default, plugins, + question="How would you like to install certificates?"): """Pick installer plugin.""" - return _pick_plugin(config, default, plugins, (interfaces.IInstaller,), - "How would you like to install certificates?") + return _pick_plugin( + config, default, plugins, question, (interfaces.IInstaller,)) -def pick_configurator(config, default, plugins): +def pick_configurator( + config, default, plugins, + question="How would you like to authenticate and install " + "certificates?"): """Pick configurator plugin.""" return _pick_plugin( - config, default, plugins, - (interfaces.IAuthenticator, interfaces.IInstaller), - "How would you like to install certificates?") + config, default, plugins, question + (interfaces.IAuthenticator, interfaces.IInstaller)) diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index b5ccf099b..b4595f257 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -54,63 +54,6 @@ class DetermineAccountTest(unittest.TestCase): self.assertTrue(chosen_acc.email, test_acc.email) -class DetermineAuthenticatorTest(unittest.TestCase): - def setUp(self): - from letsencrypt.client.plugins.apache.configurator import ( - ApacheConfigurator) - from letsencrypt.client.plugins.standalone.authenticator import ( - StandaloneAuthenticator) - - self.mock_stand = mock.MagicMock( - spec=StandaloneAuthenticator, description="Apache Web Server") - self.mock_apache = mock.MagicMock( - spec=ApacheConfigurator, description="Standalone Authenticator") - - self.mock_config = mock.Mock() - - self.all_auths = [self.mock_apache, self.mock_stand] - - @classmethod - def _call(cls, all_auths): - from letsencrypt.client.client import determine_authenticator - return determine_authenticator(all_auths) - - @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") - def test_accept_two(self, mock_choose): - mock_choose.return_value = self.mock_stand() - self.assertEqual(self._call(self.all_auths), self.mock_stand()) - - def test_accept_one(self): - self.mock_apache.prepare.return_value = self.mock_apache - self.assertEqual( - self._call(self.all_auths[:1]), self.mock_apache) - - def test_no_installation_one(self): - self.mock_apache.prepare.side_effect = ( - errors.LetsEncryptNoInstallationError) - - self.assertEqual(self._call(self.all_auths), self.mock_stand) - - def test_no_installations(self): - self.mock_apache.prepare.side_effect = ( - errors.LetsEncryptNoInstallationError) - self.mock_stand.prepare.side_effect = ( - errors.LetsEncryptNoInstallationError) - - self.assertRaises(errors.LetsEncryptClientError, - self._call, - self.all_auths) - - @mock.patch("letsencrypt.client.client.logging") - @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") - def test_misconfigured(self, mock_choose, unused_log): - self.mock_apache.prepare.side_effect = ( - errors.LetsEncryptMisconfigurationError) - mock_choose.return_value = self.mock_apache - - self.assertTrue(self._call(self.all_auths) is None) - - class RollbackTest(unittest.TestCase): """Test the rollback function.""" def setUp(self): @@ -121,23 +64,22 @@ class RollbackTest(unittest.TestCase): @classmethod def _call(cls, checkpoints): from letsencrypt.client.client import rollback - rollback(checkpoints, mock.MagicMock()) + rollback(None, checkpoints, {}, mock.MagicMock()) - @mock.patch("letsencrypt.client.client.determine_installer") - def test_no_problems(self, mock_det): - mock_det.side_effect = self.m_install + @mock.patch("letsencrypt.client.client.plugins_disco.pick_installer") + def test_no_problems(self, mock_pick_installer): + mock_pick_installer.side_effect = self.m_install self._call(1) self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1) self.assertEqual(self.m_install().restart.call_count, 1) - @mock.patch("letsencrypt.client.client.determine_installer") - def test_no_installer(self, mock_det): - mock_det.return_value = None - - # Just make sure no exceptions are raised + @mock.patch("letsencrypt.client.client.plugins_disco.pick_installer") + def test_no_installer(self, mock_pick_installer): + mock_pick_installer.return_value = None self._call(1) + # Just make sure no exceptions are raised if __name__ == "__main__": diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/client/tests/display/ops_test.py index de5745e8e..2da411b6b 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/client/tests/display/ops_test.py @@ -11,50 +11,6 @@ from letsencrypt.client import account from letsencrypt.client import le_util from letsencrypt.client.display import util as display_util -class ChooseAuthenticatorTest(unittest.TestCase): - """Test choose_authenticator function.""" - def setUp(self): - 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 = [self.mock_apache, self.mock_stand] - - self.errs = {self.mock_apache: "This is an error message."} - - @classmethod - def _call(cls, auths, errs): - from letsencrypt.client.display.ops import choose_authenticator - return choose_authenticator(auths, errs) - - @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(self.auths, {}) - - 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") - def test_no_choice(self, mock_util): - mock_util().menu.return_value = (display_util.CANCEL, 0) - self.assertTrue(self._call(self.auths, {}) is None) - class ChooseAccountTest(unittest.TestCase): """Test choose_account.""" From 2ffc3c37cba611262cd846c30ede64c642722f96 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 2 May 2015 18:00:57 +0000 Subject: [PATCH 22/76] Fix missing comma --- letsencrypt/client/plugins/disco.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index b60c836d6..3262a315b 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -175,5 +175,5 @@ def pick_configurator( "certificates?"): """Pick configurator plugin.""" return _pick_plugin( - config, default, plugins, question + config, default, plugins, question, (interfaces.IAuthenticator, interfaces.IInstaller)) From 595230fd8ecc004ff56433b7ec5902e39018ceb6 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 07:56:49 +0000 Subject: [PATCH 23/76] PluginsEntryPoint.prepare, move pick_* to display_ops --- .../plugins/letsencrypt_example_plugins.py | 8 +- letsencrypt/client/cli.py | 19 +-- letsencrypt/client/client.py | 6 +- letsencrypt/client/display/ops.py | 54 +++++- letsencrypt/client/interfaces.py | 6 +- letsencrypt/client/plugins/common.py | 3 +- letsencrypt/client/plugins/disco.py | 155 ++++++++---------- .../plugins/standalone/authenticator.py | 2 + letsencrypt/client/tests/client_test.py | 4 +- 9 files changed, 146 insertions(+), 111 deletions(-) diff --git a/examples/plugins/letsencrypt_example_plugins.py b/examples/plugins/letsencrypt_example_plugins.py index 11baf35a7..7c6d1311c 100644 --- a/examples/plugins/letsencrypt_example_plugins.py +++ b/examples/plugins/letsencrypt_example_plugins.py @@ -10,18 +10,22 @@ from letsencrypt.client.plugins import common class Authenticator(common.Plugin): + """Example Authenticator.""" zope.interface.implements(interfaces.IAuthenticator) + zope.interface.classProvides(interfaces.IPluginFactory) - description = 'Example Authenticator plugin' + description = "Example Authenticator plugin" # Implement all methods from IAuthenticator, remembering to add # "self" as first argument, e.g. def prepare(self)... class Installer(common.Plugins): + """Example Installer.""" zope.interface.implements(interfaces.IInstaller) + zope.interface.classProvides(interfaces.IPluginFactory) - description = 'Example Installer plugin' + description = "Example Installer plugin" # Implement all methods from IInstaller, remembering to add # "self" as first argument, e.g. def get_all_names(self)... diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index b10868b3e..e4150df7c 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -89,14 +89,14 @@ def run(args, config, plugins): "pair, but not both, is allowed") if args.authenticator is not None or args.installer is not None: - installer = plugins_disco.pick_installer( + installer = display_ops.pick_installer( config, args.installer, plugins) - authenticator = plugins_disco.pick_authenticator( + authenticator = display_ops.pick_authenticator( config, args.authenticator, plugins) else: # TODO: this assume that user doesn't want to pick authenticator # and installer separately... - authenticator = installer = plugins_disco.pick_configurator( + authenticator = installer = display_ops.pick_configurator( config, args.configurator, plugins) if installer is None or authenticator is None: @@ -114,13 +114,13 @@ def auth(args, config, plugins): if acc is None: return None - authenticator = plugins_disco.pick_authenticator( + authenticator = display_ops.pick_authenticator( config, args.authenticator, plugins) if authenticator is None: return "Authenticator could not be determined" if args.installer is not None: - installer = plugins_disco.pick_installer(config, args.installer, plugins) + installer = display_ops.pick_installer(config, args.installer, plugins) else: installer = None @@ -135,7 +135,7 @@ def install(args, config, plugins): if acc is None: return None - installer = plugins_disco.pick_installer(config, args.installer, plugins) + installer = display_ops.pick_installer(config, args.installer, plugins) if installer is None: return "Installer could not be determined" acme, doms = _common_run( @@ -214,11 +214,10 @@ def plugins_cmd(args, config, plugins): if not args.prepare: return _print_plugins(filtered) - prepared = plugins_disco.prepare_plugins(initialized) - logging.debug("Prepared plugins: %s", plugins) + available = plugins_disco.available_plugins(filtered) + logging.debug("Prepared plugins: %s", available) - _print_plugins(prepared, plugins, names) - plugins_disco + _print_plugins(available) def read_file(filename): diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index a98de272d..3f1c627e8 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -20,8 +20,6 @@ from letsencrypt.client import network2 from letsencrypt.client import reverter from letsencrypt.client import revoker -from letsencrypt.client.plugins import disco as plugins_disco - from letsencrypt.client.plugins.apache import configurator from letsencrypt.client.display import ops as display_ops @@ -349,7 +347,7 @@ def rollback(default_installer, checkpoints, config, plugins): """ # Misconfigurations are only a slight problems... allow the user to rollback - installer = plugins_disco.pick_installer( + installer = display_ops.pick_installer( config, default_installer, plugins, question="Which installer " "should be used for rollback?") @@ -368,7 +366,7 @@ def revoke(default_installer, config, plugins, no_confirm, cert, authkey): :type config: :class:`letsencrypt.client.interfaces.IConfig` """ - installer = plugins_disco.pick_installer( + installer = display_ops.pick_installer( config, default_installer, plugins, question="Which installer " "should be used for certificate revocation?") diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index d59c1ceb1..7b035ffd0 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -6,6 +6,7 @@ import zope.component from letsencrypt.client import interfaces from letsencrypt.client.display import util as display_util +from letsencrypt.client.plugins import disco as plugins_disco # Define a helper function to avoid verbose code util = zope.component.getUtility # pylint: disable=invalid-name @@ -17,9 +18,9 @@ def choose_plugin(prepared, question): :param list prepared: """ - opts = [plugin_ep.name_with_description if error is None + opts = [plugin_ep.name_with_description if not plugin_ep.misconfigured else "%s (Misconfigured)" % plugin_ep.name_with_description - for (plugin_ep, error) in prepared] + for plugin_ep in prepared.itervalues()] while True: code, index = util(interfaces.IDisplay).menu( @@ -29,7 +30,7 @@ def choose_plugin(prepared, question): return prepared[index][0] elif code == display_util.HELP: if prepared[index][1] is not None: - msg = "Reported Error: %s" % prepared[index][1] + msg = "Reported Error: %s" % prepared[index].prepare() else: msg = prepared[index][0].init().more_info() util(interfaces.IDisplay).notification( @@ -37,6 +38,53 @@ def choose_plugin(prepared, question): else: return None +def _pick_plugin(config, default, plugins, question, ifaces): + if default is not None: + filtered = {default: plugins[default]} + else: + filtered = plugins.filter(ifaces) + + for plugin_ep in plugins.itervalues(): + plugin_ep.init(config) + verified = plugins_disco.verify_plugins(filtered, ifaces) + prepared = plugins_disco.available_plugins(verified) + + if len(prepared) > 1: + logging.debug("Multiple candidate plugins: %s", prepared) + return choose_plugin(prepared.values(), question).init() + elif len(prepared) == 1: + plugin_ep = prepared.values()[0] + logging.debug("Single candidate plugin: %s", plugin_ep) + return plugin_ep.init() + else: + logging.debug("No candidate plugin") + return None + + +def pick_authenticator( + config, default, plugins, question="How would you " + "like to authenticate with Let's Encrypt CA?"): + """Pick authentication plugin.""" + return _pick_plugin( + config, default, plugins, question, (interfaces.IAuthenticator,)) + + +def pick_installer(config, default, plugins, + question="How would you like to install certificates?"): + """Pick installer plugin.""" + return _pick_plugin( + config, default, plugins, question, (interfaces.IInstaller,)) + + +def pick_configurator( + config, default, plugins, + question="How would you like to authenticate and install " + "certificates?"): + """Pick configurator plugin.""" + return _pick_plugin( + config, default, plugins, question, + (interfaces.IAuthenticator, interfaces.IInstaller)) + def choose_account(accounts): """Choose an account. diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index df8b616a6..018462b3c 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -69,9 +69,11 @@ class IPlugin(zope.interface.Interface): Finish up any additional initialization. :raises letsencrypt.client.errors.LetsEncryptMisconfigurationError: - when full initialization cannot be completed. + when full initialization cannot be completed. Plugin will be + displayed on a list of available plugins. :raises letsencrypt.client.errors.LetsEncryptNoInstallationError: - when the necessary programs/files cannot be located. + when the necessary programs/files cannot be located. Plugin + will NOT be displayed on a list of available plugins. """ diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/client/plugins/common.py index e9ee4d573..60b868c37 100644 --- a/letsencrypt/client/plugins/common.py +++ b/letsencrypt/client/plugins/common.py @@ -18,7 +18,8 @@ def dest_namespace(name): class Plugin(object): """Generic plugin.""" zope.interface.implements(interfaces.IPlugin) - zope.interface.classProvides(interfaces.IPluginFactory) + # classProvides is not inherited, subclasses must define it on their own + #zope.interface.classProvides(interfaces.IPluginFactory) def __init__(self, config, name): self.config = config diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index 3262a315b..dec80afa9 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -9,8 +9,6 @@ from letsencrypt.client import constants from letsencrypt.client import errors from letsencrypt.client import interfaces -from letsencrypt.client.display import ops as display_ops - class PluginEntryPoint(object): """Plugin entry point.""" @@ -23,11 +21,7 @@ class PluginEntryPoint(object): self.plugin_cls = entry_point.load() self.entry_point = entry_point self._initialized = None - - @property - def initialized(self): - """Has the plugin been initialized already?""" - return self._initialized is not None + self._prepared = None @classmethod def entry_point_to_plugin_name(cls, entry_point): @@ -36,6 +30,11 @@ class PluginEntryPoint(object): return entry_point.name return entry_point.dist.key + ":" + entry_point.name + @property + def initialized(self): + """Has the plugin been initialized already?""" + return self._initialized is not None + def init(self, config=None): """Memoized plugin inititialization.""" if not self.initialized: @@ -43,6 +42,39 @@ class PluginEntryPoint(object): self._initialized = self.plugin_cls(config, self.name) return self._initialized + @property + def prepared(self): + """Has the plugin been prepared already?""" + if not self.initialized: + logging.debug(".prepared called on uninitialized %s", self) + return self._prepared is not None + + def prepare(self): + """Memoized plugin preparation.""" + assert self.initialized + if self._prepared is None: + try: + self._initialized.prepare() + except errors.LetsEncryptMisconfigurationError as error: + logging.debug("Misconfigured %s: %s", self, error) + self._prepared = error + except errors.LetsEncryptNoInstallationError as error: + logging.debug("No installation (%s): %s", self, error) + self._prepared = error + else: + self._prepared = True + return self._prepared + + @property + def misconfigured(self): + """Is plugin misconfigured?""" + return isinstance(self._prepared, errors.LetsEncryptMisconfigurationError) + + @property + def available(self): + """Is plugin available, i.e. prepared or misconfigured?""" + return self._prepared is True or self.misconfigured + def __repr__(self): return "PluginEntryPoint#{0}".format(self.name) @@ -51,6 +83,20 @@ class PluginEntryPoint(object): """Name with description. Handy for UI.""" return "{0} ({1})".format(self.name, self.plugin_cls.description) + def verify(self, ifaces): + assert self.initialized + for iface in ifaces: # zope.interface.providedBy(plugin) + try: + zope.interface.verify.verifyObject(iface, self.init()) + except zope.interface.exceptions.BrokenImplementation: + if iface.implementedBy(self.plugin_cls): + logging.debug( + "%s implements %s but object does " + "not verify", self.plugin_cls, iface.__name__) + return False + return True + + class PluginsRegistry(collections.Mapping): """Plugins registry.""" @@ -66,8 +112,12 @@ class PluginsRegistry(collections.Mapping): constants.SETUPTOOLS_PLUGINS_ENTRY_POINT): plugin_ep = PluginEntryPoint(entry_point) assert plugin_ep.name not in plugins, ( - "PREFIX_FREE_DISTRIBTIONS messed up") - plugins[plugin_ep.name] = plugin_ep + "PREFIX_FREE_DISTRIBUTIONS messed up") + if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): + plugins[plugin_ep.name] = plugin_ep + else: + logging.warning("Plugin entry point %s does not provide " + "IPluginFactory, skipping", plugin_ep) return cls(plugins) def filter(self, *ifaces_groups): @@ -81,7 +131,8 @@ class PluginsRegistry(collections.Mapping): for ifaces in ifaces_groups))) def __repr__(self): - return "{0}({1!r})".format(self.__class__.__name__, self.plugins) + return "{0}({1!r})".format( + self.__class__.__name__, set(self.plugins.itervalues())) def __getitem__(self, name): return self.plugins[name] @@ -95,85 +146,15 @@ class PluginsRegistry(collections.Mapping): def verify_plugins(initialized, ifaces): """Verify plugin objects.""" - verified = {} - for name, plugin_ep in initialized.iteritems(): - verifies = True - for iface in ifaces: # zope.interface.providedBy(plugin) - try: - zope.interface.verify.verifyObject(iface, plugin_ep.init()) - except zope.interface.exceptions.BrokenImplementation: - if iface.implementedBy(plugin_ep.plugin_cls): - logging.debug( - "%s implements %s but object does " - "not verify", plugin_ep.plugin_cls, iface.__name__) - verifies = False - break - if verifies: - verified[name] = plugin_ep - return verified + return dict((name, plugin_ep) for name, plugin_ep in initialized.iteritems() + if plugin_ep.verify(ifaces)) -def prepare_plugins(initialized): - """Prepare plugins.""" +def available_plugins(initialized): + """Prepare plugins and filter available.""" prepared = {} - for name, plugin_ep in initialized.iteritems(): - error = None - try: - plugin_ep.init().prepare() - except errors.LetsEncryptMisconfigurationError as error: - logging.debug("Misconfigured %s: %s", plugin_ep, error) - except errors.LetsEncryptNoInstallationError as error: - logging.debug("No installation (%s): %s", plugin_ep, error) - continue - prepared[name] = (plugin_ep, error) - + plugin_ep.prepare() + if plugin_ep.available: + prepared[name] = plugin_ep return prepared # succefully prepared + misconfigured - - -def _pick_plugin(config, default, plugins, question, ifaces): - if default is not None: - filtered = {default: plugins[default]} - else: - filtered = plugins.filter(ifaces) - - for plugin_ep in plugins.itervalues(): - plugin_ep.init(config) - verified = verify_plugins(filtered, ifaces) - prepared = prepare_plugins(verified) - - if len(prepared) > 1: - logging.debug("Multiple candidate plugins: %s", prepared) - return display_ops.choose_plugin(prepared.values(), question).init() - elif len(prepared) == 1: - plugin_ep = prepared.values()[0][0] - logging.debug("Single candidate plugin: %s", plugin_ep) - return plugin_ep.init() - else: - logging.debug("No candidate plugin") - return None - - -def pick_authenticator( - config, default, plugins, question="How would you " - "like to authenticate with Let's Encrypt CA?"): - """Pick authentication plugin.""" - return _pick_plugin( - config, default, plugins, question, (interfaces.IAuthenticator,)) - - -def pick_installer(config, default, plugins, - question="How would you like to install certificates?"): - """Pick installer plugin.""" - return _pick_plugin( - config, default, plugins, question, (interfaces.IInstaller,)) - - -def pick_configurator( - config, default, plugins, - question="How would you like to authenticate and install " - "certificates?"): - """Pick configurator plugin.""" - return _pick_plugin( - config, default, plugins, question, - (interfaces.IAuthenticator, interfaces.IInstaller)) diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index 668b3f716..a10ffd32d 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -31,6 +31,8 @@ class StandaloneAuthenticator(common.Plugin): """ zope.interface.implements(interfaces.IAuthenticator) + zope.interface.classProvides(interfaces.IPluginFactory) + description = "Standalone Authenticator" def __init__(self, *args, **kwargs): diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index b4595f257..aff9b5c84 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -66,7 +66,7 @@ class RollbackTest(unittest.TestCase): from letsencrypt.client.client import rollback rollback(None, checkpoints, {}, mock.MagicMock()) - @mock.patch("letsencrypt.client.client.plugins_disco.pick_installer") + @mock.patch("letsencrypt.client.client.display_ops.pick_installer") def test_no_problems(self, mock_pick_installer): mock_pick_installer.side_effect = self.m_install @@ -75,7 +75,7 @@ class RollbackTest(unittest.TestCase): self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1) self.assertEqual(self.m_install().restart.call_count, 1) - @mock.patch("letsencrypt.client.client.plugins_disco.pick_installer") + @mock.patch("letsencrypt.client.client.display_ops.pick_installer") def test_no_installer(self, mock_pick_installer): mock_pick_installer.return_value = None self._call(1) From b600e2d27089c0daca2ed794451b6ecf101b80b7 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 12:38:53 +0000 Subject: [PATCH 24/76] PLuginsRegistry: verify, prepare, misconfigured, available. --- letsencrypt/client/cli.py | 51 +++++------------- letsencrypt/client/display/ops.py | 19 +++---- letsencrypt/client/plugins/disco.py | 66 ++++++++++++++++-------- letsencrypt/client/plugins/disco_test.py | 4 +- 4 files changed, 71 insertions(+), 69 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index e4150df7c..e77bef92d 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -82,7 +82,7 @@ def run(args, config, plugins): acc = _account_init(args, config) if acc is None: return None - + if args.configurator is not None and (args.installer is not None or args.authenticator is not None): return ("Either --configurator or --authenticator/--installer" @@ -171,53 +171,30 @@ def config_changes(args, config, plugins): client.config_changes(config) -def _print_plugins(plugins): - # TODO: this functions should use IDisplay rather than printing - - if not plugins: - print "No plugins found" - - for plugin_ep in plugins.itervalues(): - print "* {0}".format(plugin_ep.name) - print "Description: {0}".format(plugin_ep.plugin_cls.description) - print "Interfaces: {0}".format(", ".join( - iface.__name__ for iface in zope.interface.implementedBy( - plugin_ep.plugin_cls))) - print "Entry point: {0}".format(plugin_ep.entry_point) - - if plugin_ep.initialized: - print "Initialized: {0}".format(plugin_ep.init()) - - # if filtered == prepared: - #if isinstance(content, tuple) and content[1] is not None: - # print content[1] # error - - print # whitespace between plugins - - -def plugins_cmd(args, config, plugins): +def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print """List plugins.""" logging.debug("Discovered plugins: %s", plugins) ifaces = [] if args.ifaces is None else args.ifaces - filtered = plugins.filter(*((iface,) for iface in ifaces)) - logging.debug("Filtered plugins: %s", filtered) + filtered = plugins.filter_ifaces(*((iface,) for iface in ifaces)) + logging.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: - return _print_plugins(filtered) + print str(filtered) + return - for plugin_ep in filtered.itervalues(): - plugin_ep.init(config) - #verified = plugins_disco.verify_plugins(initialized, ifaces) - #logging.debug("Verified plugins: %s", initialized) + filtered.init(config) + verified = filtered.verify(ifaces) + logging.debug("Verified plugins: %r", verified) if not args.prepare: - return _print_plugins(filtered) + print str(verified) + return - available = plugins_disco.available_plugins(filtered) + verified.prepare() + available = verified.available() logging.debug("Prepared plugins: %s", available) - - _print_plugins(available) + print str(available) def read_file(filename): diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 7b035ffd0..8ca534883 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -6,7 +6,7 @@ import zope.component from letsencrypt.client import interfaces from letsencrypt.client.display import util as display_util -from letsencrypt.client.plugins import disco as plugins_disco + # Define a helper function to avoid verbose code util = zope.component.getUtility # pylint: disable=invalid-name @@ -18,8 +18,8 @@ def choose_plugin(prepared, question): :param list prepared: """ - opts = [plugin_ep.name_with_description if not plugin_ep.misconfigured - else "%s (Misconfigured)" % plugin_ep.name_with_description + opts = [plugin_ep.name_with_description + + (" [Misconfigured]" if plugin_ep.misconfigured else "") for plugin_ep in prepared.itervalues()] while True: @@ -40,14 +40,15 @@ def choose_plugin(prepared, question): def _pick_plugin(config, default, plugins, question, ifaces): if default is not None: - filtered = {default: plugins[default]} + # throw more UX-friendly error if default not in plugins + filtered = plugins.filter(lambda p_ep: p_ep.name == default) else: - filtered = plugins.filter(ifaces) + filtered = plugins.filter_ifaces(ifaces) - for plugin_ep in plugins.itervalues(): - plugin_ep.init(config) - verified = plugins_disco.verify_plugins(filtered, ifaces) - prepared = plugins_disco.available_plugins(verified) + filtered.init(config) + verified = filtered.verify(ifaces) + filtered.prepare() + prepared = filtered.available() if len(prepared) > 1: logging.debug("Multiple candidate plugins: %s", prepared) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index dec80afa9..a4557255e 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -96,6 +96,22 @@ class PluginEntryPoint(object): return False return True + def __str__(self): + lines = [ + "* {0}".format(self.name), + "Description: {0}".format(self.plugin_cls.description), + "Interfaces: {0}".format(", ".join( + iface.__name__ for iface in zope.interface.implementedBy( + self.plugin_cls))), + "Entry point: {0}".format(self.entry_point), + ] + + if self.initialized: + lines.append("Initialized: {0}".format(self.init())) + if self.prepared: + lines.append("Prep: {0}".format(self.prepare())) + + return "\n".join(lines) class PluginsRegistry(collections.Mapping): @@ -120,15 +136,34 @@ class PluginsRegistry(collections.Mapping): "IPluginFactory, skipping", plugin_ep) return cls(plugins) - def filter(self, *ifaces_groups): + def init(self, config): + """Initialize all plugins in the registry.""" + return [plugin_ep.init(config) for plugin_ep + in self.plugins.itervalues()] + + def filter(self, pred): + """Filter plugins based on predicate.""" + return type(self)(dict((name, plugin_ep) for name, plugin_ep + in self.plugins.iteritems() if pred(plugin_ep))) + + def filter_ifaces(self, *ifaces_groups): """Filter plugins based on interfaces.""" - return type(self)(dict( - (name, plugin_ep) - for name, plugin_ep in self.plugins.iteritems() - if not ifaces_groups or any( + return self.filter(lambda plugin_ep: not ifaces_groups or any( all(iface.implementedBy(plugin_ep.plugin_cls) for iface in ifaces) - for ifaces in ifaces_groups))) + for ifaces in ifaces_groups)) + + def verify(self, ifaces): + """Filter plugins based on verification.""" + return self.filter(lambda p_ep: p_ep.verify(ifaces)) + + def prepare(self): + return [plugin_ep.prepare() for plugin_ep in self.plugins.itervalues()] + + def available(self): + """Filter plugins based on availability.""" + return self.filter(lambda p_ep: p_ep.available) + # succefully prepared + misconfigured def __repr__(self): return "{0}({1!r})".format( @@ -143,18 +178,7 @@ class PluginsRegistry(collections.Mapping): def __len__(self): return len(self.plugins) - -def verify_plugins(initialized, ifaces): - """Verify plugin objects.""" - return dict((name, plugin_ep) for name, plugin_ep in initialized.iteritems() - if plugin_ep.verify(ifaces)) - - -def available_plugins(initialized): - """Prepare plugins and filter available.""" - prepared = {} - for name, plugin_ep in initialized.iteritems(): - plugin_ep.prepare() - if plugin_ep.available: - prepared[name] = plugin_ep - return prepared # succefully prepared + misconfigured + def __str__(self): + if not self.plugins: + return "No plugins" + return "\n\n".join(map(str, self.plugins.itervalues())) diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index dcfdb66f1..b55faba0c 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -85,8 +85,8 @@ class PluginsRegistryTest(unittest.TestCase): self.assertTrue(self.plugins["standalone"].plugin_cls is authenticator.StandaloneAuthenticator) - def test_filter(self): - filtered = self.plugins.filter() + def test_id_filter(self): + filtered = self.plugins.filter(lambda _: True) self.assertEqual(len(self.plugins), len(filtered)) def test_repr(self): From 1878db34163157f6115f40e76cfa17eb4883587d Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 13:21:36 +0000 Subject: [PATCH 25/76] More coverage for plugins.disco --- letsencrypt/client/plugins/disco.py | 40 ++++----- letsencrypt/client/plugins/disco_test.py | 100 ++++++++++++++++++----- 2 files changed, 100 insertions(+), 40 deletions(-) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index a4557255e..68cb230d0 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -30,6 +30,11 @@ class PluginEntryPoint(object): return entry_point.name return entry_point.dist.key + ":" + entry_point.name + @property + def name_with_description(self): + """Name with description. Handy for UI.""" + return "{0} ({1})".format(self.name, self.plugin_cls.description) + @property def initialized(self): """Has the plugin been initialized already?""" @@ -42,6 +47,20 @@ class PluginEntryPoint(object): self._initialized = self.plugin_cls(config, self.name) return self._initialized + def verify(self, ifaces): + """Verify that the plugin conforms to the specified interfaces.""" + assert self.initialized + for iface in ifaces: # zope.interface.providedBy(plugin) + try: + zope.interface.verify.verifyObject(iface, self.init()) + except zope.interface.exceptions.BrokenImplementation: + if iface.implementedBy(self.plugin_cls): + logging.debug( + "%s implements %s but object does " + "not verify", self.plugin_cls, iface.__name__) + return False + return True + @property def prepared(self): """Has the plugin been prepared already?""" @@ -68,7 +87,8 @@ class PluginEntryPoint(object): @property def misconfigured(self): """Is plugin misconfigured?""" - return isinstance(self._prepared, errors.LetsEncryptMisconfigurationError) + return isinstance( + self._prepared, errors.LetsEncryptMisconfigurationError) @property def available(self): @@ -78,24 +98,6 @@ class PluginEntryPoint(object): def __repr__(self): return "PluginEntryPoint#{0}".format(self.name) - @property - def name_with_description(self): - """Name with description. Handy for UI.""" - return "{0} ({1})".format(self.name, self.plugin_cls.description) - - def verify(self, ifaces): - assert self.initialized - for iface in ifaces: # zope.interface.providedBy(plugin) - try: - zope.interface.verify.verifyObject(iface, self.init()) - except zope.interface.exceptions.BrokenImplementation: - if iface.implementedBy(self.plugin_cls): - logging.debug( - "%s implements %s but object does " - "not verify", self.plugin_cls, iface.__name__) - return False - return True - def __str__(self): lines = [ "* {0}".format(self.name), diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index b55faba0c..54f77c941 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -3,7 +3,9 @@ import pkg_resources import unittest import mock +import zope.interface +from letsencrypt.client import errors from letsencrypt.client.plugins.standalone import authenticator @@ -21,7 +23,8 @@ class PluginEntryPointTest(unittest.TestCase): # project name != top-level package name self.ep3 = pkg_resources.EntryPoint( "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - # something we can load()/require() + + # something we can load()/require(), TODO: use mock self.ep_sa = pkg_resources.EntryPoint( "sa", "letsencrypt.client.plugins.standalone.authenticator", attrs=('StandaloneAuthenticator',), @@ -30,26 +33,6 @@ class PluginEntryPointTest(unittest.TestCase): from letsencrypt.client.plugins.disco import PluginEntryPoint self.plugin_ep = PluginEntryPoint(self.ep_sa) - def test__init__(self): - self.assertFalse(self.plugin_ep.initialized) - self.assertTrue(self.plugin_ep.entry_point is self.ep_sa) - self.assertEqual("sa", self.plugin_ep.name) - - self.assertTrue( - self.plugin_ep.plugin_cls is authenticator.StandaloneAuthenticator) - - def test_init(self): - config = mock.MagicMock() - plugin = self.plugin_ep.init(config=config) - self.assertTrue(self.plugin_ep.initialized) - self.assertTrue(plugin.config is config) - # memoize! - self.assertTrue(self.plugin_ep.init() is plugin) - self.assertTrue(plugin.config is config) - # try to give different config - self.assertTrue(self.plugin_ep.init(123) is plugin) - self.assertTrue(plugin.config is config) - def test_entry_point_to_plugin_name(self): from letsencrypt.client.plugins.disco import PluginEntryPoint @@ -69,6 +52,81 @@ class PluginEntryPointTest(unittest.TestCase): self.assertTrue( self.plugin_ep.name_with_description.startswith("sa (")) + def test__init__(self): + self.assertFalse(self.plugin_ep.initialized) + self.assertFalse(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + self.assertTrue(self.plugin_ep.entry_point is self.ep_sa) + self.assertEqual("sa", self.plugin_ep.name) + + self.assertTrue( + self.plugin_ep.plugin_cls is authenticator.StandaloneAuthenticator) + + def test_init(self): + config = mock.MagicMock() + plugin = self.plugin_ep.init(config=config) + self.assertTrue(self.plugin_ep.initialized) + self.assertTrue(plugin.config is config) + # memoize! + self.assertTrue(self.plugin_ep.init() is plugin) + self.assertTrue(plugin.config is config) + # try to give different config + self.assertTrue(self.plugin_ep.init(123) is plugin) + self.assertTrue(plugin.config is config) + + self.assertFalse(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + + def test_verify(self): + i1 = mock.MagicMock(__name__="i1") + i2 = mock.MagicMock(__name__="i2") + i3 = mock.MagicMock(__name__="i3") + self.plugin_ep._initialized = plugin = mock.MagicMock() + + exceptions = zope.interface.exceptions + with mock.patch("letsencrypt.client.plugins.disco.zope.interface") as mock_zope: + mock_zope.exceptions = exceptions + def verify_object(iface, obj): + assert obj is plugin + assert iface is i1 or iface is i2 or iface is i3 + if iface is i3: + raise mock_zope.exceptions.BrokenImplementation(None, None) + mock_zope.verify.verifyObject.side_effect = verify_object + self.assertTrue(self.plugin_ep.verify((i1,))) + self.assertTrue(self.plugin_ep.verify((i1, i2))) + self.assertFalse(self.plugin_ep.verify((i3,))) + self.assertFalse(self.plugin_ep.verify((i1, i3))) + + def test_prepare(self): + config = mock.MagicMock() + self.plugin_ep.init(config=config) + self.plugin_ep.prepare() + self.assertTrue(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + str(self.plugin_ep) # output doesn't matter that much, just jest if it runs + + def test_prepare_misconfigured(self): + plugin = mock.MagicMock() + plugin.prepare.side_effect = errors.LetsEncryptMisconfigurationError + self.plugin_ep._initialized = plugin + self.assertTrue(isinstance(self.plugin_ep.prepare(), + errors.LetsEncryptMisconfigurationError)) + self.assertTrue(self.plugin_ep.prepared) + self.assertTrue(self.plugin_ep.misconfigured) + self.assertTrue(self.plugin_ep.available) + + def test_prepare_no_installation(self): + plugin = mock.MagicMock() + plugin.prepare.side_effect = errors.LetsEncryptNoInstallationError + self.plugin_ep._initialized = plugin + self.assertTrue(isinstance(self.plugin_ep.prepare(), + errors.LetsEncryptNoInstallationError)) + self.assertTrue(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + def test_repr(self): self.assertEqual("PluginEntryPoint#sa", repr(self.plugin_ep)) From 138a9e9f0161e4a771929adc2aa134bb7dab3626 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 14:27:22 +0000 Subject: [PATCH 26/76] Full coverage and lint for disco/disco_test --- letsencrypt/client/cli.py | 2 +- letsencrypt/client/display/ops.py | 2 +- letsencrypt/client/plugins/disco.py | 41 ++++--- letsencrypt/client/plugins/disco_test.py | 133 ++++++++++++++++++----- 4 files changed, 130 insertions(+), 48 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index e77bef92d..d17b2b459 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -176,7 +176,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print logging.debug("Discovered plugins: %s", plugins) ifaces = [] if args.ifaces is None else args.ifaces - filtered = plugins.filter_ifaces(*((iface,) for iface in ifaces)) + filtered = plugins.ifaces(*((iface,) for iface in ifaces)) logging.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 8ca534883..24b9dc1d5 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -43,7 +43,7 @@ def _pick_plugin(config, default, plugins, question, ifaces): # throw more UX-friendly error if default not in plugins filtered = plugins.filter(lambda p_ep: p_ep.name == default) else: - filtered = plugins.filter_ifaces(ifaces) + filtered = plugins.ifaces(ifaces) filtered.init(config) verified = filtered.verify(ifaces) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index 68cb230d0..b2ceeb767 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -35,6 +35,13 @@ class PluginEntryPoint(object): """Name with description. Handy for UI.""" return "{0} ({1})".format(self.name, self.plugin_cls.description) + def ifaces(self, *ifaces_groups): + """Does plugin implements specified interface groups?""" + return not ifaces_groups or any( + all(iface.implementedBy(self.plugin_cls) + for iface in ifaces) + for ifaces in ifaces_groups) + @property def initialized(self): """Has the plugin been initialized already?""" @@ -104,7 +111,7 @@ class PluginEntryPoint(object): "Description: {0}".format(self.plugin_cls.description), "Interfaces: {0}".format(", ".join( iface.__name__ for iface in zope.interface.implementedBy( - self.plugin_cls))), + self.plugin_cls))), "Entry point: {0}".format(self.entry_point), ] @@ -131,13 +138,23 @@ class PluginsRegistry(collections.Mapping): plugin_ep = PluginEntryPoint(entry_point) assert plugin_ep.name not in plugins, ( "PREFIX_FREE_DISTRIBUTIONS messed up") + # providedBy | pylint: disable=no-member if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): plugins[plugin_ep.name] = plugin_ep - else: + else: # pragma: no cover logging.warning("Plugin entry point %s does not provide " "IPluginFactory, skipping", plugin_ep) return cls(plugins) + def __getitem__(self, name): + return self.plugins[name] + + def __iter__(self): + return iter(self.plugins) + + def __len__(self): + return len(self.plugins) + def init(self, config): """Initialize all plugins in the registry.""" return [plugin_ep.init(config) for plugin_ep @@ -148,18 +165,17 @@ class PluginsRegistry(collections.Mapping): return type(self)(dict((name, plugin_ep) for name, plugin_ep in self.plugins.iteritems() if pred(plugin_ep))) - def filter_ifaces(self, *ifaces_groups): + def ifaces(self, *ifaces_groups): """Filter plugins based on interfaces.""" - return self.filter(lambda plugin_ep: not ifaces_groups or any( - all(iface.implementedBy(plugin_ep.plugin_cls) - for iface in ifaces) - for ifaces in ifaces_groups)) + # pylint: disable=star-args + return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) def verify(self, ifaces): """Filter plugins based on verification.""" return self.filter(lambda p_ep: p_ep.verify(ifaces)) def prepare(self): + """Prepare all plugins in the registry.""" return [plugin_ep.prepare() for plugin_ep in self.plugins.itervalues()] def available(self): @@ -171,16 +187,7 @@ class PluginsRegistry(collections.Mapping): return "{0}({1!r})".format( self.__class__.__name__, set(self.plugins.itervalues())) - def __getitem__(self, name): - return self.plugins[name] - - def __iter__(self): - return iter(self.plugins) - - def __len__(self): - return len(self.plugins) - def __str__(self): if not self.plugins: return "No plugins" - return "\n\n".join(map(str, self.plugins.itervalues())) + return "\n\n".join(str(p_ep) for p_ep in self.plugins.itervalues()) diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index 54f77c941..ac80fae71 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -6,8 +6,15 @@ import mock import zope.interface from letsencrypt.client import errors +from letsencrypt.client import interfaces + from letsencrypt.client.plugins.standalone import authenticator +EP_SA = pkg_resources.EntryPoint( + "sa", "letsencrypt.client.plugins.standalone.authenticator", + attrs=("StandaloneAuthenticator",), + dist=mock.MagicMock(key="letsencrypt")) + class PluginEntryPointTest(unittest.TestCase): """Tests for letsencrypt.client.plugins.disco.PluginEntryPoint.""" @@ -24,14 +31,8 @@ class PluginEntryPointTest(unittest.TestCase): self.ep3 = pkg_resources.EntryPoint( "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - # something we can load()/require(), TODO: use mock - self.ep_sa = pkg_resources.EntryPoint( - "sa", "letsencrypt.client.plugins.standalone.authenticator", - attrs=('StandaloneAuthenticator',), - dist=mock.MagicMock(key="letsencrypt")) - from letsencrypt.client.plugins.disco import PluginEntryPoint - self.plugin_ep = PluginEntryPoint(self.ep_sa) + self.plugin_ep = PluginEntryPoint(EP_SA) def test_entry_point_to_plugin_name(self): from letsencrypt.client.plugins.disco import PluginEntryPoint @@ -41,7 +42,7 @@ class PluginEntryPointTest(unittest.TestCase): self.ep1prim: "p2:ep1", self.ep2: "p2:ep2", self.ep3: "p3:ep3", - self.ep_sa: "sa", + EP_SA: "sa", } for entry_point, name in names.iteritems(): @@ -52,12 +53,18 @@ class PluginEntryPointTest(unittest.TestCase): self.assertTrue( self.plugin_ep.name_with_description.startswith("sa (")) + def test_ifaces(self): + self.assertTrue(self.plugin_ep.ifaces((interfaces.IAuthenticator,))) + self.assertFalse(self.plugin_ep.ifaces((interfaces.IInstaller,))) + self.assertFalse(self.plugin_ep.ifaces(( + interfaces.IInstaller, interfaces.IAuthenticator))) + def test__init__(self): self.assertFalse(self.plugin_ep.initialized) self.assertFalse(self.plugin_ep.prepared) self.assertFalse(self.plugin_ep.misconfigured) self.assertFalse(self.plugin_ep.available) - self.assertTrue(self.plugin_ep.entry_point is self.ep_sa) + self.assertTrue(self.plugin_ep.entry_point is EP_SA) self.assertEqual("sa", self.plugin_ep.name) self.assertTrue( @@ -80,24 +87,26 @@ class PluginEntryPointTest(unittest.TestCase): self.assertFalse(self.plugin_ep.available) def test_verify(self): - i1 = mock.MagicMock(__name__="i1") - i2 = mock.MagicMock(__name__="i2") - i3 = mock.MagicMock(__name__="i3") + iface1 = mock.MagicMock(__name__="iface1") + iface2 = mock.MagicMock(__name__="iface2") + iface3 = mock.MagicMock(__name__="iface3") + # pylint: disable=protected-access self.plugin_ep._initialized = plugin = mock.MagicMock() exceptions = zope.interface.exceptions - with mock.patch("letsencrypt.client.plugins.disco.zope.interface") as mock_zope: + with mock.patch("letsencrypt.client.plugins." + "disco.zope.interface") as mock_zope: mock_zope.exceptions = exceptions - def verify_object(iface, obj): + def verify_object(iface, obj): # pylint: disable=missing-docstring assert obj is plugin - assert iface is i1 or iface is i2 or iface is i3 - if iface is i3: + assert iface is iface1 or iface is iface2 or iface is iface3 + if iface is iface3: raise mock_zope.exceptions.BrokenImplementation(None, None) mock_zope.verify.verifyObject.side_effect = verify_object - self.assertTrue(self.plugin_ep.verify((i1,))) - self.assertTrue(self.plugin_ep.verify((i1, i2))) - self.assertFalse(self.plugin_ep.verify((i3,))) - self.assertFalse(self.plugin_ep.verify((i1, i3))) + self.assertTrue(self.plugin_ep.verify((iface1,))) + self.assertTrue(self.plugin_ep.verify((iface1, iface2))) + self.assertFalse(self.plugin_ep.verify((iface3,))) + self.assertFalse(self.plugin_ep.verify((iface1, iface3))) def test_prepare(self): config = mock.MagicMock() @@ -105,11 +114,14 @@ class PluginEntryPointTest(unittest.TestCase): self.plugin_ep.prepare() self.assertTrue(self.plugin_ep.prepared) self.assertFalse(self.plugin_ep.misconfigured) - str(self.plugin_ep) # output doesn't matter that much, just jest if it runs + + # output doesn't matter that much, just test if it runs + str(self.plugin_ep) def test_prepare_misconfigured(self): plugin = mock.MagicMock() plugin.prepare.side_effect = errors.LetsEncryptMisconfigurationError + # pylint: disable=protected-access self.plugin_ep._initialized = plugin self.assertTrue(isinstance(self.plugin_ep.prepare(), errors.LetsEncryptMisconfigurationError)) @@ -120,6 +132,7 @@ class PluginEntryPointTest(unittest.TestCase): def test_prepare_no_installation(self): plugin = mock.MagicMock() plugin.prepare.side_effect = errors.LetsEncryptNoInstallationError + # pylint: disable=protected-access self.plugin_ep._initialized = plugin self.assertTrue(isinstance(self.plugin_ep.prepare(), errors.LetsEncryptNoInstallationError)) @@ -136,19 +149,81 @@ class PluginsRegistryTest(unittest.TestCase): def setUp(self): from letsencrypt.client.plugins.disco import PluginsRegistry - # TODO: mock out pkg_resources.iter_entry_points - self.plugins = PluginsRegistry.find_all() + self.plugin_ep = mock.MagicMock(name="mock") + self.plugins = {"mock": self.plugin_ep} + self.reg = PluginsRegistry(self.plugins) + + def test_find_all(self): + from letsencrypt.client.plugins.disco import PluginsRegistry + with mock.patch("letsencrypt.client.plugins.disco" + ".pkg_resources") as mock_pkg: + mock_pkg.iter_entry_points.return_value = iter([EP_SA]) + plugins = PluginsRegistry.find_all() + self.assertTrue(plugins["sa"].plugin_cls + is authenticator.StandaloneAuthenticator) + self.assertTrue(plugins["sa"].entry_point is EP_SA) + + def test_getitem(self): + self.assertEqual(self.plugin_ep, self.reg["mock"]) + + def test_iter(self): + self.assertEqual(["mock"], list(self.reg)) + + def test_len(self): + self.assertEqual(1, len(self.reg)) + self.plugins.clear() + self.assertEqual(0, len(self.reg)) def test_init(self): - self.assertTrue(self.plugins["standalone"].plugin_cls - is authenticator.StandaloneAuthenticator) + self.plugin_ep.init.return_value = "baz" + self.assertEqual(["baz"], self.reg.init("bar")) + self.plugin_ep.init.assert_called_once_with("bar") - def test_id_filter(self): - filtered = self.plugins.filter(lambda _: True) - self.assertEqual(len(self.plugins), len(filtered)) + def test_filter(self): + self.plugins.update({ + "foo": "bar", + "bar": "foo", + "baz": "boo", + }) + self.assertEqual( + {"foo": "bar", "baz": "boo"}, + self.reg.filter(lambda p_ep: str(p_ep).startswith("b"))) + + def test_ifaces(self): + self.plugin_ep.ifaces.return_value = True + self.assertEqual(self.plugins, self.reg.ifaces().plugins) + self.plugin_ep.ifaces.return_value = False + self.assertEqual({}, self.reg.ifaces().plugins) + + def test_verify(self): + self.plugin_ep.verify.return_value = True + self.assertEqual( + self.plugins, self.reg.verify(mock.MagicMock()).plugins) + self.plugin_ep.verify.return_value = False + self.assertEqual({}, self.reg.verify(mock.MagicMock()).plugins) + + def test_prepare(self): + self.plugin_ep.prepare.return_value = "baz" + self.assertEqual(["baz"], self.reg.prepare()) + self.plugin_ep.prepare.assert_called_once_with() + + def test_available(self): + self.plugin_ep.available = True + self.assertEqual(self.plugins, self.reg.available().plugins) + self.plugin_ep.available = False + self.assertEqual({}, self.reg.available().plugins) def test_repr(self): - repr(self.plugins) + self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock" + self.assertEqual("PluginsRegistry(set([PluginEntryPoint#mock]))", + repr(self.reg)) + + def test_str(self): + self.plugin_ep.__str__ = lambda _: "Mock" + self.plugins["foo"] = "Mock" + self.assertEqual("Mock\n\nMock", str(self.reg)) + self.plugins.clear() + self.assertEqual("No plugins", str(self.reg)) if __name__ == "__main__": From 0216ea3f2684ab3622f7954845230a008f2c356d Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 19:44:53 +0000 Subject: [PATCH 27/76] boulder#130 fixed --- letsencrypt/client/network2.py | 6 +----- letsencrypt/client/tests/network2_test.py | 7 ++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/letsencrypt/client/network2.py b/letsencrypt/client/network2.py index 16ab80f3b..eaa485a8d 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/client/network2.py @@ -315,11 +315,7 @@ class Network(object): try: authzr_uri = response.links['up']['url'] except KeyError: - # TODO: Right now Boulder responds with the authorization resource - # instead of a challenge resource... this can be uncommented - # once the error is fixed (boulder#130). - return None - # raise errors.NetworkError('"up" Link header missing') + raise errors.NetworkError('"up" Link header missing') challr = messages2.ChallengeResource( authzr_uri=authzr_uri, body=messages2.ChallengeBody.from_json(response.json())) diff --git a/letsencrypt/client/tests/network2_test.py b/letsencrypt/client/tests/network2_test.py index 195788d66..d14d27f6a 100644 --- a/letsencrypt/client/tests/network2_test.py +++ b/letsencrypt/client/tests/network2_test.py @@ -286,11 +286,8 @@ class NetworkTest(unittest.TestCase): def test_answer_challenge_missing_next(self): self._mock_post_get() - self.assertTrue(self.net.answer_challenge( - self.challr.body, challenges.DNSResponse()) is None) - # TODO: boulder#130, acme-spec#110 - # self.assertRaises(errors.NetworkError, self.net.answer_challenge, - # self.challr.body, challenges.DNSResponse()) + self.assertRaises(errors.NetworkError, self.net.answer_challenge, + self.challr.body, challenges.DNSResponse()) def test_retry_after_date(self): self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' From b4f99df7987a999a59ccafef63dd7d02a4287305 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 3 May 2015 22:15:30 +0000 Subject: [PATCH 28/76] Full coverage and lint for display.ops --- letsencrypt/client/display/ops.py | 41 +++++-- letsencrypt/client/tests/display/ops_test.py | 119 +++++++++++++++++++ 2 files changed, 149 insertions(+), 11 deletions(-) diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 24b9dc1d5..5a77e0ffb 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -15,30 +15,49 @@ util = zope.component.getUtility # pylint: disable=invalid-name def choose_plugin(prepared, question): """Allow the user to choose ther plugin. - :param list prepared: + :param list prepared: List of `~.PluginEntryPoint`. + :param str question: Question to be presented to the user. + + :returns: Plugin entry point chosen by the user. + :rtype: `~.PluginEntryPoint` """ opts = [plugin_ep.name_with_description + (" [Misconfigured]" if plugin_ep.misconfigured else "") - for plugin_ep in prepared.itervalues()] + for plugin_ep in prepared] while True: code, index = util(interfaces.IDisplay).menu( question, opts, help_label="More Info") if code == display_util.OK: - return prepared[index][0] + return prepared[index] elif code == display_util.HELP: - if prepared[index][1] is not None: + if prepared[index].misconfigured: msg = "Reported Error: %s" % prepared[index].prepare() else: - msg = prepared[index][0].init().more_info() + msg = prepared[index].init().more_info() util(interfaces.IDisplay).notification( msg, height=display_util.HEIGHT) else: return None -def _pick_plugin(config, default, plugins, question, ifaces): + +def pick_plugin(config, default, plugins, question, ifaces): + """Pick plugin. + + :param letsencrypt.client.interfaces.IConfig: Configuration + :param str default: Plugin name supplied by user or ``None``. + :param letsencrypt.client.plugins.disco.PluginsRegistry plugins: + All plugins registered as entry points. + :param str question: Question to be presented to the user in case + multiple candidates are found. + :param list ifaces: Interfaces that plugins must provide. + + :returns: Initialized plugin. + :rtype: IPlugin + + """ if default is not None: # throw more UX-friendly error if default not in plugins filtered = plugins.filter(lambda p_ep: p_ep.name == default) @@ -47,8 +66,8 @@ def _pick_plugin(config, default, plugins, question, ifaces): filtered.init(config) verified = filtered.verify(ifaces) - filtered.prepare() - prepared = filtered.available() + verified.prepare() + prepared = verified.available() if len(prepared) > 1: logging.debug("Multiple candidate plugins: %s", prepared) @@ -66,14 +85,14 @@ def pick_authenticator( config, default, plugins, question="How would you " "like to authenticate with Let's Encrypt CA?"): """Pick authentication plugin.""" - return _pick_plugin( + return pick_plugin( config, default, plugins, question, (interfaces.IAuthenticator,)) def pick_installer(config, default, plugins, question="How would you like to install certificates?"): """Pick installer plugin.""" - return _pick_plugin( + return pick_plugin( config, default, plugins, question, (interfaces.IInstaller,)) @@ -82,7 +101,7 @@ def pick_configurator( question="How would you like to authenticate and install " "certificates?"): """Pick configurator plugin.""" - return _pick_plugin( + return pick_plugin( config, default, plugins, question, (interfaces.IAuthenticator, interfaces.IInstaller)) diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/client/tests/display/ops_test.py index 2da411b6b..151358f8a 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/client/tests/display/ops_test.py @@ -8,10 +8,129 @@ import mock import zope.component from letsencrypt.client import account +from letsencrypt.client import interfaces from letsencrypt.client import le_util + from letsencrypt.client.display import util as display_util +class ChoosePluginTest(unittest.TestCase): + """Tests for letsencrypt.client.display.ops.choose_plugin.""" + + def setUp(self): + zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) + self.mock_apache = mock.Mock( + name_with_description="a", misconfigured=True) + self.mock_stand = mock.Mock( + name_with_description="s", misconfigured=False) + self.mock_stand.init().more_info.return_value = "standalone" + self.plugins = [ + self.mock_apache, + self.mock_stand, + ] + + def _call(self): + from letsencrypt.client.display.ops import choose_plugin + return choose_plugin(self.plugins, "Question?") + + @mock.patch("letsencrypt.client.display.ops.util") + def test_successful_choice(self, mock_util): + mock_util().menu.return_value = (display_util.OK, 0) + self.assertEqual(self.mock_apache, self._call()) + + @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), + ] + + self.assertEqual(self.mock_stand, self._call()) + self.assertEqual(mock_util().notification.call_count, 2) + + @mock.patch("letsencrypt.client.display.ops.util") + def test_no_choice(self, mock_util): + mock_util().menu.return_value = (display_util.CANCEL, 0) + self.assertTrue(self._call() is None) + + +class PickPluginTest(unittest.TestCase): + """Tests for letsencrypt.client.display.ops.pick_plugin.""" + + def setUp(self): + self.config = mock.Mock() + self.default = None + self.reg = mock.MagicMock() + self.question = "Question?" + self.ifaces = [] + + def _call(self): + from letsencrypt.client.display.ops import pick_plugin + return pick_plugin(self.config, self.default, self.reg, + self.question, self.ifaces) + + def test_default_provided(self): + self.default = "foo" + self._call() + self.reg.filter.assert_called_once() + + def test_no_default(self): + self._call() + self.reg.filter.assert_called_once() + + def test_no_candidate(self): + self.assertTrue(self._call() is None) + + def test_single(self): + plugin_ep = mock.MagicMock() + plugin_ep.init.return_value = "foo" + self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep} + self.assertEqual("foo", self._call()) + + def test_multiple(self): + plugin_ep = mock.MagicMock() + plugin_ep.init.return_value = "foo" + self.reg.ifaces().verify().available.return_value = { + "bar": plugin_ep, + "baz": plugin_ep, + } + with mock.patch("letsencrypt.client.display" + ".ops.choose_plugin") as mock_choose: + mock_choose.return_value = plugin_ep + self.assertEqual("foo", self._call()) + mock_choose.assert_called_once_with( + [plugin_ep, plugin_ep], self.question) + + +class ConveniencePickPluginTest(unittest.TestCase): + """Tests for letsencrypt.client.display.ops.pick_*.""" + + def _test(self, fun, ifaces): + config = mock.Mock() + default = mock.Mock() + plugins = mock.Mock() + + with mock.patch("letsencrypt.client.display.ops.pick_plugin") as mock_p: + mock_p.return_value = "foo" + self.assertEqual("foo", fun(config, default, plugins, "Question?")) + mock_p.assert_called_once_with( + config, default, plugins, "Question?", ifaces) + + def test_authenticator(self): + from letsencrypt.client.display.ops import pick_authenticator + self._test(pick_authenticator, (interfaces.IAuthenticator,)) + + def test_installer(self): + from letsencrypt.client.display.ops import pick_installer + self._test(pick_installer, (interfaces.IInstaller,)) + + def test_configurator(self): + from letsencrypt.client.display.ops import pick_configurator + self._test(pick_configurator, ( + interfaces.IAuthenticator, interfaces.IInstaller)) + + class ChooseAccountTest(unittest.TestCase): """Test choose_account.""" def setUp(self): From 9e7918fc75c665c86b3e4983b267d35bd1753b6c Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 08:10:52 +0000 Subject: [PATCH 29/76] Test CLI plugins command --- letsencrypt/client/tests/cli_test.py | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py index bb50715e5..0b90953b1 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/client/tests/cli_test.py @@ -1,11 +1,37 @@ +"""Tests for letsencrypt.client.cli.""" +import itertools +import sys import unittest +import mock +import zope.component + +from letsencrypt.client.display import util as display_util + class CLITest(unittest.TestCase): + """Tests for different commands.""" - def test_it(self): + def _call(self, args): from letsencrypt.client import cli - self.assertRaises(SystemExit, cli.main, ['--help']) + args = ['--text'] + args + with mock.patch("letsencrypt.client.cli.sys.stdout") as stdout: + with mock.patch("letsencrypt.client.cli.sys.stderr") as stderr: + ret = cli.main(args) + return ret, stdout, stderr + + def test_no_flags(self): + self.assertRaises(SystemExit, self._call, []) + + def test_help(self): + self.assertRaises(SystemExit, self._call, ['--help']) + + def test_plugins(self): + flags = ['--init', '--prepare', '--authenticators', '--installers'] + for args in itertools.chain(*(itertools.combinations(flags, r) + for r in xrange(len(flags)))): + print args + self._call(['plugins',] + list(args)) if __name__ == '__main__': From 8ae6a60fbaa13f99cb39a03dc3e86db0419c86af Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 08:26:08 +0000 Subject: [PATCH 30/76] pep8 --- letsencrypt/client/cli.py | 31 ++++++++----------- letsencrypt/client/client.py | 2 -- letsencrypt/client/constants.py | 1 - .../client/plugins/apache/configurator.py | 3 +- letsencrypt/client/tests/cli_test.py | 12 +++---- letsencrypt/client/tests/client_test.py | 1 - 6 files changed, 20 insertions(+), 30 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index d17b2b459..2035a00db 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -1,10 +1,8 @@ """Let's Encrypt CLI.""" # TODO: Sanity check all input. Be sure to avoid shell code etc... import argparse -import collections import logging import os -import pkg_resources import sys import configargparse @@ -28,9 +26,6 @@ from letsencrypt.client.display import ops as display_ops from letsencrypt.client.plugins import disco as plugins_disco -from letsencrypt.client.plugins.apache import configurator as apache_configurator -from letsencrypt.client.plugins.nginx import configurator as nginx_configurator - def _account_init(args, config): le_util.make_or_verify_dir( @@ -72,7 +67,7 @@ def _common_run(args, config, acc, authenticator, installer): try: acme.register() except errors.LetsEncryptClientError: - return None + sys.exit("Unable to register an account with ACME server") return acme, doms @@ -125,7 +120,7 @@ def auth(args, config, plugins): installer = None acme, doms = _common_run( - args, config, acc, authenticator=authenticator, installer=None) + args, config, acc, authenticator=authenticator, installer=installer) acme.obtain_certificate(doms) @@ -145,7 +140,7 @@ def install(args, config, plugins): acme.enhance_config(doms, args.redirect) -def revoke(args, config, plugins): +def revoke(args, unused_config, unused_plugins): """Revoke.""" if args.rev_cert is None and args.rev_key is None: return "At least one of --certificate or --key is required" @@ -162,13 +157,13 @@ def rollback(args, config, plugins): client.rollback(args.installer, args.checkpoints, config, plugins) -def config_changes(args, config, plugins): +def config_changes(unused_args, config, unused_plugins): """View config changes. View checkpoints and associated configuration changes. """ - client.config_changes(config) + client.view_config_changes(config) def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print @@ -234,7 +229,7 @@ def create_parser(plugins): # --help is automatically provided by argparse add("--version", action="version", version="%(prog)s {0}".format( - letsencrypt.__version__)) + letsencrypt.__version__)) add("-v", "--verbose", dest="verbose_count", action="count", default=flag_default("verbose_count")) add("--no-confirm", dest="no_confirm", action="store_true", @@ -245,18 +240,18 @@ def create_parser(plugins): help="Use the text output instead of the curses UI.") subparsers = parser.add_subparsers(metavar="SUBCOMMAND") - def add_subparser(name, func): + def add_subparser(name, func): # pylint: disable=missing-docstring subparser = subparsers.add_parser( name, help=func.__doc__.splitlines()[0], description=func.__doc__) subparser.set_defaults(func=func) return subparser - parser_run = add_subparser("run", run) - parser_auth = add_subparser("auth", auth) - parser_install = add_subparser("install", install) + add_subparser("run", run) + add_subparser("auth", auth) + add_subparser("install", install) parser_revoke = add_subparser("revoke", revoke) parser_rollback = add_subparser("rollback", rollback) - parrser_config_changes = add_subparser("config_changes", config_changes) + add_subparser("config_changes", config_changes) parser_plugins = add_subparser("plugins", plugins_cmd) parser_plugins.add_argument("--init", action="store_true") @@ -302,7 +297,7 @@ def create_parser(plugins): default=flag_default("rollback_checkpoints"), help="Revert configuration N number of checkpoints.") - paths_parser(parser.add_argument_group("paths")) + _paths_parser(parser.add_argument_group("paths")) # TODO: plugin_parser should be called for every detected plugin for name, plugin_ep in plugins.iteritems(): @@ -312,7 +307,7 @@ def create_parser(plugins): return parser -def paths_parser(parser): +def _paths_parser(parser): add = parser.add_argument add("--config-dir", default=flag_default("config_dir"), help=config_help("config_dir")) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 3f1c627e8..4fb02a74f 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -20,8 +20,6 @@ from letsencrypt.client import network2 from letsencrypt.client import reverter from letsencrypt.client import revoker -from letsencrypt.client.plugins.apache import configurator - from letsencrypt.client.display import ops as display_ops from letsencrypt.client.display import enhancements diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index d7ed55bb7..3f8cf4f05 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -1,6 +1,5 @@ """Let's Encrypt constants.""" import logging -import pkg_resources from letsencrypt.acme import challenges diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 5c56e0d21..fbbc0d579 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -96,7 +96,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): add("enmod", default=constants.DEFAULT_ENMOD, help="Path to the Apache 'a2enmod' binary.") add("init-script", default=constants.DEFAULT_INIT_SCRIPT, - help="Path to the Apache init script (used for server reload/restart).") + help="Path to the Apache init script (used for server " + "reload/restart).") def __init__(self, *args, **kwargs): """Initialize an Apache Configurator. diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py index 0b90953b1..271b9c5aa 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/client/tests/cli_test.py @@ -1,18 +1,15 @@ """Tests for letsencrypt.client.cli.""" import itertools -import sys import unittest import mock -import zope.component - -from letsencrypt.client.display import util as display_util class CLITest(unittest.TestCase): """Tests for different commands.""" - def _call(self, args): + @classmethod + def _call(cls, args): from letsencrypt.client import cli args = ['--text'] + args with mock.patch("letsencrypt.client.cli.sys.stdout") as stdout: @@ -28,8 +25,9 @@ class CLITest(unittest.TestCase): def test_plugins(self): flags = ['--init', '--prepare', '--authenticators', '--installers'] - for args in itertools.chain(*(itertools.combinations(flags, r) - for r in xrange(len(flags)))): + for args in itertools.chain( + *(itertools.combinations(flags, r) + for r in xrange(len(flags)))): print args self._call(['plugins',] + list(args)) diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index aff9b5c84..33530a083 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -8,7 +8,6 @@ import mock from letsencrypt.client import account from letsencrypt.client import configuration -from letsencrypt.client import errors from letsencrypt.client import le_util From a2df2455672d87c0d19a3274443c4fa9ee30b71b Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 11:55:17 +0000 Subject: [PATCH 31/76] Temporary fox for ConfigArgParse#17 --- Vagrantfile | 2 +- docs/contributing.rst | 4 ++-- docs/using.rst | 2 +- requirements.txt | 3 +++ tox.ini | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 requirements.txt diff --git a/Vagrantfile b/Vagrantfile index b4a06ea05..fa9195dd1 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -10,7 +10,7 @@ cd /vagrant sudo ./bootstrap/ubuntu.sh if [ ! -d "venv" ]; then virtualenv --no-site-packages -p python2 venv - ./venv/bin/python setup.py dev + ./venv/bin/pip install -r requirements.txt -e .[dev] fi SETUP_SCRIPT diff --git a/docs/contributing.rst b/docs/contributing.rst index 0ed022724..bfe5216ae 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -15,7 +15,7 @@ Now you can install the development packages: .. code-block:: shell - ./venv/bin/python setup.py dev + ./venv/bin/pip install -r requirements.txt -e .[dev] The code base, including your pull requests, **must** have 100% test statement coverage **and** be compliant with the :ref:`coding style @@ -48,7 +48,7 @@ synced to ``/vagrant``, so you can get started with: vagrant ssh cd /vagrant - ./venv/bin/python setup.py install + ./venv/bin/pip install -r requirements.txt sudo ./venv/bin/letsencrypt Support for other Linux distributions coming soon. diff --git a/docs/using.rst b/docs/using.rst index 3a7940993..f10966602 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -53,7 +53,7 @@ Installation .. code-block:: shell virtualenv --no-site-packages -p python2 venv - ./venv/bin/python setup.py install + ./venv/bin/pip install -r requirements.txt sudo ./venv/bin/letsencrypt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..d7174f103 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# https://github.com/bw2/ConfigArgParse/issues/17 +-e git+https://github.com/kuba/ConfigArgParse.git#egg=ConfigArgParse +-e . diff --git a/tox.ini b/tox.ini index 47b509203..cd6f3c7b0 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py26,py27,cover,lint [testenv] commands = - pip install -e .[testing] + pip install -r requirements.txt -e .[testing] python setup.py test -q # -q does not suppress errors setenv = From f38911eb10e026aca174fe70f956d0c0f361c076 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 12:04:27 +0000 Subject: [PATCH 32/76] Actually install ConfigArgParse@python2.6 branch --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d7174f103..0f0223dab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # https://github.com/bw2/ConfigArgParse/issues/17 --e git+https://github.com/kuba/ConfigArgParse.git#egg=ConfigArgParse +-e git+https://github.com/kuba/ConfigArgParse.git@python2.6#egg=ConfigArgParse -e . From 73ac2e36cce1b3a5db36aca8659b55e664f5c9d2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 12:46:24 +0000 Subject: [PATCH 33/76] Fix plugins command --- letsencrypt/client/cli.py | 6 ++++-- letsencrypt/client/tests/cli_test.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 2035a00db..7f54afebe 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -168,10 +168,10 @@ def config_changes(unused_args, config, unused_plugins): def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print """List plugins.""" - logging.debug("Discovered plugins: %s", plugins) + logging.debug("Expected interfaces: %s", args.ifaces) ifaces = [] if args.ifaces is None else args.ifaces - filtered = plugins.ifaces(*((iface,) for iface in ifaces)) + filtered = plugins.ifaces(ifaces) logging.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: @@ -352,6 +352,8 @@ def main(args=sys.argv[1:]): if args.use_curses: logger.addHandler(log.DialogHandler()) + logging.debug("Discovered plugins: %r", plugins) + if not os.geteuid() == 0: logging.warning( "Root (sudo) is required to run most of letsencrypt functionality.") diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py index 271b9c5aa..bd25a9792 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/client/tests/cli_test.py @@ -28,7 +28,6 @@ class CLITest(unittest.TestCase): for args in itertools.chain( *(itertools.combinations(flags, r) for r in xrange(len(flags)))): - print args self._call(['plugins',] + list(args)) From c185480ae9ab0dea35ca295b388a2728be9c97d5 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 14:02:03 +0000 Subject: [PATCH 34/76] setup.cfg aliases don't work with pip --- Vagrantfile | 2 +- docs/contributing.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index fa9195dd1..1d3b48f06 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -10,7 +10,7 @@ cd /vagrant sudo ./bootstrap/ubuntu.sh if [ ! -d "venv" ]; then virtualenv --no-site-packages -p python2 venv - ./venv/bin/pip install -r requirements.txt -e .[dev] + ./venv/bin/pip install -r requirements.txt -e .[dev,docs,testing] fi SETUP_SCRIPT diff --git a/docs/contributing.rst b/docs/contributing.rst index bfe5216ae..d5088705b 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -15,7 +15,7 @@ Now you can install the development packages: .. code-block:: shell - ./venv/bin/pip install -r requirements.txt -e .[dev] + ./venv/bin/pip install -r requirements.txt -e .[dev,docs,testing] The code base, including your pull requests, **must** have 100% test statement coverage **and** be compliant with the :ref:`coding style From aa6984e3103bb979fbe58e9c13519aeba4a4de34 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 14:29:32 +0000 Subject: [PATCH 35/76] Attempt at cleaning {cert,chain}_path mess --- letsencrypt/client/cli.py | 2 +- letsencrypt/client/client.py | 25 +++++++-------- letsencrypt/client/interfaces.py | 4 +-- .../client/plugins/apache/configurator.py | 31 ++++++++++--------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 7f54afebe..bf9a9ed19 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -135,7 +135,7 @@ def install(args, config, plugins): return "Installer could not be determined" acme, doms = _common_run( args, config, acc, authenticator=None, installer=installer) - assert args.cert_path is not None and args.chain_path is not None + assert args.cert_path is not None acme.deploy_certificate(doms, acc.key, args.cert_path, args.chain_path) acme.enhance_config(doms, args.redirect) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 4fb02a74f..12a652a7f 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -108,7 +108,7 @@ class Client(object): this CSR can be different than self.authkey :type csr: :class:`CSR` - :returns: cert_file, chain_file (paths to respective files) + :returns: cert_path, chain_path (paths to respective files) :rtype: `tuple` of `str` """ @@ -136,13 +136,13 @@ class Client(object): authzr) # Save Certificate - cert_file, chain_file = self.save_certificate( + cert_path, chain_path = self.save_certificate( certr, self.config.cert_path, self.config.chain_path) revoker.Revoker.store_cert_key( - cert_file, self.account.key.file, self.config) + cert_path, self.account.key.file, self.config) - return cert_file, chain_file + return cert_path, chain_path def save_certificate(self, certr, cert_path, chain_path): # pylint: disable=no-self-use @@ -154,7 +154,7 @@ class Client(object): :param str cert_path: Path to attempt to save the cert file :param str chain_path: Path to attempt to save the chain file - :returns: cert_file, chain_file (absolute paths to the actual files) + :returns: cert_path, chain_path (absolute paths to the actual files) :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files @@ -191,7 +191,7 @@ class Client(object): return os.path.abspath(act_cert_path), cert_chain_abspath - def deploy_certificate(self, domains, privkey, cert_file, chain_file=None): + def deploy_certificate(self, domains, privkey, cert_path, chain_path=None): """Install certificate :param list domains: list of domains to install the certificate @@ -199,8 +199,8 @@ class Client(object): :param privkey: private key for certificate :type privkey: :class:`letsencrypt.client.le_util.Key` - :param str cert_file: certificate file path - :param str chain_file: chain file path + :param str cert_path: certificate file path + :param str chain_path: chain file path """ if self.installer is None: @@ -208,13 +208,12 @@ class Client(object): "the certificate") raise errors.LetsEncryptClientError("No installer available") - chain = None if chain_file is None else os.path.abspath(chain_file) + chain_path = None if chain_path is None else os.path.abspath(chain_path) for dom in domains: - self.installer.deploy_cert(dom, - os.path.abspath(cert_file), - os.path.abspath(privkey.file), - chain) + self.installer.deploy_cert( + dom, os.path.abspath(cert_path), + os.path.abspath(privkey.file), chain_path) self.installer.save("Deployed Let's Encrypt Certificate") # sites may have been enabled / final cleanup diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 018462b3c..b005eb02d 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -175,8 +175,8 @@ class IConfig(zope.interface.Interface): le_vhost_ext = zope.interface.Attribute( "SSL vhost configuration extension.") - cert_path = zope.interface.Attribute("Let's Encrypt certificate file.") - chain_path = zope.interface.Attribute("Let's Encrypt chain file.") + cert_path = zope.interface.Attribute("Let's Encrypt certificate file path.") + chain_path = zope.interface.Attribute("Let's Encrypt chain file path.") class IInstaller(IPlugin): diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index fbbc0d579..3bc545475 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -147,7 +147,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): temp_install(self.conf('mod-ssl-conf')) - def deploy_cert(self, domain, cert, key, cert_chain=None): + def deploy_cert(self, domain, cert_path, key, chain_path=None): """Deploys certificate to specified virtual host. Currently tries to find the last directives to deploy the cert in @@ -163,25 +163,26 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): This shouldn't happen within letsencrypt though :param str domain: domain to deploy certificate - :param str cert: certificate filename + :param str cert_path: certificate filename :param str key: private key filename - :param str cert_chain: certificate chain filename + :param str chain_path: certificate chain filename """ vhost = self.choose_vhost(domain) + # TODO(jdkasten): vhost might be None path = {} - path["cert_file"] = self.parser.find_dir(parser.case_i( + path["cert_path"] = self.parser.find_dir(parser.case_i( "SSLCertificateFile"), None, vhost.path) path["cert_key"] = self.parser.find_dir(parser.case_i( "SSLCertificateKeyFile"), None, vhost.path) # Only include if a certificate chain is specified - if cert_chain is not None: - path["cert_chain"] = self.parser.find_dir( + if chain_path is not None: + path["chain_path"] = self.parser.find_dir( parser.case_i("SSLCertificateChainFile"), None, vhost.path) - if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0: + if not path["cert_path"] or not path["cert_key"]: # Throw some can't find all of the directives error" logging.warn( "Cannot find a cert or key directive in %s", vhost.path) @@ -191,22 +192,22 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): logging.info("Deploying Certificate to VirtualHost %s", vhost.filep) - self.aug.set(path["cert_file"][0], cert) + self.aug.set(path["cert_path"][0], cert_path) self.aug.set(path["cert_key"][0], key) - if cert_chain is not None: - if len(path["cert_chain"]) == 0: + if chain_path is not None: + if not path["chain_path"]: self.parser.add_dir( - vhost.path, "SSLCertificateChainFile", cert_chain) + vhost.path, "SSLCertificateChainFile", chain_path) else: - self.aug.set(path["cert_chain"][0], cert_chain) + self.aug.set(path["chain_path"][0], chain_path) self.save_notes += ("Changed vhost at %s with addresses of %s\n" % (vhost.filep, ", ".join(str(addr) for addr in vhost.addrs))) - self.save_notes += "\tSSLCertificateFile %s\n" % cert + self.save_notes += "\tSSLCertificateFile %s\n" % cert_path self.save_notes += "\tSSLCertificateKeyFile %s\n" % key - if cert_chain: - self.save_notes += "\tSSLCertificateChainFile %s\n" % cert_chain + if chain_path is not None: + self.save_notes += "\tSSLCertificateChainFile %s\n" % chain_path # Make sure vhost is enabled if not vhost.enabled: From 5863323eb05c34ef9ddfe2cf0a88c2943da3ede6 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 4 May 2015 14:30:02 +0000 Subject: [PATCH 36/76] PluginEntryPoint.description, show in CLI --- letsencrypt/client/cli.py | 3 ++- letsencrypt/client/plugins/disco.py | 7 ++++++- letsencrypt/client/plugins/disco_test.py | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index bf9a9ed19..f874f4d7d 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -302,7 +302,8 @@ def create_parser(plugins): # TODO: plugin_parser should be called for every detected plugin for name, plugin_ep in plugins.iteritems(): plugin_ep.plugin_cls.inject_parser_options( - parser.add_argument_group(name), name) + parser.add_argument_group( + name, description=plugin_ep.description), name) return parser diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index b2ceeb767..f4a9faecf 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -30,10 +30,15 @@ class PluginEntryPoint(object): return entry_point.name return entry_point.dist.key + ":" + entry_point.name + @property + def description(self): + """Description of the plugin.""" + return self.plugin_cls.description + @property def name_with_description(self): """Name with description. Handy for UI.""" - return "{0} ({1})".format(self.name, self.plugin_cls.description) + return "{0} ({1})".format(self.name, self.description) def ifaces(self, *ifaces_groups): """Does plugin implements specified interface groups?""" diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index ac80fae71..945f72d6b 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -49,6 +49,9 @@ class PluginEntryPointTest(unittest.TestCase): self.assertEqual( name, PluginEntryPoint.entry_point_to_plugin_name(entry_point)) + def test_description(self): + self.assertEqual("Standalone Authenticator", self.plugin_ep.description) + def test_name_with_description(self): self.assertTrue( self.plugin_ep.name_with_description.startswith("sa (")) From 3c645a991629c8c4bf5022a08112fd59c23c0742 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 5 May 2015 19:07:40 +0000 Subject: [PATCH 37/76] messages2.Error.__str__ (more readable exceptions) This partially fixes #349. --- letsencrypt/acme/messages2.py | 5 +++++ letsencrypt/acme/messages2_test.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/letsencrypt/acme/messages2.py b/letsencrypt/acme/messages2.py index 93f77a3e9..a2829ff57 100644 --- a/letsencrypt/acme/messages2.py +++ b/letsencrypt/acme/messages2.py @@ -46,6 +46,11 @@ class Error(jose.JSONObjectWithFields, Exception): """Hardcoded error description based on its type.""" return self.ERROR_TYPE_DESCRIPTIONS[self.typ] + def __str__(self): + if self.typ is not None: + return ' :: '.join([self.typ, self.description, self.detail]) + else: + return str(self.detail) class _Constant(jose.JSONDeSerializable): """ACME constant.""" diff --git a/letsencrypt/acme/messages2_test.py b/letsencrypt/acme/messages2_test.py index d45aa7f9e..9b114e861 100644 --- a/letsencrypt/acme/messages2_test.py +++ b/letsencrypt/acme/messages2_test.py @@ -50,6 +50,12 @@ class ErrorTest(unittest.TestCase): from letsencrypt.acme.messages2 import Error hash(Error.from_json(self.error.to_json())) + def test_str(self): + self.assertEqual( + 'malformed :: The request message was malformed :: foo', + str(self.error)) + self.assertEqual('foo', str(self.error.update(typ=None))) + class ConstantTest(unittest.TestCase): """Tests for letsencrypt.acme.messages2._Constant.""" From 6580d3a85bf566013286f9fd915d927d50da86c2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 5 May 2015 19:09:35 +0000 Subject: [PATCH 38/76] Add test for ChallengeBody proxy behaviour --- letsencrypt/acme/messages2_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/letsencrypt/acme/messages2_test.py b/letsencrypt/acme/messages2_test.py index 9b114e861..9e8ef33c8 100644 --- a/letsencrypt/acme/messages2_test.py +++ b/letsencrypt/acme/messages2_test.py @@ -169,6 +169,9 @@ class ChallengeBodyTest(unittest.TestCase): from letsencrypt.acme.messages2 import ChallengeBody hash(ChallengeBody.from_json(self.jobj_from)) + def test_proxy(self): + self.assertEqual('foo', self.challb.token) + class AuthorizationTest(unittest.TestCase): """Tests for letsencrypt.acme.messages2.Authorization.""" From 73679a4e8556c49093ff69291a758a23672d33ec Mon Sep 17 00:00:00 2001 From: James Kasten Date: Tue, 5 May 2015 14:15:48 -0700 Subject: [PATCH 39/76] generate a separate key for the certificate --- letsencrypt/client/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index a4e98fa41..0422563c3 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -99,9 +99,7 @@ class Client(object): :meth:`.register` must be called before :meth:`.obtain_certificate` - .. todo:: This function currently uses the account key for the cert. - This should be changed to an independent key once renewal is sorted - out. + .. todo:: This function does not currently handle csr correctly... :param set domains: domains to get a certificate @@ -127,8 +125,10 @@ class Client(object): # Create CSR from names if csr is None: + cert_key = crypto_util.init_save_key( + self.config.rsa_key_size, self.config.key_dir) csr = crypto_util.init_save_csr( - self.account.key, domains, self.config.cert_dir) + cert_key, domains, self.config.cert_dir) # Retrieve certificate certr = self.network.request_issuance( From 394e18c4c6ab0d34b6364b3a44e00dd3f065a37c Mon Sep 17 00:00:00 2001 From: James Kasten Date: Tue, 5 May 2015 14:43:00 -0700 Subject: [PATCH 40/76] use certificate_key throughout --- letsencrypt/client/client.py | 11 +++++------ letsencrypt/scripts/main.py | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 0422563c3..58cccc3af 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -124,11 +124,10 @@ class Client(object): authzr = self.auth_handler.get_authorizations(domains) # Create CSR from names - if csr is None: - cert_key = crypto_util.init_save_key( - self.config.rsa_key_size, self.config.key_dir) - csr = crypto_util.init_save_csr( - cert_key, domains, self.config.cert_dir) + cert_key = crypto_util.init_save_key( + self.config.rsa_key_size, self.config.key_dir) + csr = crypto_util.init_save_csr( + cert_key, domains, self.config.cert_dir) # Retrieve certificate certr = self.network.request_issuance( @@ -143,7 +142,7 @@ class Client(object): revoker.Revoker.store_cert_key( cert_file, self.account.key.file, self.config) - return cert_file, chain_file + return cert_key, cert_file, chain_file def save_certificate(self, certr, cert_path, chain_path): # pylint: disable=no-self-use diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index ae15f22dd..254df5bdd 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -244,9 +244,9 @@ def main(): # pylint: disable=too-many-branches, too-many-statements acme.register() except errors.LetsEncryptClientError: sys.exit(0) - cert_file, chain_file = acme.obtain_certificate(doms) + cert_key, cert_file, chain_file = acme.obtain_certificate(doms) if installer is not None and cert_file is not None: - acme.deploy_certificate(doms, acc.key, cert_file, chain_file) + acme.deploy_certificate(doms, cert_key, cert_file, chain_file) if installer is not None: acme.enhance_config(doms, args.redirect) From 94d8a1ca018464479f37c61c6b668439ed8507d0 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Tue, 5 May 2015 14:54:15 -0700 Subject: [PATCH 41/76] update documentation of obtain_certificate --- letsencrypt/client/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 58cccc3af..8518c56b9 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -107,8 +107,8 @@ class Client(object): this CSR can be different than self.authkey :type csr: :class:`CSR` - :returns: cert_file, chain_file (paths to respective files) - :rtype: `tuple` of `str` + :returns: cert_key, cert_path, chain_path + :rtype: `tuple` of (:class:`letsencrypt.client.le_util.Key`, str, str) """ if self.auth_handler is None: @@ -136,13 +136,13 @@ class Client(object): authzr) # Save Certificate - cert_file, chain_file = self.save_certificate( + cert_path, chain_path = self.save_certificate( certr, self.config.cert_path, self.config.chain_path) revoker.Revoker.store_cert_key( - cert_file, self.account.key.file, self.config) + cert_path, self.account.key.file, self.config) - return cert_key, cert_file, chain_file + return cert_key, cert_path, chain_path def save_certificate(self, certr, cert_path, chain_path): # pylint: disable=no-self-use From 23d27f4659c19aa66bdc1d3a05d090c3238064ed Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 28 Apr 2015 13:49:12 -0700 Subject: [PATCH 42/76] Bump min nginx version to 0.8.48 We are assuming that if a server_name isn't specified, it matches the empty string. Prior to 0.8.48, it would match the machine's hostname. --- letsencrypt/client/plugins/nginx/configurator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 5f49ca8ee..158feb32c 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -399,10 +399,11 @@ class NginxConfigurator(object): nginx_version = tuple([int(i) for i in version_matches[0].split(".")]) - # nginx < 0.8.21 doesn't use default_server - if nginx_version < (0, 8, 21): + # nginx < 0.8.48 uses machine hostname as default server_name instead of + # the empty string + if nginx_version < (0, 8, 48): raise errors.LetsEncryptConfiguratorError( - "Nginx version must be 0.8.21+") + "Nginx version must be 0.8.48+") return nginx_version From 6f4af62f61bfacb63d5b141a4f9cbc17729fdf17 Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 28 Apr 2015 18:38:28 -0700 Subject: [PATCH 43/76] Add dvsni tests --- letsencrypt/client/plugins/nginx/dvsni.py | 99 ++++++++++-- letsencrypt/client/plugins/nginx/obj.py | 14 +- letsencrypt/client/plugins/nginx/parser.py | 6 +- .../client/plugins/nginx/tests/dvsni_test.py | 148 ++++++++++++++---- .../client/plugins/nginx/tests/obj_test.py | 6 +- 5 files changed, 224 insertions(+), 49 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt/client/plugins/nginx/dvsni.py index 7233d7c62..0eab4d402 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt/client/plugins/nginx/dvsni.py @@ -1,7 +1,11 @@ """NginxDVSNI""" import logging +import os +from letsencrypt.client import errors from letsencrypt.client.plugins.apache.dvsni import ApacheDvsni +from letsencrypt.client.plugins.nginx import obj +from letsencrypt.client.plugins.nginx.nginxparser import dump class NginxDvsni(ApacheDvsni): @@ -29,35 +33,110 @@ class NginxDvsni(ApacheDvsni): """ def perform(self): - """Perform a DVSNI challenge on Nginx.""" + """Perform a DVSNI challenge on Nginx. + + :returns: list of :class:`letsencrypt.acme.challenges.DVSNIResponse` + :rtype: list + + """ if not self.achalls: return [] self.configurator.save() addresses = [] + default_addr = "443 default_server ssl" + for achall in self.achalls: vhost = self.configurator.choose_vhost(achall.domain) if vhost is None: logging.error( - "No nginx vhost exists with servername or alias of: %s", + "No nginx vhost exists with server_name or alias of: %s", achall.domain) logging.error("No default 443 nginx vhost exists") - logging.error("Please specify servernames in the Nginx config") + logging.error("Please specify server_names in the Nginx config") return None + + for addr in vhost.addrs: + if addr.default: + addresses.append([obj.Addr.fromstring(default_addr)]) + break else: addresses.append(list(vhost.addrs)) - responses = [] + # Create challenge certs + responses = [self._setup_challenge_cert(x) for x in self.achalls] - # Create all of the challenge certs - # for achall in self.achalls: - # responses.append(self._setup_challenge_cert(achall)) - - # Setup the configuration - # self._mod_config(addresses) + # Set up the configuration + self._mod_config(addresses) # Save reversible changes self.configurator.save("SNI Challenge", True) return responses + + def _mod_config(self, ll_addrs): + """Modifies Nginx config to include challenge server blocks. + + :param list ll_addrs: list of lists of + :class:`letsencrypt.client.plugins.apache.obj.Addr` to apply + + :raises errors.LetsEncryptMisconfigurationError: + Unable to find a suitable HTTP block to include DVSNI hosts. + + """ + # Add the 'include' statement for the challenges if it doesn't exist + # already in the main config + included = False + directive = ['include', self.challenge_conf] + root = self.configurator.parser.loc["root"] + main = self.configurator.parser.parsed[root] + for entry in main: + if entry[0] == ['http']: + body = entry[1] + if directive not in body: + body.append(directive) + included = True + break + if not included: + raise errors.LetsEncryptMisconfigurationError( + 'LetsEncrypt could not find an HTTP block to include DVSNI ' + 'challenges in %s.' % root) + + config = [] + for idx, addrs in enumerate(ll_addrs): + config.append(self._make_server_block(self.achalls[idx], addrs)) + + self.configurator.reverter.register_file_creation( + True, self.challenge_conf) + + with open(self.challenge_conf, "w") as new_conf: + dump(config, new_conf) + + def _make_server_block(self, achall, addrs): + """Creates a server block for a DVSNI challenge. + + :param achall: Annotated DVSNI challenge. + :type achall: :class:`letsencrypt.client.achallenges.DVSNI` + + :param list addrs: addresses of challenged domain + :class:`list` of type :class:`~nginx.obj.Addr` + + :returns: server block for the challenge host + :rtype: list + + """ + block = [] + for addr in addrs: + block.append(['listen', str(addr)]) + + block.append(['server_name', achall.nonce_domain]) + block.append(['include', self.configurator.parser.loc["ssl_options"]]) + block.append(['ssl_certificate', self.get_cert_file(achall)]) + block.append(['ssl_certificate_key', achall.key.file]) + + document_root = os.path.join( + self.configurator.config.config_dir, "dvsni_page") + block.append([['location', '/'], [['root', document_root]]]) + + return [['server'], block] diff --git a/letsencrypt/client/plugins/nginx/obj.py b/letsencrypt/client/plugins/nginx/obj.py index acaacb3b0..f4b2f0f57 100644 --- a/letsencrypt/client/plugins/nginx/obj.py +++ b/letsencrypt/client/plugins/nginx/obj.py @@ -67,12 +67,20 @@ class Addr(ApacheAddr): return cls(host, port, ssl, default) def __str__(self): + parts = '' if self.tup[0] and self.tup[1]: - return "%s:%s" % self.tup + parts = "%s:%s" % self.tup elif self.tup[0]: - return self.tup[0] + parts = self.tup[0] else: - return self.tup[1] + parts = self.tup[1] + + if self.default: + parts += ' default_server' + if self.ssl: + parts += ' ssl' + + return parts def __eq__(self, other): if isinstance(other, self.__class__): diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 55a0b01e8..8de705dde 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -33,6 +33,7 @@ class NginxParser(object): """Loads Nginx files into a parsed tree. """ + self.parsed = {} self._parse_recursively(self.loc["root"]) def _parse_recursively(self, filepath): @@ -252,8 +253,9 @@ class NginxParser(object): def add_server_directives(self, filename, names, directives, replace=False): - """Add or replace directives in server blocks whose server_name set - is 'names'. If replace is True, this raises a misconfiguration error + """Add or replace directives in server blocks identified by server_name. + + ..note :: If replace is True, this raises a misconfiguration error if the directive does not already exist. ..todo :: Doesn't match server blocks whose server_name directives are diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index bf66367e6..a44e0fcd6 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -6,13 +6,16 @@ import shutil import mock from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 from letsencrypt.client import achallenges +from letsencrypt.client import errors from letsencrypt.client import le_util +from letsencrypt.client.plugins.nginx.obj import Addr from letsencrypt.client.plugins.nginx.tests import util +from letsencrypt.client.tests import acme_util + class DvsniPerformTest(util.NginxTest): """Test the NginxDVSNI challenge.""" @@ -36,25 +39,29 @@ class DvsniPerformTest(util.NginxTest): self.achalls = [ achallenges.DVSNI( - challb=messages2.ChallengeBody( - chall=challenges.DVSNI( + challb=acme_util.chall_to_challb( + challenges.DVSNI( r="foo", - nonce="bar", - ), - uri="https://letsencrypt-ca.org/chall0_uri", - status=messages2.Status("pending"), - ), domain="www.example.com", key=auth_key), + nonce="bar" + ), "pending"), + domain="www.example.com", key=auth_key), achallenges.DVSNI( - challb=messages2.ChallengeBody( - chall=challenges.DVSNI( + challb=acme_util.chall_to_challb( + challenges.DVSNI( r="\xba\xa9\xda? Date: Tue, 5 May 2015 23:57:40 -0700 Subject: [PATCH 44/76] Address @kuba review comments --- letsencrypt/client/plugins/nginx/dvsni.py | 24 +++++++++---------- .../client/plugins/nginx/tests/dvsni_test.py | 7 ++---- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt/client/plugins/nginx/dvsni.py index 0eab4d402..30af2e7a1 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt/client/plugins/nginx/dvsni.py @@ -1,4 +1,5 @@ """NginxDVSNI""" +import itertools import logging import os @@ -103,9 +104,8 @@ class NginxDvsni(ApacheDvsni): 'LetsEncrypt could not find an HTTP block to include DVSNI ' 'challenges in %s.' % root) - config = [] - for idx, addrs in enumerate(ll_addrs): - config.append(self._make_server_block(self.achalls[idx], addrs)) + config = [self._make_server_block(pair[0], pair[1]) + for pair in itertools.izip(self.achalls, ll_addrs)] self.configurator.reverter.register_file_creation( True, self.challenge_conf) @@ -126,17 +126,15 @@ class NginxDvsni(ApacheDvsni): :rtype: list """ - block = [] - for addr in addrs: - block.append(['listen', str(addr)]) - - block.append(['server_name', achall.nonce_domain]) - block.append(['include', self.configurator.parser.loc["ssl_options"]]) - block.append(['ssl_certificate', self.get_cert_file(achall)]) - block.append(['ssl_certificate_key', achall.key.file]) - document_root = os.path.join( self.configurator.config.config_dir, "dvsni_page") - block.append([['location', '/'], [['root', document_root]]]) + + block = [['listen', str(addr)] for addr in addrs] + + block.extend([['server_name', achall.nonce_domain], + ['include', self.configurator.parser.loc["ssl_options"]], + ['ssl_certificate', self.get_cert_file(achall)], + ['ssl_certificate_key', achall.key.file], + [['location', '/'], [['root', document_root]]]]) return [['server'], block] diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index a44e0fcd6..15d7dbdb5 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -79,7 +79,7 @@ class DvsniPerformTest(util.NginxTest): def test_perform(self, mock_save): self.sni.add_chall(self.achalls[1]) responses = self.sni.perform() - self.assertEqual(None, responses) + self.assertTrue(responses is None) self.assertEqual(mock_save.call_count, 1) def test_perform0(self): @@ -153,10 +153,7 @@ class DvsniPerformTest(util.NginxTest): self.assertTrue(['include', self.sni.challenge_conf] in http[1]) vhosts = self.sni.configurator.parser.get_vhosts() - vhs = [] - for vhost in vhosts: - if vhost.filep == self.sni.challenge_conf: - vhs.append(vhost) + vhs = [vh for vh in vhosts if vh.filep == self.sni.challenge_conf] for vhost in vhs: if vhost.addrs == set(v_addr1): From 32b7705080dc74ed57cb1a0264ea6cecf49feeb3 Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Wed, 6 May 2015 17:24:34 +0100 Subject: [PATCH 45/76] Fix typo in authenticator description --- letsencrypt/client/plugins/standalone/authenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index 3912033e8..9a812bdc7 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -406,7 +406,7 @@ class StandaloneAuthenticator(object): 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" + "on port 443 and perform DVSNI challenges. Once a certificate " "is attained, it will be saved in the " "(TODO) current working directory.{0}{0}" "TCP port 443 must be available in order to use the " From 4dc566a871a26d7a7f61e0c807abb4dc9a514e8a Mon Sep 17 00:00:00 2001 From: yan Date: Thu, 7 May 2015 18:30:50 -0700 Subject: [PATCH 46/76] Update vhost object when making nginx SSL block --- .../client/plugins/nginx/configurator.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 158feb32c..ac6671178 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -20,6 +20,7 @@ from letsencrypt.client import reverter from letsencrypt.client.plugins.nginx import dvsni from letsencrypt.client.plugins.nginx import parser +from letsencrypt.client.plugins.nginx import obj class NginxConfigurator(object): @@ -170,7 +171,7 @@ class NginxConfigurator(object): if vhost is not None: if not vhost.ssl: - self._make_server_ssl(vhost.filep, vhost.names) + self._make_server_ssl(vhost) return vhost @@ -245,23 +246,28 @@ class NginxConfigurator(object): return all_names - def _make_server_ssl(self, filename, names): + def _make_server_ssl(self, vhost): """Makes a server SSL based on server_name and filename by adding a 'listen 443 ssl' directive to the server block. .. todo:: Maybe this should create a new block instead of modifying the existing one? - :param str filename: The absolute filename of the config file. - :param set names: The server names of the block to add SSL in + :param vhost: The vhost to add SSL to. + :type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` """ + ssl_block = [['listen', '443 ssl'], + ['ssl_certificate', + '/etc/ssl/certs/ssl-cert-snakeoil.pem'], + ['ssl_certificate_key', + '/etc/ssl/private/ssl-cert-snakeoil.key'], + ['include', self.parser.loc["ssl_options"]]] self.parser.add_server_directives( - filename, names, - [['listen', '443 ssl'], - ['ssl_certificate', '/etc/ssl/certs/ssl-cert-snakeoil.pem'], - ['ssl_certificate_key', '/etc/ssl/private/ssl-cert-snakeoil.key'], - ['include', self.parser.loc["ssl_options"]]]) + vhost.filep, vhost.names, ssl_block) + vhost.ssl = True + vhost.raw.extend(ssl_block) + vhost.addrs.add(obj.Addr('', '443', True, False)) def get_all_certs_keys(self): """Find all existing keys, certs from configuration. From fbab449694dad2dc5b546bf7bc867a79ea87a9a6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 May 2015 15:17:29 -0400 Subject: [PATCH 47/76] Finished basic POP challenge --- letsencrypt/client/continuity_auth.py | 2 +- letsencrypt/client/proof_of_possession.py | 88 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 letsencrypt/client/proof_of_possession.py diff --git a/letsencrypt/client/continuity_auth.py b/letsencrypt/client/continuity_auth.py index 063d3d408..2f01d901e 100644 --- a/letsencrypt/client/continuity_auth.py +++ b/letsencrypt/client/continuity_auth.py @@ -32,7 +32,7 @@ class ContinuityAuthenticator(object): def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" - return [challenges.RecoveryToken] + return [challenges.ProofOfPossession, challenges.RecoveryToken] def perform(self, achalls): """Perform client specific challenges for IAuthenticator""" diff --git a/letsencrypt/client/proof_of_possession.py b/letsencrypt/client/proof_of_possession.py new file mode 100644 index 000000000..2abed067d --- /dev/null +++ b/letsencrypt/client/proof_of_possession.py @@ -0,0 +1,88 @@ +"""Proof of Possession Identifier Validation Challenge. + +Based on draft-barnes-acme, section 6.5. + +""" +import M2Crypto +import os +import zope.component + +from letsencrypt.acme import challenges +from letsencrypt.acme import jose +from letsencrypt.acme import other +from letsencrypt.client import interfaces +from letsencrypt.client.display import util as display_util + + +class ProofOfPossession(object): + """Proof of Possession Identifier Validation Challenge. + + Based on draft-barnes-acme, section 6.5. + + """ + def __init__(self, certs_keys): + """Initializes the object with known certificates and keys. + + :param list certs_keys: tuples with form `[(cert, key, path)]`, where: + - `cert` - str path to certificate file + - `key` - str path to associated key file + - `path` - file path to configuration file + + """ + self.certs_keys = certs_keys + + def perform(self, achall): + """Perform the Proof of Possession Challenge. + + :param achall: Proof of Possession Challenge + :type achall: :class:`letsencrypt.client.achallenges.ProofOfPossession` + + :returns: Response or None/False if the challenge cannot be completed + :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse' + or False + + """ + if (not isinstance(achall.challb.hints.jwk, achall.challb.alg.kty) or + achall.challb.alg in [jose.HS256, jose.HS384, jose.HS512]): + return None + + for cert, prv_key, _ in self.certs_keys: + der_key = M2Crypto.X509.load_cert(cert).get_pubkey().as_der() + cert_key = challb.alg.kty.load(der_key) + if cert_key == challb.hints.jwk: + return _gen_response(achall, key) + + # Is there are different prompt we should give the user? + code, prv_key = zope.component.getUtility( + interfaces.IDsiplay).input( + "Path to private key for identifier: %s " % achall.domain) + if code != display_util.CANCEL: + return _gen_response(achall, prv_key) + + # If we get here, the key wasn't found + return False + + def _gen_response(self, challb, key_path): # pylint: disable=no-self-use + """Create the response to the Proof of Possession Challenge. + + :param challb: Proof of Possession Challenge + :type challb: :class:`letsencrypt.acme.challenges.ProofOfPossession` + + :param str key_path: Path to the private key corresponding to the + hinted to public key + + :returns: Response or None/False if the challenge cannot be completed + :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse' + or False + + """ + + if os.path.isfile(key_path): + with key as open(key_path, 'rb'): + try: + jwk = challb.alg.kty.load(key.read()) + except (IndexError, ValueError, TypeError): + return False + sig = other.Signature.from_msg(challb.nonce, jwk, alg=challb.alg) + return challenges.ProofOfPossessionResponse(nonce=challb.nonce, + signature=sig) From 1533eea35143d439442ec84473d0b3fbe78041b5 Mon Sep 17 00:00:00 2001 From: yan Date: Fri, 8 May 2015 12:34:48 -0700 Subject: [PATCH 48/76] Start nginx if it's not already running --- .../client/plugins/nginx/configurator.py | 17 ++++++++++++----- .../plugins/nginx/tests/configurator_test.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index ac6671178..3c00e5e50 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -545,11 +545,18 @@ def nginx_restart(nginx_ctl): stdout, stderr = proc.communicate() if proc.returncode != 0: - # Enter recovery routine... - logging.error("Nginx Restart Failed!") - logging.error(stdout) - logging.error(stderr) - return False + # Maybe Nginx isn't running + nginx_proc = subprocess.Popen([nginx_ctl], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = nginx_proc.communicate() + + if nginx_proc.returncode != 0: + # Enter recovery routine... + logging.error("Nginx Restart Failed!") + logging.error(stdout) + logging.error(stderr) + return False except (OSError, ValueError): logging.fatal( diff --git a/letsencrypt/client/plugins/nginx/tests/configurator_test.py b/letsencrypt/client/plugins/nginx/tests/configurator_test.py index cb5fef6bf..9399d42a6 100644 --- a/letsencrypt/client/plugins/nginx/tests/configurator_test.py +++ b/letsencrypt/client/plugins/nginx/tests/configurator_test.py @@ -259,6 +259,20 @@ class NginxConfiguratorTest(util.NginxTest): mocked.returncode = 0 self.assertTrue(self.config.restart()) + @mock.patch("letsencrypt.client.plugins.nginx.configurator." + "subprocess.Popen") + def test_nginx_restart_fail(self, mock_popen): + mocked = mock_popen() + mocked.communicate.return_value = ('', '') + mocked.returncode = 1 + self.assertFalse(self.config.restart()) + + @mock.patch("letsencrypt.client.plugins.nginx.configurator." + "subprocess.Popen") + def test_no_nginx_start(self, mock_popen): + mock_popen.side_effect = OSError("Can't find program") + self.assertRaises(SystemExit, self.config.restart) + @mock.patch("letsencrypt.client.plugins.nginx.configurator." "subprocess.Popen") def test_config_test(self, mock_popen): From f7116a738806e6dab3198b53535b56dd14a8933a Mon Sep 17 00:00:00 2001 From: yan Date: Fri, 8 May 2015 12:36:32 -0700 Subject: [PATCH 49/76] Reduce logging severity for unparseable config files --- letsencrypt/client/plugins/nginx/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 8de705dde..16480d75f 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -166,7 +166,7 @@ class NginxParser(object): except IOError: logging.warn("Could not open file: %s", item) except pyparsing.ParseException: - logging.warn("Could not parse file: %s", item) + logging.debug("Could not parse file: %s", item) return trees def _set_locations(self, ssl_options): From fce08ea30c0dc60cb49761a2b0108c6481f09daa Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 8 May 2015 21:32:13 +0000 Subject: [PATCH 50/76] Use CLI_DEFAULTS in plugins --- letsencrypt/client/plugins/apache/configurator.py | 10 +++++----- letsencrypt/client/plugins/apache/constants.py | 14 ++++++++------ letsencrypt/client/plugins/nginx/configurator.py | 6 +++--- letsencrypt/client/plugins/nginx/constants.py | 9 ++++++--- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 3bc545475..82d6f323c 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -86,16 +86,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): @classmethod def add_parser_arguments(cls, add): - add("server-root", default=constants.DEFAULT_SERVER_ROOT, + add("server-root", default=constants.CLI_DEFAULTS["server_root"], help="Apache server root directory.") - add("mod-ssl-conf", default=constants.DEFAULT_MOD_SSL_CONF, + add("mod-ssl-conf", default=constants.CLI_DEFAULTS["mod_ssl_conf"], help="Contains standard Apache SSL directives.") - add("ctl", default=constants.DEFAULT_CTL, + add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the 'apache2ctl' binary, used for 'configtest' and " "retrieving Apache2 version number.") - add("enmod", default=constants.DEFAULT_ENMOD, + add("enmod", default=constants.CLI_DEFAULTS["enmod"], help="Path to the Apache 'a2enmod' binary.") - add("init-script", default=constants.DEFAULT_INIT_SCRIPT, + add("init-script", default=constants.CLI_DEFAULTS["init_script"], help="Path to the Apache init script (used for server " "reload/restart).") diff --git a/letsencrypt/client/plugins/apache/constants.py b/letsencrypt/client/plugins/apache/constants.py index 63b3bd148..d9f2a0b9d 100644 --- a/letsencrypt/client/plugins/apache/constants.py +++ b/letsencrypt/client/plugins/apache/constants.py @@ -2,12 +2,14 @@ import pkg_resources -# CLI/IConfig defaults -DEFAULT_SERVER_ROOT = "/etc/apache2" -DEFAULT_MOD_SSL_CONF = "/etc/letsencrypt/options-ssl.conf" -DEFAULT_CTL = "apache2ctl" -DEFAULT_ENMOD = "a2enmod" -DEFAULT_INIT_SCRIPT = "/etc/init.d/apache2" +CLI_DEFAULTS = dict( + server_root="/etc/apache2", + mod_ssl_conf="/etc/letsencrypt/options-ssl.conf", + ctl="apache2ctl", + enmod="a2enmod", + init_script="/etc/init.d/apache2", +) +"""CLI defaults.""" MOD_SSL_CONF = pkg_resources.resource_filename( diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 660653efa..49d5a6dd0 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -55,11 +55,11 @@ class NginxConfigurator(common.Plugin): @classmethod def add_parser_arguments(cls, add): - add("server-root", default=constants.DEFAULT_SERVER_ROOT, + add("server-root", default=constants.CLI_DEFAULTS["server_root"], help="Nginx server root directory.") - add("mod-ssl-conf", default=constants.DEFAULT_MOD_SSL_CONF, + add("mod-ssl-conf", default=constants.CLI_DEFAULTS["mod_ssl_conf"], help="Contains standard nginx SSL directives.") - add("ctl", default=constants.DEFAULT_CTL, help="Path to the " + add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the " "'nginx' binary, used for 'configtest' and retrieving nginx " "version number.") diff --git a/letsencrypt/client/plugins/nginx/constants.py b/letsencrypt/client/plugins/nginx/constants.py index 51fff39e1..17d05f438 100644 --- a/letsencrypt/client/plugins/nginx/constants.py +++ b/letsencrypt/client/plugins/nginx/constants.py @@ -2,9 +2,12 @@ import pkg_resources -DEFAULT_SERVER_ROOT = "/etc/nginx" -DEFAULT_MOD_SSL_CONF = "/etc/letsencrypt/options-ssl-nginx.conf" -DEFAULT_CTL = "nginx" +CLI_DEFAULTS = dict( + server_root="/etc/nginx", + mod_ssl_conf="/etc/letsencrypt/options-ssl-nginx.conf", + ctl="nginx", +) +"""CLI defaults.""" MOD_SSL_CONF = pkg_resources.resource_filename( From dfb94613bf2dda3ef8577a167bea511d2fd9afc1 Mon Sep 17 00:00:00 2001 From: yan Date: Fri, 8 May 2015 17:17:24 -0700 Subject: [PATCH 51/76] Add nginx server block for target_name if one doesn't exist --- .../client/plugins/nginx/configurator.py | 8 ++- letsencrypt/client/plugins/nginx/dvsni.py | 3 +- letsencrypt/client/plugins/nginx/obj.py | 2 +- letsencrypt/client/plugins/nginx/parser.py | 56 +++++++++++-------- .../plugins/nginx/tests/configurator_test.py | 2 +- .../client/plugins/nginx/tests/dvsni_test.py | 31 +++++----- .../client/plugins/nginx/tests/parser_test.py | 10 ++++ 7 files changed, 68 insertions(+), 44 deletions(-) diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 3c00e5e50..88a440f21 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -159,8 +159,12 @@ class NginxConfigurator(object): matches = self._get_ranked_matches(target_name) if not matches: - # No matches at all :'( - pass + # No matches. Create a new vhost with this name in nginx.conf. + filep = self.parser.loc["root"] + new_block = [['server'], [['server_name', target_name]]] + self.parser.add_http_directives(filep, new_block) + vhost = obj.VirtualHost(filep, set([]), False, True, + set([target_name]), list(new_block[1])) elif matches[0]['rank'] in xrange(2, 6): # Wildcard match - need to find the longest one rank = matches[0]['rank'] diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt/client/plugins/nginx/dvsni.py index 30af2e7a1..0e4f125f6 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt/client/plugins/nginx/dvsni.py @@ -52,9 +52,8 @@ class NginxDvsni(ApacheDvsni): vhost = self.configurator.choose_vhost(achall.domain) if vhost is None: logging.error( - "No nginx vhost exists with server_name or alias of: %s", + "No nginx vhost exists with server_name matching: %s", achall.domain) - logging.error("No default 443 nginx vhost exists") logging.error("Please specify server_names in the Nginx config") return None diff --git a/letsencrypt/client/plugins/nginx/obj.py b/letsencrypt/client/plugins/nginx/obj.py index f4b2f0f57..b2db6522a 100644 --- a/letsencrypt/client/plugins/nginx/obj.py +++ b/letsencrypt/client/plugins/nginx/obj.py @@ -97,7 +97,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods :ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`) :ivar set names: Server names/aliases of vhost (:class:`list` of :class:`str`) - :ivar array raw: The raw form of the parsed server block + :ivar list raw: The raw form of the parsed server block :ivar bool ssl: SSLEngine on in vhost :ivar bool enabled: Virtual host is enabled diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 16480d75f..099a8e36d 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -253,7 +253,7 @@ class NginxParser(object): def add_server_directives(self, filename, names, directives, replace=False): - """Add or replace directives in server blocks identified by server_name. + """Add or replace directives in the first server block with names. ..note :: If replace is True, this raises a misconfiguration error if the directive does not already exist. @@ -267,14 +267,20 @@ class NginxParser(object): :param bool replace: Whether to only replace existing directives """ - if replace: - _do_for_subarray(self.parsed[filename], - lambda x: self._has_server_names(x, names), - lambda x: _replace_directives(x, directives)) - else: - _do_for_subarray(self.parsed[filename], - lambda x: self._has_server_names(x, names), - lambda x: x.extend(directives)) + _do_for_subarray(self.parsed[filename], + lambda x: self._has_server_names(x, names), + lambda x: _add_directives(x, directives, replace)) + + def add_http_directives(self, filename, directives): + """Adds directives to the first encountered HTTP block in filename. + + :param str filename: The absolute filename of the config file + :param list directives: The directives to add + + """ + _do_for_subarray(self.parsed[filename], + lambda x: x[0] == ['http'], + lambda x: _add_directives(x[1], [directives], False)) def get_all_certs_keys(self): """Gets all certs and keys in the nginx config. @@ -463,24 +469,28 @@ def _parse_server(server): return parsed_server -def _replace_directives(block, directives): - """Replaces directives in a block. If the directive doesn't exist in +def _add_directives(block, directives, replace=False): + """Adds or replaces directives in a block. If the directive doesn't exist in the entry already, raises a misconfiguration error. ..todo :: Find directives that are in included files. :param list block: The block to replace in :param list directives: The new directives. + """ - for directive in directives: - changed = False - if len(directive) == 0: - continue - for index, line in enumerate(block): - if len(line) > 0 and line[0] == directive[0]: - block[index] = directive - changed = True - if not changed: - raise errors.LetsEncryptMisconfigurationError( - 'LetsEncrypt expected directive for %s in the Nginx config ' - 'but did not find it.' % directive[0]) + if replace: + for directive in directives: + changed = False + if len(directive) == 0: + continue + for index, line in enumerate(block): + if len(line) > 0 and line[0] == directive[0]: + block[index] = directive + changed = True + if not changed: + raise errors.LetsEncryptMisconfigurationError( + 'LetsEncrypt expected directive for %s in the Nginx ' + 'config but did not find it.' % directive[0]) + else: + block.extend(directives) diff --git a/letsencrypt/client/plugins/nginx/tests/configurator_test.py b/letsencrypt/client/plugins/nginx/tests/configurator_test.py index 9399d42a6..a17fbb611 100644 --- a/letsencrypt/client/plugins/nginx/tests/configurator_test.py +++ b/letsencrypt/client/plugins/nginx/tests/configurator_test.py @@ -91,7 +91,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual(results[name], self.config.choose_vhost(name).names) for name in bad_results: - self.assertEqual(None, self.config.choose_vhost(name)) + self.assertEqual(set([name]), self.config.choose_vhost(name).names) def test_more_info(self): self.assertTrue('nginx.conf' in self.config.more_info()) diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index 15d7dbdb5..7505b3751 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -75,12 +75,12 @@ class DvsniPerformTest(util.NginxTest): self.assertEqual([0], self.sni.indices) @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "NginxConfigurator.save") - def test_perform(self, mock_save): + "NginxConfigurator.choose_vhost") + def test_perform(self, mock_choose): self.sni.add_chall(self.achalls[1]) - responses = self.sni.perform() - self.assertTrue(responses is None) - self.assertEqual(mock_save.call_count, 1) + mock_choose.return_value = None + result = self.sni.perform() + self.assertTrue(result is None) def test_perform0(self): responses = self.sni.perform() @@ -108,30 +108,31 @@ class DvsniPerformTest(util.NginxTest): self.assertTrue(['include', self.sni.challenge_conf] in http[1]) def test_perform2(self): - self.sni.add_chall(self.achalls[0]) - self.sni.add_chall(self.achalls[2]) + for achall in self.achalls: + self.sni.add_chall(achall) mock_setup_cert = mock.MagicMock(side_effect=[ challenges.DVSNIResponse(s="nginxS0"), - challenges.DVSNIResponse(s="nginxS1")]) + challenges.DVSNIResponse(s="nginxS1"), + challenges.DVSNIResponse(s="nginxS2")]) # pylint: disable=protected-access self.sni._setup_challenge_cert = mock_setup_cert responses = self.sni.perform() - self.assertEqual(mock_setup_cert.call_count, 2) + self.assertEqual(mock_setup_cert.call_count, 3) - self.assertEqual( - mock_setup_cert.call_args_list[0], mock.call(self.achalls[0])) - self.assertEqual( - mock_setup_cert.call_args_list[1], mock.call(self.achalls[2])) + for index, achall in enumerate(self.achalls): + self.assertEqual( + mock_setup_cert.call_args_list[index], mock.call(achall)) http = self.sni.configurator.parser.parsed[ self.sni.configurator.parser.loc["root"]][-1] self.assertTrue(['include', self.sni.challenge_conf] in http[1]) + self.assertTrue(['server_name', 'blah'] in http[1][-2][1]) - self.assertEqual(len(responses), 2) - for i in xrange(2): + self.assertEqual(len(responses), 3) + for i in xrange(3): self.assertEqual(responses[i].s, "nginxS%d" % i) def test_mod_config(self): diff --git a/letsencrypt/client/plugins/nginx/tests/parser_test.py b/letsencrypt/client/plugins/nginx/tests/parser_test.py index 21e96aa26..51ca03e5e 100644 --- a/letsencrypt/client/plugins/nginx/tests/parser_test.py +++ b/letsencrypt/client/plugins/nginx/tests/parser_test.py @@ -140,6 +140,16 @@ class NginxParserTest(util.NginxTest): ['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) + def test_add_http_directives(self): + nparser = parser.NginxParser(self.config_path, self.ssl_options) + filep = nparser.abs_path('nginx.conf') + block = [['server'], + [['listen', '80'], + ['server_name', 'localhost']]] + nparser.add_http_directives(filep, block) + self.assertEqual(nparser.parsed[filep][-1][0], ['http']) + self.assertEqual(nparser.parsed[filep][-1][1][-1], block) + def test_replace_server_directives(self): nparser = parser.NginxParser(self.config_path, self.ssl_options) target = set(['.example.com', 'example.*']) From ae6b13cd5b45d09f053c0666a8a2737894eea170 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 May 2015 23:14:04 -0400 Subject: [PATCH 52/76] Finished basic POP challenge with tests --- letsencrypt/client/client.py | 3 +- letsencrypt/client/continuity_auth.py | 19 ++++- letsencrypt/client/proof_of_possession.py | 52 ++++++------- .../client/tests/continuity_auth_test.py | 27 ++++++- .../client/tests/proof_of_possession_test.py | 78 +++++++++++++++++++ .../client/tests/testdata/matching_cert.pem | 14 ++++ 6 files changed, 156 insertions(+), 37 deletions(-) create mode 100644 letsencrypt/client/tests/proof_of_possession_test.py create mode 100644 letsencrypt/client/tests/testdata/matching_cert.pem diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 8518c56b9..6a54de26d 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -68,7 +68,8 @@ class Client(object): self.config = config if dv_auth is not None: - cont_auth = continuity_auth.ContinuityAuthenticator(config) + cont_auth = continuity_auth.ContinuityAuthenticator(config, + installer) self.auth_handler = auth_handler.AuthHandler( dv_auth, cont_auth, self.network, self.account) else: diff --git a/letsencrypt/client/continuity_auth.py b/letsencrypt/client/continuity_auth.py index 2f01d901e..c6926d952 100644 --- a/letsencrypt/client/continuity_auth.py +++ b/letsencrypt/client/continuity_auth.py @@ -6,6 +6,7 @@ from letsencrypt.acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import errors from letsencrypt.client import interfaces +from letsencrypt.client import proof_of_possession from letsencrypt.client import recovery_token @@ -13,22 +14,30 @@ class ContinuityAuthenticator(object): """IAuthenticator for :const:`~letsencrypt.acme.challenges.ContinuityChallenge` class challenges. - :ivar rec_token: Performs "recoveryToken" challenges + :ivar rec_token: Performs "recoveryToken" challenges. :type rec_token: :class:`letsencrypt.client.recovery_token.RecoveryToken` + :ivar proof_of_pos: Performs "proofOfPossession" challenges. + :type proof_of_pos: + :class:`letsencrypt.client.proof_of_possession.Proof_of_Possession` + """ zope.interface.implements(interfaces.IAuthenticator) # This will have an installer soon for get_key/cert purposes - def __init__(self, config): + def __init__(self, config, installer): """Initialize Client Authenticator. :param config: Configuration. :type config: :class:`letsencrypt.client.interfaces.IConfig` + :param installer: Let's Encrypt Installer. + :type installer: :class:`letsencrypt.client.interfaces.IInstaller` + """ self.rec_token = recovery_token.RecoveryToken( config.server, config.rec_token_dir) + self.proof_of_pos = proof_of_possession.ProofOfPossession(installer) def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" @@ -38,7 +47,9 @@ class ContinuityAuthenticator(object): """Perform client specific challenges for IAuthenticator""" responses = [] for achall in achalls: - if isinstance(achall, achallenges.RecoveryToken): + if isinstance(achall, achallenges.ProofOfPossession): + responses.append(self.proof_of_pos.perform(achall)) + elif isinstance(achall, achallenges.RecoveryToken): responses.append(self.rec_token.perform(achall)) else: raise errors.LetsEncryptContAuthError("Unexpected Challenge") @@ -49,5 +60,5 @@ class ContinuityAuthenticator(object): for achall in achalls: if isinstance(achall, achallenges.RecoveryToken): self.rec_token.cleanup(achall) - else: + elif not isinstance(achall, achallenges.ProofOfPossession): raise errors.LetsEncryptContAuthError("Unexpected Challenge") diff --git a/letsencrypt/client/proof_of_possession.py b/letsencrypt/client/proof_of_possession.py index 2abed067d..82e3524b4 100644 --- a/letsencrypt/client/proof_of_possession.py +++ b/letsencrypt/client/proof_of_possession.py @@ -1,8 +1,4 @@ -"""Proof of Possession Identifier Validation Challenge. - -Based on draft-barnes-acme, section 6.5. - -""" +"""Proof of Possession Identifier Validation Challenge.""" import M2Crypto import os import zope.component @@ -14,22 +10,17 @@ from letsencrypt.client import interfaces from letsencrypt.client.display import util as display_util -class ProofOfPossession(object): +class ProofOfPossession(object): # pylint: disable=too-few-public-methods """Proof of Possession Identifier Validation Challenge. Based on draft-barnes-acme, section 6.5. + :ivar installer: Installer object + :type installer: :class:`~letsencrypt.client.interfaces.IInstaller` + """ - def __init__(self, certs_keys): - """Initializes the object with known certificates and keys. - - :param list certs_keys: tuples with form `[(cert, key, path)]`, where: - - `cert` - str path to certificate file - - `key` - str path to associated key file - - `path` - file path to configuration file - - """ - self.certs_keys = certs_keys + def __init__(self, installer): + self.installer = installer def perform(self, achall): """Perform the Proof of Possession Challenge. @@ -46,18 +37,19 @@ class ProofOfPossession(object): achall.challb.alg in [jose.HS256, jose.HS384, jose.HS512]): return None - for cert, prv_key, _ in self.certs_keys: - der_key = M2Crypto.X509.load_cert(cert).get_pubkey().as_der() - cert_key = challb.alg.kty.load(der_key) - if cert_key == challb.hints.jwk: - return _gen_response(achall, key) + # This will work regardless of how JWKES is implemented + for cert, key, _ in self.installer.get_all_certs_keys(): + der_cert_key = M2Crypto.X509.load_cert(cert).get_pubkey().as_der() + cert_key = achall.challb.alg.kty.load(der_cert_key) + if cert_key == achall.challb.hints.jwk: + return self._gen_response(achall, key) # Is there are different prompt we should give the user? - code, prv_key = zope.component.getUtility( - interfaces.IDsiplay).input( + code, key = zope.component.getUtility( + interfaces.IDisplay).input( "Path to private key for identifier: %s " % achall.domain) if code != display_util.CANCEL: - return _gen_response(achall, prv_key) + return self._gen_response(achall, key) # If we get here, the key wasn't found return False @@ -68,8 +60,8 @@ class ProofOfPossession(object): :param challb: Proof of Possession Challenge :type challb: :class:`letsencrypt.acme.challenges.ProofOfPossession` - :param str key_path: Path to the private key corresponding to the - hinted to public key + :param str key_path: Path to the key corresponding to the hinted to + public key. :returns: Response or None/False if the challenge cannot be completed :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse' @@ -78,11 +70,13 @@ class ProofOfPossession(object): """ if os.path.isfile(key_path): - with key as open(key_path, 'rb'): + with open(key_path, 'rb') as key: try: jwk = challb.alg.kty.load(key.read()) except (IndexError, ValueError, TypeError): - return False - sig = other.Signature.from_msg(challb.nonce, jwk, alg=challb.alg) + return False + # If JWKES doesn't have a key attribute, this needs to be modified + sig = other.Signature.from_msg(challb.nonce, jwk.key, + alg=challb.alg) return challenges.ProofOfPossessionResponse(nonce=challb.nonce, signature=sig) diff --git a/letsencrypt/client/tests/continuity_auth_test.py b/letsencrypt/client/tests/continuity_auth_test.py index 7a2279bcd..567a4e93e 100644 --- a/letsencrypt/client/tests/continuity_auth_test.py +++ b/letsencrypt/client/tests/continuity_auth_test.py @@ -16,9 +16,11 @@ class PerformTest(unittest.TestCase): from letsencrypt.client.continuity_auth import ContinuityAuthenticator self.auth = ContinuityAuthenticator( - mock.MagicMock(server="demo_server.org")) + mock.MagicMock(server="demo_server.org"), None) self.auth.rec_token.perform = mock.MagicMock( name="rec_token_perform", side_effect=gen_client_resp) + self.auth.proof_of_pos.perform = mock.MagicMock( + name="proof_of_pos_perform", side_effect=gen_client_resp) def test_rec_token1(self): token = achallenges.RecoveryToken(challb=None, domain="0") @@ -36,6 +38,24 @@ class PerformTest(unittest.TestCase): for i in xrange(5): self.assertEqual(responses[i], "RecoveryToken%d" % i) + def test_pop_and_rec_token(self): + achalls = [] + for i in xrange(4): + if i % 2 == 0: + achalls.append(achallenges.RecoveryToken(challb=None, + domain=str(i))) + else: + achalls.append(achallenges.ProofOfPossession(challb=None, + domain=str(i))) + responses = self.auth.perform(achalls) + + self.assertEqual(len(responses), 4) + for i in xrange(4): + if i % 2 == 0: + self.assertEqual(responses[i], "RecoveryToken%d" % i) + else: + self.assertEqual(responses[i], "ProofOfPossession%d" % i) + def test_unexpected(self): self.assertRaises( errors.LetsEncryptContAuthError, self.auth.perform, [ @@ -43,7 +63,8 @@ class PerformTest(unittest.TestCase): def test_chall_pref(self): self.assertEqual( - self.auth.get_chall_pref("example.com"), [challenges.RecoveryToken]) + self.auth.get_chall_pref("example.com"), + [challenges.ProofOfPossession, challenges.RecoveryToken]) class CleanupTest(unittest.TestCase): @@ -53,7 +74,7 @@ class CleanupTest(unittest.TestCase): from letsencrypt.client.continuity_auth import ContinuityAuthenticator self.auth = ContinuityAuthenticator( - mock.MagicMock(server="demo_server.org")) + mock.MagicMock(server="demo_server.org"), None) self.mock_cleanup = mock.MagicMock(name="rec_token_cleanup") self.auth.rec_token.cleanup = self.mock_cleanup diff --git a/letsencrypt/client/tests/proof_of_possession_test.py b/letsencrypt/client/tests/proof_of_possession_test.py new file mode 100644 index 000000000..0ba801ff1 --- /dev/null +++ b/letsencrypt/client/tests/proof_of_possession_test.py @@ -0,0 +1,78 @@ +"""Tests for proof_of_possession.py""" +import Crypto.PublicKey.RSA +import os +import pkg_resources +import unittest + +import mock + +from letsencrypt.acme import challenges +from letsencrypt.acme import jose +from letsencrypt.client import achallenges +from letsencrypt.client import proof_of_possession +from letsencrypt.client.display import util as display_util + + +BASE_PACKAGE = "letsencrypt.client.tests" +CERT0_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "cert.pem")) +CERT1_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "cert-san.pem")) +CERT2_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "matching_cert.pem")) +KEY_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "rsa512_key.pem")) +KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( + BASE_PACKAGE, os.path.join('testdata', 'rsa512_key.pem'))).publickey() + + +class ProofOfPossessionTest(unittest.TestCase): + def setUp(self): + self.installer = mock.MagicMock() + self.installer.get_all_certs_keys.return_value = zip( + [CERT0_PATH, CERT1_PATH, CERT2_PATH], 3 * [KEY_PATH], 3 * [None]) + self.proof_of_pos = proof_of_possession.ProofOfPossession( + self.installer) + + hints = challenges.ProofOfPossession.Hints( + jwk=jose.JWKRSA(key=KEY), cert_fingerprints=(), + certs=(), serial_numbers=(), subject_key_identifiers=(), + issuers=(), authorized_for=()) + challenge = challenges.ProofOfPossession( + alg=jose.RS256, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) + self.achall = achallenges.ProofOfPossession( + challb=challenge, domain="example.com") + + def test_perform_no_input(self): + response = self.proof_of_pos.perform(self.achall) + self.assertTrue(response.verify()) + + @mock.patch("letsencrypt.client.recovery_token.zope.component.getUtility") + def test_perform_with_input(self, mock_input): + # Remove the matching certificate + self.installer.get_all_certs_keys.return_value.pop() + mock_input().input.side_effect = [(display_util.CANCEL, ""), + (display_util.OK, CERT0_PATH), + (display_util.OK, KEY_PATH)] + + response = self.proof_of_pos.perform(self.achall) + self.assertFalse(response) + + response = self.proof_of_pos.perform(self.achall) + self.assertFalse(response) + + response = self.proof_of_pos.perform(self.achall) + self.assertTrue(response.verify()) + + def test_perform_bad_challenge(self): + hints = challenges.ProofOfPossession.Hints( + jwk=jose.jwk.JWKOct(key=KEY), cert_fingerprints=(), + certs=(), serial_numbers=(), subject_key_identifiers=(), + issuers=(), authorized_for=()) + challenge = challenges.ProofOfPossession( + alg=jose.HS512, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) + self.achall = achallenges.ProofOfPossession( + challb=challenge, domain="example.com") + + response = self.proof_of_pos.perform(self.achall) + self.assertEqual(response, None) diff --git a/letsencrypt/client/tests/testdata/matching_cert.pem b/letsencrypt/client/tests/testdata/matching_cert.pem new file mode 100644 index 000000000..fda9cb1f4 --- /dev/null +++ b/letsencrypt/client/tests/testdata/matching_cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAeGgAwIBAgIJALizm9Y3q620MA0GCSqGSIb3DQEBCwUAMHcxCzAJBgNV +BAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjESMBAGA1UEBwwJQW5uIEFyYm9yMSsw +KQYDVQQKDCJVbml2ZXJzaXR5IG9mIE1pY2hpZ2FuIGFuZCB0aGUgRUZGMRQwEgYD +VQQDDAtleGFtcGxlLmNvbTAeFw0xNTA1MDkwMDI0NTJaFw0xNjA1MDgwMDI0NTJa +MHcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjESMBAGA1UEBwwJQW5u +IEFyYm9yMSswKQYDVQQKDCJVbml2ZXJzaXR5IG9mIE1pY2hpZ2FuIGFuZCB0aGUg +RUZGMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC +QQD0thFxUTc2v6qV55wRxfwnBUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3Hg +BVy9ddFc8RX4vNZaR+ROXNEzAgMBAAGjUDBOMB0GA1UdDgQWBBRJieHEVSHKmBk0 +mTExx1erzlylCjAfBgNVHSMEGDAWgBRJieHEVSHKmBk0mTExx1erzlylCjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EABT/nlpqOaanFSLZmWIrKv0zt63k4 +bmWNMA8fYT45KYpLomsW8qXdpC82IlVKfNk7fW0UYT3HOeDSJRcycxNCTQ== +-----END CERTIFICATE----- From e3d95c5a6887894be2a82784c7436f17205a8023 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 May 2015 23:43:06 -0400 Subject: [PATCH 53/76] Final changes --- docs/api/client/proof_of_possession.rst | 5 ++++ letsencrypt/client/proof_of_possession.py | 4 +-- .../client/tests/proof_of_possession_test.py | 26 +++++++++++-------- 3 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 docs/api/client/proof_of_possession.rst diff --git a/docs/api/client/proof_of_possession.rst b/docs/api/client/proof_of_possession.rst new file mode 100644 index 000000000..9f1ea0793 --- /dev/null +++ b/docs/api/client/proof_of_possession.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.client.proof_of_possession` +-------------------------------------------------- + +.. automodule:: letsencrypt.client.proof_of_possession + :members: diff --git a/letsencrypt/client/proof_of_possession.py b/letsencrypt/client/proof_of_possession.py index 82e3524b4..a2f4956b9 100644 --- a/letsencrypt/client/proof_of_possession.py +++ b/letsencrypt/client/proof_of_possession.py @@ -29,7 +29,7 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods :type achall: :class:`letsencrypt.client.achallenges.ProofOfPossession` :returns: Response or None/False if the challenge cannot be completed - :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse' + :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse` or False """ @@ -64,7 +64,7 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods public key. :returns: Response or None/False if the challenge cannot be completed - :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse' + :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse` or False """ diff --git a/letsencrypt/client/tests/proof_of_possession_test.py b/letsencrypt/client/tests/proof_of_possession_test.py index 0ba801ff1..f068bc1b2 100644 --- a/letsencrypt/client/tests/proof_of_possession_test.py +++ b/letsencrypt/client/tests/proof_of_possession_test.py @@ -43,6 +43,19 @@ class ProofOfPossessionTest(unittest.TestCase): self.achall = achallenges.ProofOfPossession( challb=challenge, domain="example.com") + def test_perform_bad_challenge(self): + hints = challenges.ProofOfPossession.Hints( + jwk=jose.jwk.JWKOct(key=KEY), cert_fingerprints=(), + certs=(), serial_numbers=(), subject_key_identifiers=(), + issuers=(), authorized_for=()) + challenge = challenges.ProofOfPossession( + alg=jose.HS512, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) + self.achall = achallenges.ProofOfPossession( + challb=challenge, domain="example.com") + + response = self.proof_of_pos.perform(self.achall) + self.assertEqual(response, None) + def test_perform_no_input(self): response = self.proof_of_pos.perform(self.achall) self.assertTrue(response.verify()) @@ -64,15 +77,6 @@ class ProofOfPossessionTest(unittest.TestCase): response = self.proof_of_pos.perform(self.achall) self.assertTrue(response.verify()) - def test_perform_bad_challenge(self): - hints = challenges.ProofOfPossession.Hints( - jwk=jose.jwk.JWKOct(key=KEY), cert_fingerprints=(), - certs=(), serial_numbers=(), subject_key_identifiers=(), - issuers=(), authorized_for=()) - challenge = challenges.ProofOfPossession( - alg=jose.HS512, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) - self.achall = achallenges.ProofOfPossession( - challb=challenge, domain="example.com") - response = self.proof_of_pos.perform(self.achall) - self.assertEqual(response, None) +if __name__ == "__main__": + unittest.main() From e197f156a72e33c63e7068e012fe3bb0ce397e81 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 05:47:09 +0000 Subject: [PATCH 54/76] choose_plugin can return None --- letsencrypt/client/display/ops.py | 6 +++++- letsencrypt/client/tests/display/ops_test.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index 5a77e0ffb..dc6992c8c 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -71,7 +71,11 @@ def pick_plugin(config, default, plugins, question, ifaces): if len(prepared) > 1: logging.debug("Multiple candidate plugins: %s", prepared) - return choose_plugin(prepared.values(), question).init() + plugin_ep = choose_plugin(prepared.values(), question) + if plugin_ep is None: + return None + else: + return plugin_ep.init() elif len(prepared) == 1: plugin_ep = prepared.values()[0] logging.debug("Single candidate plugin: %s", plugin_ep) diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/client/tests/display/ops_test.py index 151358f8a..7c5c1f74f 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/client/tests/display/ops_test.py @@ -102,6 +102,17 @@ class PickPluginTest(unittest.TestCase): mock_choose.assert_called_once_with( [plugin_ep, plugin_ep], self.question) + def test_choose_plugin_none(self): + self.reg.ifaces().verify().available.return_value = { + "bar": None, + "baz": None, + } + + with mock.patch("letsencrypt.client.display" + ".ops.choose_plugin") as mock_choose: + mock_choose.return_value = None + self.assertTrue(self._call() is None) + class ConveniencePickPluginTest(unittest.TestCase): """Tests for letsencrypt.client.display.ops.pick_*.""" From 75a7b7605b82945a8701f34df414da98411afd52 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 07:25:36 +0000 Subject: [PATCH 55/76] PluginsRegistry.find_init --- letsencrypt/client/plugins/disco.py | 26 ++++++++++++++++++++++++ letsencrypt/client/plugins/disco_test.py | 6 ++++++ 2 files changed, 32 insertions(+) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index f4a9faecf..fd636d59a 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -16,6 +16,9 @@ class PluginEntryPoint(object): PREFIX_FREE_DISTRIBUTIONS = ["letsencrypt"] """Distributions for which prefix will be omitted.""" + # this object is mutable, don't allow it to be hashed! + __hash__ = None + def __init__(self, entry_point): self.name = self.entry_point_to_plugin_name(entry_point) self.plugin_cls = entry_point.load() @@ -188,6 +191,29 @@ class PluginsRegistry(collections.Mapping): return self.filter(lambda p_ep: p_ep.available) # succefully prepared + misconfigured + def find_init(self, plugin): + """Find an initialized plugin. + + This is particularly useful for finding a name for the plugin + (although `.IPluginFactory.__call__` takes ``name`` as one of + the arguments, ``IPlugin.name`` is not part of the interface):: + + # plugin is an instance providing IPlugin, initialized + # somewhere else in the code + plugin_registry.find_init(plugin).name + + Returns ``None`` if ``plugin`` is not found in the registry. + + """ + # use list instead of set beacse PluginEntryPoint is not hashable + candidates = [plugin_ep for plugin_ep in self.plugins.itervalues() + if plugin_ep.initialized and plugin_ep.init() is plugin] + assert len(candidates) <= 1 + if candidates: + return candidates[0] + else: + return None + def __repr__(self): return "{0}({1!r})".format( self.__class__.__name__, set(self.plugins.itervalues())) diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index 945f72d6b..ca3bc42d4 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -216,6 +216,12 @@ class PluginsRegistryTest(unittest.TestCase): self.plugin_ep.available = False self.assertEqual({}, self.reg.available().plugins) + def test_find_init(self): + self.assertTrue(self.reg.find_init(mock.Mock()) is None) + self.plugin_ep.initalized = True + self.assertTrue( + self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep) + def test_repr(self): self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock" self.assertEqual("PluginsRegistry(set([PluginEntryPoint#mock]))", From e415a63d1fa6d969f9deaadd087284eac5510af6 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 07:29:51 +0000 Subject: [PATCH 56/76] PluginsRegistry.plugins -> PluginsRegistry._plugins --- letsencrypt/client/plugins/disco.py | 22 +++++++++++----------- letsencrypt/client/plugins/disco_test.py | 15 +++++++++------ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index fd636d59a..ce6e23172 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -135,7 +135,7 @@ class PluginsRegistry(collections.Mapping): """Plugins registry.""" def __init__(self, plugins): - self.plugins = plugins + self._plugins = plugins @classmethod def find_all(cls): @@ -155,23 +155,23 @@ class PluginsRegistry(collections.Mapping): return cls(plugins) def __getitem__(self, name): - return self.plugins[name] + return self._plugins[name] def __iter__(self): - return iter(self.plugins) + return iter(self._plugins) def __len__(self): - return len(self.plugins) + return len(self._plugins) def init(self, config): """Initialize all plugins in the registry.""" return [plugin_ep.init(config) for plugin_ep - in self.plugins.itervalues()] + in self._plugins.itervalues()] def filter(self, pred): """Filter plugins based on predicate.""" return type(self)(dict((name, plugin_ep) for name, plugin_ep - in self.plugins.iteritems() if pred(plugin_ep))) + in self._plugins.iteritems() if pred(plugin_ep))) def ifaces(self, *ifaces_groups): """Filter plugins based on interfaces.""" @@ -184,7 +184,7 @@ class PluginsRegistry(collections.Mapping): def prepare(self): """Prepare all plugins in the registry.""" - return [plugin_ep.prepare() for plugin_ep in self.plugins.itervalues()] + return [plugin_ep.prepare() for plugin_ep in self._plugins.itervalues()] def available(self): """Filter plugins based on availability.""" @@ -206,7 +206,7 @@ class PluginsRegistry(collections.Mapping): """ # use list instead of set beacse PluginEntryPoint is not hashable - candidates = [plugin_ep for plugin_ep in self.plugins.itervalues() + candidates = [plugin_ep for plugin_ep in self._plugins.itervalues() if plugin_ep.initialized and plugin_ep.init() is plugin] assert len(candidates) <= 1 if candidates: @@ -216,9 +216,9 @@ class PluginsRegistry(collections.Mapping): def __repr__(self): return "{0}({1!r})".format( - self.__class__.__name__, set(self.plugins.itervalues())) + self.__class__.__name__, set(self._plugins.itervalues())) def __str__(self): - if not self.plugins: + if not self._plugins: return "No plugins" - return "\n\n".join(str(p_ep) for p_ep in self.plugins.itervalues()) + return "\n\n".join(str(p_ep) for p_ep in self._plugins.itervalues()) diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index ca3bc42d4..f5ea9e6ee 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -194,16 +194,18 @@ class PluginsRegistryTest(unittest.TestCase): def test_ifaces(self): self.plugin_ep.ifaces.return_value = True - self.assertEqual(self.plugins, self.reg.ifaces().plugins) + # pylint: disable=protected-access + self.assertEqual(self.plugins, self.reg.ifaces()._plugins) self.plugin_ep.ifaces.return_value = False - self.assertEqual({}, self.reg.ifaces().plugins) + self.assertEqual({}, self.reg.ifaces()._plugins) def test_verify(self): self.plugin_ep.verify.return_value = True + # pylint: disable=protected-access self.assertEqual( - self.plugins, self.reg.verify(mock.MagicMock()).plugins) + self.plugins, self.reg.verify(mock.MagicMock())._plugins) self.plugin_ep.verify.return_value = False - self.assertEqual({}, self.reg.verify(mock.MagicMock()).plugins) + self.assertEqual({}, self.reg.verify(mock.MagicMock())._plugins) def test_prepare(self): self.plugin_ep.prepare.return_value = "baz" @@ -212,9 +214,10 @@ class PluginsRegistryTest(unittest.TestCase): def test_available(self): self.plugin_ep.available = True - self.assertEqual(self.plugins, self.reg.available().plugins) + # pylint: disable=protected-access + self.assertEqual(self.plugins, self.reg.available()._plugins) self.plugin_ep.available = False - self.assertEqual({}, self.reg.available().plugins) + self.assertEqual({}, self.reg.available()._plugins) def test_find_init(self): self.assertTrue(self.reg.find_init(mock.Mock()) is None) From 21411266087a6db13501241e0c34f0a5240a395f Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 07:36:52 +0000 Subject: [PATCH 57/76] clean up disco logging --- letsencrypt/client/plugins/disco.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index ce6e23172..50e0bce50 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -80,7 +80,7 @@ class PluginEntryPoint(object): def prepared(self): """Has the plugin been prepared already?""" if not self.initialized: - logging.debug(".prepared called on uninitialized %s", self) + logging.debug(".prepared called on uninitialized %r", self) return self._prepared is not None def prepare(self): @@ -90,10 +90,10 @@ class PluginEntryPoint(object): try: self._initialized.prepare() except errors.LetsEncryptMisconfigurationError as error: - logging.debug("Misconfigured %s: %s", self, error) + logging.debug("Misconfigured %r: %s", self, error) self._prepared = error except errors.LetsEncryptNoInstallationError as error: - logging.debug("No installation (%s): %s", self, error) + logging.debug("No installation (%r): %s", self, error) self._prepared = error else: self._prepared = True @@ -150,8 +150,8 @@ class PluginsRegistry(collections.Mapping): if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): plugins[plugin_ep.name] = plugin_ep else: # pragma: no cover - logging.warning("Plugin entry point %s does not provide " - "IPluginFactory, skipping", plugin_ep) + logging.warning( + "%r does not provide IPluginFactory, skipping", plugin_ep) return cls(plugins) def __getitem__(self, name): From a3c8753dcf48966e46c29a0d6ccd84d9fd198d07 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 15:09:29 +0000 Subject: [PATCH 58/76] Fix client to work with cert_chain_uri, "is (not) None" fixes --- letsencrypt/client/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 8518c56b9..ba3e0299b 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -77,7 +77,7 @@ class Client(object): def register(self): """New Registration with the ACME server.""" self.account = self.network.register_from_account(self.account) - if self.account.terms_of_service: + if self.account.terms_of_service is not None: if not self.config.tos: # TODO: Replace with self.account.terms_of_service eula = pkg_resources.resource_string("letsencrypt", "EULA") @@ -172,13 +172,13 @@ class Client(object): logging.info("Server issued certificate; certificate written to %s", act_cert_path) - if certr.cert_chain_uri: + if certr.cert_chain_uri is not None: # TODO: Except - chain_cert = self.network.fetch_chain(certr.cert_chain_uri) - if chain_cert: + chain_cert = self.network.fetch_chain(certr) + if chain_cert is not None: chain_file, act_chain_path = le_util.unique_file( chain_path, 0o644) - chain_pem = chain_cert.to_pem() + chain_pem = chain_cert.as_pem() try: chain_file.write(chain_pem) finally: From 42e7ec4bd2432397927f8d9661455f6681dfc5ac Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 19:50:58 +0000 Subject: [PATCH 59/76] CLI: use_curses -> text_mode Previously, the --help output seemed to be broken: -t, --text Use the text output instead of the curses UI. (default: True) --- letsencrypt/client/cli.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index 762e9a850..a83c0f767 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -236,7 +236,7 @@ def create_parser(plugins): help="Turn off confirmation screens, currently used for --revoke") add("-e", "--agree-tos", dest="tos", action="store_true", help="Skip the end user license agreement screen.") - add("-t", "--text", dest="use_curses", action="store_false", + add("-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") subparsers = parser.add_subparsers(metavar="SUBCOMMAND") @@ -339,10 +339,10 @@ def main(args=sys.argv[1:]): config = configuration.NamespaceConfig(args) # Displayer - if args.use_curses: - displayer = display_util.NcursesDisplay() - else: + if args.text_mode: displayer = display_util.FileDisplay(sys.stdout) + else: + displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Logging @@ -350,7 +350,7 @@ def main(args=sys.argv[1:]): logger = logging.getLogger() logger.setLevel(level) logging.debug("Logging level set at %d", level) - if args.use_curses: + if not args.text_mode: logger.addHandler(log.DialogHandler()) logging.debug("Discovered plugins: %r", plugins) From e8eae2dab2bdef51ae40c2dd21477a1a61539485 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 11:07:56 +0000 Subject: [PATCH 60/76] find_packages() in setup.py --- setup.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index a892937e2..0302f1435 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import os import re from setuptools import setup +from setuptools import find_packages # Workaround for http://bugs.python.org/issue8876, see # http://bugs.python.org/issue8876#msg208792 @@ -97,23 +98,7 @@ setup( 'Topic :: Utilities', ], - packages=[ - 'letsencrypt', - 'letsencrypt.acme', - 'letsencrypt.acme.jose', - 'letsencrypt.client', - 'letsencrypt.client.display', - 'letsencrypt.client.plugins', - 'letsencrypt.client.plugins.apache', - 'letsencrypt.client.plugins.apache.tests', - 'letsencrypt.client.plugins.nginx', - 'letsencrypt.client.plugins.nginx.tests', - 'letsencrypt.client.plugins.standalone', - 'letsencrypt.client.plugins.standalone.tests', - 'letsencrypt.client.tests', - 'letsencrypt.client.tests.display', - ], - + packages=find_packages(exclude=['docs', 'examples', 'tests', 'venv']), install_requires=install_requires, extras_require={ 'dev': dev_extras, From 81e8ba7daf65b8d6283b13cc9eefce29613915eb Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 11:12:03 +0000 Subject: [PATCH 61/76] Update MANIFEST.in --- MANIFEST.in | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3bd657b87..5d40ffa59 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,14 @@ include CHANGES.rst include CONTRIBUTING.md include linter_plugin.py include letsencrypt/EULA -recursive-include letsencrypt *.json -recursive-include letsencrypt *.conf + recursive-include letsencrypt/client/tests/testdata * + +recursive-include letsencrypt/acme/schemata *.json +recursive-include letsencrypt/acme/jose/testdata * + +recursive-include letsencrypt/client/plugins/apache/tests/testdata * +include letsencrypt/client/plugins/apache/options-ssl.conf + +recursive-include letsencrypt/client/plugins/nginx/tests/testdata * +include letsencrypt/client/plugins/nginx/options-ssl.conf From 3a6bd7123d5ab76c8243e95080ceb521f74578c8 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 11:26:21 +0000 Subject: [PATCH 62/76] Move acme to top-level --- MANIFEST.in | 4 +- {letsencrypt/acme => acme}/__init__.py | 0 {letsencrypt/acme => acme}/challenges.py | 10 +- {letsencrypt/acme => acme}/challenges_test.py | 101 +++++++++--------- {letsencrypt/acme => acme}/errors.py | 2 +- {letsencrypt/acme => acme}/fields.py | 2 +- {letsencrypt/acme => acme}/fields_test.py | 14 +-- {letsencrypt/acme => acme}/jose/__init__.py | 16 +-- {letsencrypt/acme => acme}/jose/b64.py | 0 {letsencrypt/acme => acme}/jose/b64_test.py | 10 +- {letsencrypt/acme => acme}/jose/errors.py | 0 .../acme => acme}/jose/errors_test.py | 4 +- {letsencrypt/acme => acme}/jose/interfaces.py | 8 +- .../acme => acme}/jose/interfaces_test.py | 10 +- {letsencrypt/acme => acme}/jose/json_util.py | 20 ++-- .../acme => acme}/jose/json_util_test.py | 50 ++++----- {letsencrypt/acme => acme}/jose/jwa.py | 6 +- {letsencrypt/acme => acme}/jose/jwa_test.py | 24 ++--- {letsencrypt/acme => acme}/jose/jwk.py | 8 +- {letsencrypt/acme => acme}/jose/jwk_test.py | 28 ++--- {letsencrypt/acme => acme}/jose/jws.py | 22 ++-- {letsencrypt/acme => acme}/jose/jws_test.py | 60 +++++------ .../acme => acme}/jose/testdata/README | 0 .../acme => acme}/jose/testdata/csr2.pem | 0 .../jose/testdata/rsa1024_key.pem | 0 .../jose/testdata/rsa256_key.pem | 0 .../jose/testdata/rsa512_key.pem | 0 {letsencrypt/acme => acme}/jose/util.py | 0 {letsencrypt/acme => acme}/jose/util_test.py | 20 ++-- {letsencrypt/acme => acme}/messages.py | 42 ++++---- {letsencrypt/acme => acme}/messages2.py | 32 +++--- {letsencrypt/acme => acme}/messages2_test.py | 68 ++++++------ {letsencrypt/acme => acme}/messages_test.py | 80 +++++++------- {letsencrypt/acme => acme}/other.py | 2 +- {letsencrypt/acme => acme}/other_test.py | 16 +-- .../acme => acme}/schemata/authorization.json | 2 +- .../schemata/authorizationRequest.json | 4 +- .../acme => acme}/schemata/certificate.json | 0 .../schemata/certificateRequest.json | 2 +- .../acme => acme}/schemata/challenge.json | 2 +- .../schemata/challengeRequest.json | 0 .../schemata/challengeobject.json | 0 .../acme => acme}/schemata/defer.json | 0 .../acme => acme}/schemata/error.json | 0 {letsencrypt/acme => acme}/schemata/jwk.json | 0 .../schemata/responseobject.json | 2 +- .../acme => acme}/schemata/revocation.json | 0 .../schemata/revocationRequest.json | 2 +- .../acme => acme}/schemata/signature.json | 0 .../acme => acme}/schemata/statusRequest.json | 0 {letsencrypt/acme => acme}/util.py | 0 docs/acme/index.rst | 61 +++++++++++ docs/acme/jose.rst | 67 ++++++++++++ docs/api/acme/index.rst | 61 ----------- docs/api/acme/jose.rst | 67 ------------ docs/contributing.rst | 2 +- docs/index.rst | 1 + letsencrypt/client/account.py | 4 +- letsencrypt/client/achallenges.py | 12 +-- letsencrypt/client/auth_handler.py | 22 ++-- letsencrypt/client/client.py | 6 +- letsencrypt/client/constants.py | 2 +- letsencrypt/client/continuity_auth.py | 4 +- letsencrypt/client/interfaces.py | 8 +- letsencrypt/client/network.py | 20 ++-- letsencrypt/client/network2.py | 8 +- .../client/plugins/apache/configurator.py | 2 +- .../plugins/apache/tests/configurator_test.py | 2 +- .../client/plugins/apache/tests/dvsni_test.py | 6 +- .../client/plugins/apache/tests/util.py | 4 +- letsencrypt/client/plugins/common.py | 2 +- .../client/plugins/nginx/configurator.py | 2 +- .../plugins/nginx/tests/configurator_test.py | 4 +- .../client/plugins/nginx/tests/dvsni_test.py | 8 +- .../client/plugins/nginx/tests/util.py | 4 +- .../plugins/standalone/authenticator.py | 2 +- .../standalone/tests/authenticator_test.py | 4 +- letsencrypt/client/recovery_token.py | 2 +- letsencrypt/client/revoker.py | 4 +- letsencrypt/client/tests/account_test.py | 6 +- letsencrypt/client/tests/achallenges_test.py | 5 +- letsencrypt/client/tests/acme_util.py | 10 +- letsencrypt/client/tests/auth_handler_test.py | 8 +- .../client/tests/continuity_auth_test.py | 2 +- letsencrypt/client/tests/crypto_util_test.py | 4 +- letsencrypt/client/tests/network2_test.py | 10 +- .../client/tests/recovery_token_test.py | 2 +- letsencrypt/client/tests/revoker_test.py | 3 +- setup.cfg | 2 +- setup.py | 2 +- tox.ini | 3 +- 91 files changed, 559 insertions(+), 562 deletions(-) rename {letsencrypt/acme => acme}/__init__.py (100%) rename {letsencrypt/acme => acme}/challenges.py (96%) rename {letsencrypt/acme => acme}/challenges_test.py (81%) rename {letsencrypt/acme => acme}/errors.py (77%) rename {letsencrypt/acme => acme}/fields.py (94%) rename {letsencrypt/acme => acme}/fields_test.py (69%) rename {letsencrypt/acme => acme}/jose/__init__.py (77%) rename {letsencrypt/acme => acme}/jose/b64.py (100%) rename {letsencrypt/acme => acme}/jose/b64_test.py (87%) rename {letsencrypt/acme => acme}/jose/errors.py (100%) rename {letsencrypt/acme => acme}/jose/errors_test.py (75%) rename {letsencrypt/acme => acme}/jose/interfaces.py (96%) rename {letsencrypt/acme => acme}/jose/interfaces_test.py (90%) rename {letsencrypt/acme => acme}/jose/json_util.py (95%) rename {letsencrypt/acme => acme}/jose/json_util_test.py (87%) rename {letsencrypt/acme => acme}/jose/jwa.py (96%) rename {letsencrypt/acme => acme}/jose/jwa_test.py (83%) rename {letsencrypt/acme => acme}/jose/jwk.py (95%) rename {letsencrypt/acme => acme}/jose/jwk_test.py (81%) rename {letsencrypt/acme => acme}/jose/jws.py (96%) rename {letsencrypt/acme => acme}/jose/jws_test.py (83%) rename {letsencrypt/acme => acme}/jose/testdata/README (100%) rename {letsencrypt/acme => acme}/jose/testdata/csr2.pem (100%) rename {letsencrypt/acme => acme}/jose/testdata/rsa1024_key.pem (100%) rename {letsencrypt/acme => acme}/jose/testdata/rsa256_key.pem (100%) rename {letsencrypt/acme => acme}/jose/testdata/rsa512_key.pem (100%) rename {letsencrypt/acme => acme}/jose/util.py (100%) rename {letsencrypt/acme => acme}/jose/util_test.py (87%) rename {letsencrypt/acme => acme}/messages.py (89%) rename {letsencrypt/acme => acme}/messages2.py (91%) rename {letsencrypt/acme => acme}/messages2_test.py (78%) rename {letsencrypt/acme => acme}/messages_test.py (86%) rename {letsencrypt/acme => acme}/other.py (98%) rename {letsencrypt/acme => acme}/other_test.py (87%) rename {letsencrypt/acme => acme}/schemata/authorization.json (88%) rename {letsencrypt/acme => acme}/schemata/authorizationRequest.json (85%) rename {letsencrypt/acme => acme}/schemata/certificate.json (100%) rename {letsencrypt/acme => acme}/schemata/certificateRequest.json (88%) rename {letsencrypt/acme => acme}/schemata/challenge.json (91%) rename {letsencrypt/acme => acme}/schemata/challengeRequest.json (100%) rename {letsencrypt/acme => acme}/schemata/challengeobject.json (100%) rename {letsencrypt/acme => acme}/schemata/defer.json (100%) rename {letsencrypt/acme => acme}/schemata/error.json (100%) rename {letsencrypt/acme => acme}/schemata/jwk.json (100%) rename {letsencrypt/acme => acme}/schemata/responseobject.json (96%) rename {letsencrypt/acme => acme}/schemata/revocation.json (100%) rename {letsencrypt/acme => acme}/schemata/revocationRequest.json (87%) rename {letsencrypt/acme => acme}/schemata/signature.json (100%) rename {letsencrypt/acme => acme}/schemata/statusRequest.json (100%) rename {letsencrypt/acme => acme}/util.py (100%) create mode 100644 docs/acme/index.rst create mode 100644 docs/acme/jose.rst delete mode 100644 docs/api/acme/index.rst delete mode 100644 docs/api/acme/jose.rst diff --git a/MANIFEST.in b/MANIFEST.in index 5d40ffa59..79c87e8f0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,8 +6,8 @@ include letsencrypt/EULA recursive-include letsencrypt/client/tests/testdata * -recursive-include letsencrypt/acme/schemata *.json -recursive-include letsencrypt/acme/jose/testdata * +recursive-include acme/schemata *.json +recursive-include acme/jose/testdata * recursive-include letsencrypt/client/plugins/apache/tests/testdata * include letsencrypt/client/plugins/apache/options-ssl.conf diff --git a/letsencrypt/acme/__init__.py b/acme/__init__.py similarity index 100% rename from letsencrypt/acme/__init__.py rename to acme/__init__.py diff --git a/letsencrypt/acme/challenges.py b/acme/challenges.py similarity index 96% rename from letsencrypt/acme/challenges.py rename to acme/challenges.py index 9c0f263c7..11a1c9a60 100644 --- a/letsencrypt/acme/challenges.py +++ b/acme/challenges.py @@ -5,8 +5,8 @@ import hashlib import Crypto.Random -from letsencrypt.acme import jose -from letsencrypt.acme import other +from acme import jose +from acme import other # pylint: disable=too-few-public-methods @@ -186,8 +186,8 @@ class ProofOfPossession(ContinuityChallenge): class Hints(jose.JSONObjectWithFields): """Hints for "proofOfPossession" challenge. - :ivar jwk: JSON Web Key (:class:`letsencrypt.acme.jose.JWK`) - :ivar list certs: List of :class:`letsencrypt.acme.jose.ComparableX509` + :ivar jwk: JSON Web Key (:class:`acme.jose.JWK`) + :ivar list certs: List of :class:`acme.jose.ComparableX509` certificates. """ @@ -221,7 +221,7 @@ class ProofOfPossessionResponse(ChallengeResponse): """ACME "proofOfPossession" challenge response. :ivar str nonce: Random data, **not** base64-encoded. - :ivar signature: :class:`~letsencrypt.acme.other.Signature` of this message. + :ivar signature: :class:`~acme.other.Signature` of this message. """ typ = "proofOfPossession" diff --git a/letsencrypt/acme/challenges_test.py b/acme/challenges_test.py similarity index 81% rename from letsencrypt/acme/challenges_test.py rename to acme/challenges_test.py index 9ca9f6dd8..c2c8bdfea 100644 --- a/letsencrypt/acme/challenges_test.py +++ b/acme/challenges_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.challenges.""" +"""Tests for acme.challenges.""" import os import pkg_resources import unittest @@ -6,8 +6,8 @@ import unittest import Crypto.PublicKey.RSA import M2Crypto -from letsencrypt.acme import jose -from letsencrypt.acme import other +from acme import jose +from acme import other CERT = jose.ComparableX509(M2Crypto.X509.load_cert( @@ -15,14 +15,13 @@ CERT = jose.ComparableX509(M2Crypto.X509.load_cert( 'letsencrypt.client.tests', os.path.join('testdata', 'cert.pem')))) KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( - 'letsencrypt.acme.jose', - os.path.join('testdata', 'rsa512_key.pem')))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) class SimpleHTTPSTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import SimpleHTTPS + from acme.challenges import SimpleHTTPS self.msg = SimpleHTTPS( token='evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA') self.jmsg = { @@ -34,18 +33,18 @@ class SimpleHTTPSTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import SimpleHTTPS + from acme.challenges import SimpleHTTPS self.assertEqual(self.msg, SimpleHTTPS.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import SimpleHTTPS + from acme.challenges import SimpleHTTPS hash(SimpleHTTPS.from_json(self.jmsg)) class SimpleHTTPSResponseTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import SimpleHTTPSResponse + from acme.challenges import SimpleHTTPSResponse self.msg = SimpleHTTPSResponse(path='6tbIMBC5Anhl5bOlWT5ZFA') self.jmsg = { 'type': 'simpleHttps', @@ -60,19 +59,19 @@ class SimpleHTTPSResponseTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import SimpleHTTPSResponse + from acme.challenges import SimpleHTTPSResponse self.assertEqual( self.msg, SimpleHTTPSResponse.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import SimpleHTTPSResponse + from acme.challenges import SimpleHTTPSResponse hash(SimpleHTTPSResponse.from_json(self.jmsg)) class DVSNITest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI self.msg = DVSNI( r="O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6" "\xbf'\xb3\xed\x9a9nX\x0f'\\m\xe7\x12", @@ -91,21 +90,21 @@ class DVSNITest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI self.assertEqual(self.msg, DVSNI.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI hash(DVSNI.from_json(self.jmsg)) def test_from_json_invalid_r_length(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI self.jmsg['r'] = 'abcd' self.assertRaises( jose.DeserializationError, DVSNI.from_json, self.jmsg) def test_from_json_invalid_nonce_length(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI self.jmsg['nonce'] = 'abcd' self.assertRaises( jose.DeserializationError, DVSNI.from_json, self.jmsg) @@ -114,7 +113,7 @@ class DVSNITest(unittest.TestCase): class DVSNIResponseTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import DVSNIResponse + from acme.challenges import DVSNIResponse self.msg = DVSNIResponse( s='\xf5\xd6\xe3\xb2]\xe0L\x0bN\x9cKJ\x14I\xa1K\xa3#\xf9\xa8' '\xcd\x8c7\x0e\x99\x19)\xdc\xb7\xf3\x9bw') @@ -124,7 +123,7 @@ class DVSNIResponseTest(unittest.TestCase): } def test_z_and_domain(self): - from letsencrypt.acme.challenges import DVSNI + from acme.challenges import DVSNI challenge = DVSNI( r="O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6" "\xbf'\xb3\xed\x9a9nX\x0f'\\m\xe7\x12", @@ -140,18 +139,18 @@ class DVSNIResponseTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import DVSNIResponse + from acme.challenges import DVSNIResponse self.assertEqual(self.msg, DVSNIResponse.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import DVSNIResponse + from acme.challenges import DVSNIResponse hash(DVSNIResponse.from_json(self.jmsg)) class RecoveryContactTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import RecoveryContact + from acme.challenges import RecoveryContact self.msg = RecoveryContact( activation_url='https://example.ca/sendrecovery/a5bd99383fb0', success_url='https://example.ca/confirmrecovery/bb1b9928932', @@ -167,11 +166,11 @@ class RecoveryContactTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import RecoveryContact + from acme.challenges import RecoveryContact self.assertEqual(self.msg, RecoveryContact.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import RecoveryContact + from acme.challenges import RecoveryContact hash(RecoveryContact.from_json(self.jmsg)) def test_json_without_optionals(self): @@ -179,7 +178,7 @@ class RecoveryContactTest(unittest.TestCase): del self.jmsg['successURL'] del self.jmsg['contact'] - from letsencrypt.acme.challenges import RecoveryContact + from acme.challenges import RecoveryContact msg = RecoveryContact.from_json(self.jmsg) self.assertTrue(msg.activation_url is None) @@ -191,7 +190,7 @@ class RecoveryContactTest(unittest.TestCase): class RecoveryContactResponseTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import RecoveryContactResponse + from acme.challenges import RecoveryContactResponse self.msg = RecoveryContactResponse(token='23029d88d9e123e') self.jmsg = {'type': 'recoveryContact', 'token': '23029d88d9e123e'} @@ -199,18 +198,18 @@ class RecoveryContactResponseTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import RecoveryContactResponse + from acme.challenges import RecoveryContactResponse self.assertEqual( self.msg, RecoveryContactResponse.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import RecoveryContactResponse + from acme.challenges import RecoveryContactResponse hash(RecoveryContactResponse.from_json(self.jmsg)) def test_json_without_optionals(self): del self.jmsg['token'] - from letsencrypt.acme.challenges import RecoveryContactResponse + from acme.challenges import RecoveryContactResponse msg = RecoveryContactResponse.from_json(self.jmsg) self.assertTrue(msg.token is None) @@ -220,7 +219,7 @@ class RecoveryContactResponseTest(unittest.TestCase): class RecoveryTokenTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import RecoveryToken + from acme.challenges import RecoveryToken self.msg = RecoveryToken() self.jmsg = {'type': 'recoveryToken'} @@ -228,18 +227,18 @@ class RecoveryTokenTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import RecoveryToken + from acme.challenges import RecoveryToken self.assertEqual(self.msg, RecoveryToken.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import RecoveryToken + from acme.challenges import RecoveryToken hash(RecoveryToken.from_json(self.jmsg)) class RecoveryTokenResponseTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import RecoveryTokenResponse + from acme.challenges import RecoveryTokenResponse self.msg = RecoveryTokenResponse(token='23029d88d9e123e') self.jmsg = {'type': 'recoveryToken', 'token': '23029d88d9e123e'} @@ -247,18 +246,18 @@ class RecoveryTokenResponseTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import RecoveryTokenResponse + from acme.challenges import RecoveryTokenResponse self.assertEqual( self.msg, RecoveryTokenResponse.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import RecoveryTokenResponse + from acme.challenges import RecoveryTokenResponse hash(RecoveryTokenResponse.from_json(self.jmsg)) def test_json_without_optionals(self): del self.jmsg['token'] - from letsencrypt.acme.challenges import RecoveryTokenResponse + from acme.challenges import RecoveryTokenResponse msg = RecoveryTokenResponse.from_json(self.jmsg) self.assertTrue(msg.token is None) @@ -282,7 +281,7 @@ class ProofOfPossessionHintsTest(unittest.TestCase): authorized_for = ('www.example.com', 'example.net') serial_numbers = (34234239832, 23993939911, 17) - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession self.msg = ProofOfPossession.Hints( jwk=jwk, issuers=issuers, cert_fingerprints=cert_fingerprints, certs=(CERT,), subject_key_identifiers=subject_key_identifiers, @@ -304,12 +303,12 @@ class ProofOfPossessionHintsTest(unittest.TestCase): self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession self.assertEqual( self.msg, ProofOfPossession.Hints.from_json(self.jmsg_from)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession hash(ProofOfPossession.Hints.from_json(self.jmsg_from)) def test_json_without_optionals(self): @@ -318,7 +317,7 @@ class ProofOfPossessionHintsTest(unittest.TestCase): del self.jmsg_from[optional] del self.jmsg_to[optional] - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession msg = ProofOfPossession.Hints.from_json(self.jmsg_from) self.assertEqual(msg.cert_fingerprints, ()) @@ -334,7 +333,7 @@ class ProofOfPossessionHintsTest(unittest.TestCase): class ProofOfPossessionTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession hints = ProofOfPossession.Hints( jwk=jose.JWKRSA(key=KEY.publickey()), cert_fingerprints=(), certs=(), serial_numbers=(), subject_key_identifiers=(), @@ -360,12 +359,12 @@ class ProofOfPossessionTest(unittest.TestCase): self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession self.assertEqual( self.msg, ProofOfPossession.from_json(self.jmsg_from)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import ProofOfPossession + from acme.challenges import ProofOfPossession hash(ProofOfPossession.from_json(self.jmsg_from)) @@ -384,7 +383,7 @@ class ProofOfPossessionResponseTest(unittest.TestCase): nonce='\x99\xc7Q\xb3f2\xbc\xdci\xfe\xd6\x98k\xc67\xdf', ) - from letsencrypt.acme.challenges import ProofOfPossessionResponse + from acme.challenges import ProofOfPossessionResponse self.msg = ProofOfPossessionResponse( nonce='xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ', signature=signature) @@ -407,19 +406,19 @@ class ProofOfPossessionResponseTest(unittest.TestCase): self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import ProofOfPossessionResponse + from acme.challenges import ProofOfPossessionResponse self.assertEqual( self.msg, ProofOfPossessionResponse.from_json(self.jmsg_from)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import ProofOfPossessionResponse + from acme.challenges import ProofOfPossessionResponse hash(ProofOfPossessionResponse.from_json(self.jmsg_from)) class DNSTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import DNS + from acme.challenges import DNS self.msg = DNS(token='17817c66b60ce2e4012dfad92657527a') self.jmsg = {'type': 'dns', 'token': '17817c66b60ce2e4012dfad92657527a'} @@ -427,18 +426,18 @@ class DNSTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import DNS + from acme.challenges import DNS self.assertEqual(self.msg, DNS.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import DNS + from acme.challenges import DNS hash(DNS.from_json(self.jmsg)) class DNSResponseTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.challenges import DNSResponse + from acme.challenges import DNSResponse self.msg = DNSResponse() self.jmsg = {'type': 'dns'} @@ -446,11 +445,11 @@ class DNSResponseTest(unittest.TestCase): self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.challenges import DNSResponse + from acme.challenges import DNSResponse self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg)) def test_from_json_hashable(self): - from letsencrypt.acme.challenges import DNSResponse + from acme.challenges import DNSResponse hash(DNSResponse.from_json(self.jmsg)) diff --git a/letsencrypt/acme/errors.py b/acme/errors.py similarity index 77% rename from letsencrypt/acme/errors.py rename to acme/errors.py index d69efda11..957e781af 100644 --- a/letsencrypt/acme/errors.py +++ b/acme/errors.py @@ -1,5 +1,5 @@ """ACME errors.""" -from letsencrypt.acme.jose import errors as jose_errors +from acme.jose import errors as jose_errors class Error(Exception): """Generic ACME error.""" diff --git a/letsencrypt/acme/fields.py b/acme/fields.py similarity index 94% rename from letsencrypt/acme/fields.py rename to acme/fields.py index f001f1cd5..3af336fe5 100644 --- a/letsencrypt/acme/fields.py +++ b/acme/fields.py @@ -1,7 +1,7 @@ """ACME JSON fields.""" import pyrfc3339 -from letsencrypt.acme import jose +from acme import jose class RFC3339Field(jose.Field): diff --git a/letsencrypt/acme/fields_test.py b/acme/fields_test.py similarity index 69% rename from letsencrypt/acme/fields_test.py rename to acme/fields_test.py index 204849408..340e0131e 100644 --- a/letsencrypt/acme/fields_test.py +++ b/acme/fields_test.py @@ -1,35 +1,35 @@ -"""Tests for letsencrypt.acme.fields.""" +"""Tests for acme.fields.""" import datetime import unittest import pytz -from letsencrypt.acme import jose +from acme import jose class RFC3339FieldTest(unittest.TestCase): - """Tests for letsencrypt.acme.fields.RFC3339Field.""" + """Tests for acme.fields.RFC3339Field.""" def setUp(self): self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc) self.encoded = '2015-03-27T00:00:00Z' def test_default_encoder(self): - from letsencrypt.acme.fields import RFC3339Field + from acme.fields import RFC3339Field self.assertEqual( self.encoded, RFC3339Field.default_encoder(self.decoded)) def test_default_encoder_naive_fails(self): - from letsencrypt.acme.fields import RFC3339Field + from acme.fields import RFC3339Field self.assertRaises( ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) def test_default_decoder(self): - from letsencrypt.acme.fields import RFC3339Field + from acme.fields import RFC3339Field self.assertEqual( self.decoded, RFC3339Field.default_decoder(self.encoded)) def test_default_decoder_raises_deserialization_error(self): - from letsencrypt.acme.fields import RFC3339Field + from acme.fields import RFC3339Field self.assertRaises( jose.DeserializationError, RFC3339Field.default_decoder, '') diff --git a/letsencrypt/acme/jose/__init__.py b/acme/jose/__init__.py similarity index 77% rename from letsencrypt/acme/jose/__init__.py rename to acme/jose/__init__.py index 20f9ba7d3..db3258a3d 100644 --- a/letsencrypt/acme/jose/__init__.py +++ b/acme/jose/__init__.py @@ -22,21 +22,21 @@ particular the following RFCs: https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-signature/ """ -from letsencrypt.acme.jose.b64 import ( +from acme.jose.b64 import ( b64decode, b64encode, ) -from letsencrypt.acme.jose.errors import ( +from acme.jose.errors import ( DeserializationError, SerializationError, Error, UnrecognizedTypeError, ) -from letsencrypt.acme.jose.interfaces import JSONDeSerializable +from acme.jose.interfaces import JSONDeSerializable -from letsencrypt.acme.jose.json_util import ( +from acme.jose.json_util import ( Field, JSONObjectWithFields, TypedJSONObjectWithFields, @@ -48,7 +48,7 @@ from letsencrypt.acme.jose.json_util import ( encode_csr, ) -from letsencrypt.acme.jose.jwa import ( +from acme.jose.jwa import ( HS256, HS384, HS512, @@ -61,14 +61,14 @@ from letsencrypt.acme.jose.jwa import ( RS512, ) -from letsencrypt.acme.jose.jwk import ( +from acme.jose.jwk import ( JWK, JWKRSA, ) -from letsencrypt.acme.jose.jws import JWS +from acme.jose.jws import JWS -from letsencrypt.acme.jose.util import ( +from acme.jose.util import ( ComparableX509, HashableRSAKey, ImmutableMap, diff --git a/letsencrypt/acme/jose/b64.py b/acme/jose/b64.py similarity index 100% rename from letsencrypt/acme/jose/b64.py rename to acme/jose/b64.py diff --git a/letsencrypt/acme/jose/b64_test.py b/acme/jose/b64_test.py similarity index 87% rename from letsencrypt/acme/jose/b64_test.py rename to acme/jose/b64_test.py index 89ff27f5d..0e2a726fe 100644 --- a/letsencrypt/acme/jose/b64_test.py +++ b/acme/jose/b64_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.jose.b64.""" +"""Tests for acme.jose.b64.""" import unittest @@ -19,11 +19,11 @@ B64_URL_UNSAFE_EXAMPLES = { class B64EncodeTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.b64.b64encode.""" + """Tests for acme.jose.b64.b64encode.""" @classmethod def _call(cls, data): - from letsencrypt.acme.jose.b64 import b64encode + from acme.jose.b64 import b64encode return b64encode(data) def test_unsafe_url(self): @@ -39,11 +39,11 @@ class B64EncodeTest(unittest.TestCase): class B64DecodeTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.b64.b64decode.""" + """Tests for acme.jose.b64.b64decode.""" @classmethod def _call(cls, data): - from letsencrypt.acme.jose.b64 import b64decode + from acme.jose.b64 import b64decode return b64decode(data) def test_unsafe_url(self): diff --git a/letsencrypt/acme/jose/errors.py b/acme/jose/errors.py similarity index 100% rename from letsencrypt/acme/jose/errors.py rename to acme/jose/errors.py diff --git a/letsencrypt/acme/jose/errors_test.py b/acme/jose/errors_test.py similarity index 75% rename from letsencrypt/acme/jose/errors_test.py rename to acme/jose/errors_test.py index dd6af6c1a..cafe16e8d 100644 --- a/letsencrypt/acme/jose/errors_test.py +++ b/acme/jose/errors_test.py @@ -1,10 +1,10 @@ -"""Tests for letsencrypt.acme.jose.errors.""" +"""Tests for acme.jose.errors.""" import unittest class UnrecognizedTypeErrorTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.jose.errors import UnrecognizedTypeError + from acme.jose.errors import UnrecognizedTypeError self.error = UnrecognizedTypeError('foo', {'type': 'foo'}) def test_str(self): diff --git a/letsencrypt/acme/jose/interfaces.py b/acme/jose/interfaces.py similarity index 96% rename from letsencrypt/acme/jose/interfaces.py rename to acme/jose/interfaces.py index 8e06f99f9..27dcf863f 100644 --- a/letsencrypt/acme/jose/interfaces.py +++ b/acme/jose/interfaces.py @@ -3,7 +3,7 @@ import abc import collections import json -from letsencrypt.acme.jose import util +from acme.jose import util # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class # pylint: disable=too-few-public-methods @@ -110,7 +110,7 @@ class JSONDeSerializable(object): # in particular... assert Bar().to_partial_json() != ['foo', 'foo'] - :raises letsencrypt.acme.jose.errors.SerializationError: + :raises acme.jose.errors.SerializationError: in case of any serialization error. :returns: Partially serializable object. @@ -125,7 +125,7 @@ class JSONDeSerializable(object): assert Bar().to_json() == ['foo', 'foo'] - :raises letsencrypt.acme.jose.errors.SerializationError: + :raises acme.jose.errors.SerializationError: in case of any serialization error. :returns: Fully serialized object. @@ -157,7 +157,7 @@ class JSONDeSerializable(object): types, as decoded from JSON document. Not necessarily :class:`dict` (as decoded from "JSON object" document). - :raises letsencrypt.acme.jose.errors.DeserializationError: + :raises acme.jose.errors.DeserializationError: if decoding was unsuccessful, e.g. in case of unparseable X509 certificate, or wrong padding in JOSE base64 encoded string, etc. diff --git a/letsencrypt/acme/jose/interfaces_test.py b/acme/jose/interfaces_test.py similarity index 90% rename from letsencrypt/acme/jose/interfaces_test.py rename to acme/jose/interfaces_test.py index 4c0fc6eb9..8e4d415ef 100644 --- a/letsencrypt/acme/jose/interfaces_test.py +++ b/acme/jose/interfaces_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.jose.interfaces.""" +"""Tests for acme.jose.interfaces.""" import unittest @@ -6,7 +6,7 @@ class JSONDeSerializableTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes def setUp(self): - from letsencrypt.acme.jose.interfaces import JSONDeSerializable + from acme.jose.interfaces import JSONDeSerializable # pylint: disable=missing-docstring,invalid-name @@ -76,7 +76,7 @@ class JSONDeSerializableTest(unittest.TestCase): self.assertEqual(self.tuple.to_json(), (('foo', ))) def test_from_json_not_implemented(self): - from letsencrypt.acme.jose.interfaces import JSONDeSerializable + from acme.jose.interfaces import JSONDeSerializable self.assertRaises(TypeError, JSONDeSerializable.from_json, 'xxx') def test_json_loads(self): @@ -95,7 +95,7 @@ class JSONDeSerializableTest(unittest.TestCase): self.seq.json_dumps_pretty(), '[\n "foo1",\n "foo2"\n]') def test_json_dump_default(self): - from letsencrypt.acme.jose.interfaces import JSONDeSerializable + from acme.jose.interfaces import JSONDeSerializable self.assertEqual( 'foo1', JSONDeSerializable.json_dump_default(self.basic1)) @@ -106,7 +106,7 @@ class JSONDeSerializableTest(unittest.TestCase): self.assertTrue(jobj[1] is self.basic2) def test_json_dump_default_type_error(self): - from letsencrypt.acme.jose.interfaces import JSONDeSerializable + from acme.jose.interfaces import JSONDeSerializable self.assertRaises( TypeError, JSONDeSerializable.json_dump_default, object()) diff --git a/letsencrypt/acme/jose/json_util.py b/acme/jose/json_util.py similarity index 95% rename from letsencrypt/acme/jose/json_util.py rename to acme/jose/json_util.py index ac8cdf7aa..0c91c3412 100644 --- a/letsencrypt/acme/jose/json_util.py +++ b/acme/jose/json_util.py @@ -12,10 +12,10 @@ import logging import M2Crypto -from letsencrypt.acme.jose import b64 -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import interfaces -from letsencrypt.acme.jose import util +from acme.jose import b64 +from acme.jose import errors +from acme.jose import interfaces +from acme.jose import util class Field(object): @@ -27,8 +27,8 @@ class Field(object): ``encoder`` (``decoder``) is a callable that accepts a single parameter, i.e. a value to be encoded (decoded), and returns the serialized (deserialized) value. In case of errors it should raise - :class:`~letsencrypt.acme.jose.errors.SerializationError` - (:class:`~letsencrypt.acme.jose.errors.DeserializationError`). + :class:`~acme.jose.errors.SerializationError` + (:class:`~acme.jose.errors.DeserializationError`). Note, that ``decoder`` should perform partial serialization only. @@ -96,7 +96,7 @@ class Field(object): """Default decoder. Recursively deserialize into immutable types ( - :class:`letsencrypt.acme.jose.util.frozendict` instead of + :class:`acme.jose.util.frozendict` instead of :func:`dict`, :func:`tuple` instead of :func:`list`). """ @@ -304,7 +304,7 @@ def encode_cert(cert): """Encode certificate as JOSE Base-64 DER. :param cert: Certificate. - :type cert: :class:`letsencrypt.acme.jose.util.ComparableX509` + :type cert: :class:`acme.jose.util.ComparableX509` """ return b64.b64encode(cert.as_der()) @@ -381,7 +381,7 @@ class TypedJSONObjectWithFields(JSONObjectWithFields): :returns: Serializable JSON object representing ACME typed object. :meth:`validate` will almost certainly not work, due to reasons - explained in :class:`letsencrypt.acme.interfaces.IJSONSerializable`. + explained in :class:`acme.interfaces.IJSONSerializable`. :rtype: dict """ @@ -393,7 +393,7 @@ class TypedJSONObjectWithFields(JSONObjectWithFields): def from_json(cls, jobj): """Deserialize ACME object from valid JSON object. - :raises letsencrypt.acme.errors.UnrecognizedTypeError: if type + :raises acme.errors.UnrecognizedTypeError: if type of the ACME object has not been registered. """ diff --git a/letsencrypt/acme/jose/json_util_test.py b/acme/jose/json_util_test.py similarity index 87% rename from letsencrypt/acme/jose/json_util_test.py rename to acme/jose/json_util_test.py index 88818ed07..42113279e 100644 --- a/letsencrypt/acme/jose/json_util_test.py +++ b/acme/jose/json_util_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.jose.json_util.""" +"""Tests for acme.jose.json_util.""" import os import pkg_resources import unittest @@ -6,9 +6,9 @@ import unittest import M2Crypto import mock -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import interfaces -from letsencrypt.acme.jose import util +from acme.jose import errors +from acme.jose import interfaces +from acme.jose import util CERT = M2Crypto.X509.load_cert(pkg_resources.resource_filename( @@ -18,7 +18,7 @@ CSR = M2Crypto.X509.load_request(pkg_resources.resource_filename( class FieldTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.json_util.Field.""" + """Tests for acme.jose.json_util.Field.""" def test_descriptors(self): mock_value = mock.MagicMock() @@ -31,7 +31,7 @@ class FieldTest(unittest.TestCase): def encoder(unused_value): return 'e' - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field field = Field('foo') field = field.encoder(encoder) @@ -51,39 +51,39 @@ class FieldTest(unittest.TestCase): pass mock_field = MockField() - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field self.assertTrue(Field.default_encoder(mock_field) is mock_field) # in particular... self.assertNotEqual('foo', Field.default_encoder(mock_field)) def test_default_encoder_passthrough(self): mock_value = mock.MagicMock() - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field self.assertTrue(Field.default_encoder(mock_value) is mock_value) def test_default_decoder_list_to_tuple(self): - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field self.assertEqual((1, 2, 3), Field.default_decoder([1, 2, 3])) def test_default_decoder_dict_to_frozendict(self): - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field obj = Field.default_decoder({'x': 2}) self.assertTrue(isinstance(obj, util.frozendict)) self.assertEqual(obj, util.frozendict(x=2)) def test_default_decoder_passthrough(self): mock_value = mock.MagicMock() - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import Field self.assertTrue(Field.default_decoder(mock_value) is mock_value) class JSONObjectWithFieldsTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.json_util.JSONObjectWithFields.""" + """Tests for acme.jose.json_util.JSONObjectWithFields.""" # pylint: disable=protected-access def setUp(self): - from letsencrypt.acme.jose.json_util import JSONObjectWithFields - from letsencrypt.acme.jose.json_util import Field + from acme.jose.json_util import JSONObjectWithFields + from acme.jose.json_util import Field class MockJSONObjectWithFields(JSONObjectWithFields): # pylint: disable=invalid-name,missing-docstring,no-self-argument @@ -185,11 +185,11 @@ class DeEncodersTest(unittest.TestCase): ) def test_decode_b64_jose_padding_error(self): - from letsencrypt.acme.jose.json_util import decode_b64jose + from acme.jose.json_util import decode_b64jose self.assertRaises(errors.DeserializationError, decode_b64jose, 'x') def test_decode_b64_jose_size(self): - from letsencrypt.acme.jose.json_util import decode_b64jose + from acme.jose.json_util import decode_b64jose self.assertEqual('foo', decode_b64jose('Zm9v', size=3)) self.assertRaises( errors.DeserializationError, decode_b64jose, 'Zm9v', size=2) @@ -197,44 +197,44 @@ class DeEncodersTest(unittest.TestCase): errors.DeserializationError, decode_b64jose, 'Zm9v', size=4) def test_decode_b64_jose_minimum_size(self): - from letsencrypt.acme.jose.json_util import decode_b64jose + from acme.jose.json_util import decode_b64jose self.assertEqual('foo', decode_b64jose('Zm9v', size=3, minimum=True)) self.assertEqual('foo', decode_b64jose('Zm9v', size=2, minimum=True)) self.assertRaises(errors.DeserializationError, decode_b64jose, 'Zm9v', size=4, minimum=True) def test_decode_hex16(self): - from letsencrypt.acme.jose.json_util import decode_hex16 + from acme.jose.json_util import decode_hex16 self.assertEqual('foo', decode_hex16('666f6f')) def test_decode_hex16_minimum_size(self): - from letsencrypt.acme.jose.json_util import decode_hex16 + from acme.jose.json_util import decode_hex16 self.assertEqual('foo', decode_hex16('666f6f', size=3, minimum=True)) self.assertEqual('foo', decode_hex16('666f6f', size=2, minimum=True)) self.assertRaises(errors.DeserializationError, decode_hex16, '666f6f', size=4, minimum=True) def test_decode_hex16_odd_length(self): - from letsencrypt.acme.jose.json_util import decode_hex16 + from acme.jose.json_util import decode_hex16 self.assertRaises(errors.DeserializationError, decode_hex16, 'x') def test_encode_cert(self): - from letsencrypt.acme.jose.json_util import encode_cert + from acme.jose.json_util import encode_cert self.assertEqual(self.b64_cert, encode_cert(CERT)) def test_decode_cert(self): - from letsencrypt.acme.jose.json_util import decode_cert + from acme.jose.json_util import decode_cert cert = decode_cert(self.b64_cert) self.assertTrue(isinstance(cert, util.ComparableX509)) self.assertEqual(cert, CERT) self.assertRaises(errors.DeserializationError, decode_cert, '') def test_encode_csr(self): - from letsencrypt.acme.jose.json_util import encode_csr + from acme.jose.json_util import encode_csr self.assertEqual(self.b64_cert, encode_csr(CERT)) def test_decode_csr(self): - from letsencrypt.acme.jose.json_util import decode_csr + from acme.jose.json_util import decode_csr csr = decode_csr(self.b64_csr) self.assertTrue(isinstance(csr, util.ComparableX509)) self.assertEqual(csr, CSR) @@ -244,7 +244,7 @@ class DeEncodersTest(unittest.TestCase): class TypedJSONObjectWithFieldsTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.jose.json_util import TypedJSONObjectWithFields + from acme.jose.json_util import TypedJSONObjectWithFields # pylint: disable=missing-docstring,abstract-method # pylint: disable=too-few-public-methods diff --git a/letsencrypt/acme/jose/jwa.py b/acme/jose/jwa.py similarity index 96% rename from letsencrypt/acme/jose/jwa.py rename to acme/jose/jwa.py index b1f058d77..97c770b78 100644 --- a/letsencrypt/acme/jose/jwa.py +++ b/acme/jose/jwa.py @@ -13,9 +13,9 @@ from Crypto.Hash import SHA512 from Crypto.Signature import PKCS1_PSS from Crypto.Signature import PKCS1_v1_5 -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import interfaces -from letsencrypt.acme.jose import jwk +from acme.jose import errors +from acme.jose import interfaces +from acme.jose import jwk class JWA(interfaces.JSONDeSerializable): # pylint: disable=abstract-method diff --git a/letsencrypt/acme/jose/jwa_test.py b/acme/jose/jwa_test.py similarity index 83% rename from letsencrypt/acme/jose/jwa_test.py rename to acme/jose/jwa_test.py index 48fdfce0d..083cd0b73 100644 --- a/letsencrypt/acme/jose/jwa_test.py +++ b/acme/jose/jwa_test.py @@ -1,11 +1,11 @@ -"""Tests for letsencrypt.acme.jose.jwa.""" +"""Tests for acme.jose.jwa.""" import os import pkg_resources import unittest from Crypto.PublicKey import RSA -from letsencrypt.acme.jose import errors +from acme.jose import errors RSA256_KEY = RSA.importKey(pkg_resources.resource_string( @@ -17,10 +17,10 @@ RSA1024_KEY = RSA.importKey(pkg_resources.resource_string( class JWASignatureTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jwa.JWASignature.""" + """Tests for acme.jose.jwa.JWASignature.""" def setUp(self): - from letsencrypt.acme.jose.jwa import JWASignature + from acme.jose.jwa import JWASignature class MockSig(JWASignature): # pylint: disable=missing-docstring,too-few-public-methods @@ -48,15 +48,15 @@ class JWASignatureTest(unittest.TestCase): self.assertEqual(self.Sig2.to_partial_json(), 'Sig2') def test_from_json(self): - from letsencrypt.acme.jose.jwa import JWASignature - from letsencrypt.acme.jose.jwa import RS256 + from acme.jose.jwa import JWASignature + from acme.jose.jwa import RS256 self.assertTrue(JWASignature.from_json('RS256') is RS256) class JWAHSTest(unittest.TestCase): # pylint: disable=too-few-public-methods def test_it(self): - from letsencrypt.acme.jose.jwa import HS256 + from acme.jose.jwa import HS256 sig = ( "\xceR\xea\xcd\x94\xab\xcf\xfb\xe0\xacA.:\x1a'\x08i\xe2\xc4" "\r\x85+\x0e\x85\xaeUZ\xd4\xb3\x97zO" @@ -69,19 +69,19 @@ class JWAHSTest(unittest.TestCase): # pylint: disable=too-few-public-methods class JWARSTest(unittest.TestCase): def test_sign_no_private_part(self): - from letsencrypt.acme.jose.jwa import RS256 + from acme.jose.jwa import RS256 self.assertRaises( errors.Error, RS256.sign, RSA512_KEY.publickey(), 'foo') def test_sign_key_too_small(self): - from letsencrypt.acme.jose.jwa import RS256 - from letsencrypt.acme.jose.jwa import PS256 + from acme.jose.jwa import RS256 + from acme.jose.jwa import PS256 self.assertRaises(errors.Error, RS256.sign, RSA256_KEY, 'foo') self.assertRaises(errors.Error, PS256.sign, RSA256_KEY, 'foo') self.assertRaises(errors.Error, PS256.sign, RSA512_KEY, 'foo') def test_rs(self): - from letsencrypt.acme.jose.jwa import RS256 + from acme.jose.jwa import RS256 sig = ( '|\xc6\xb2\xa4\xab(\x87\x99\xfa*:\xea\xf8\xa0N&}\x9f\x0f\xc0O' '\xc6t\xa3\xe6\xfa\xbb"\x15Y\x80Y\xe0\x81\xb8\x88)\xba\x0c\x9c' @@ -95,7 +95,7 @@ class JWARSTest(unittest.TestCase): self.assertFalse(RS256.verify(RSA512_KEY, 'foo', sig + '!') is False) def test_ps(self): - from letsencrypt.acme.jose.jwa import PS256 + from acme.jose.jwa import PS256 sig = PS256.sign(RSA1024_KEY, 'foo') self.assertTrue(PS256.verify(RSA1024_KEY, 'foo', sig) is True) self.assertTrue(PS256.verify(RSA1024_KEY, 'foo', sig + '!') is False) diff --git a/letsencrypt/acme/jose/jwk.py b/acme/jose/jwk.py similarity index 95% rename from letsencrypt/acme/jose/jwk.py rename to acme/jose/jwk.py index ec35baa18..7c55e99a8 100644 --- a/letsencrypt/acme/jose/jwk.py +++ b/acme/jose/jwk.py @@ -4,10 +4,10 @@ import binascii import Crypto.PublicKey.RSA -from letsencrypt.acme.jose import b64 -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import json_util -from letsencrypt.acme.jose import util +from acme.jose import b64 +from acme.jose import errors +from acme.jose import json_util +from acme.jose import util class JWK(json_util.TypedJSONObjectWithFields): diff --git a/letsencrypt/acme/jose/jwk_test.py b/acme/jose/jwk_test.py similarity index 81% rename from letsencrypt/acme/jose/jwk_test.py rename to acme/jose/jwk_test.py index 1328528e8..7b942eca1 100644 --- a/letsencrypt/acme/jose/jwk_test.py +++ b/acme/jose/jwk_test.py @@ -1,12 +1,12 @@ -"""Tests for letsencrypt.acme.jose.jwk.""" +"""Tests for acme.jose.jwk.""" import os import pkg_resources import unittest from Crypto.PublicKey import RSA -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import util +from acme.jose import errors +from acme.jose import util RSA256_KEY = util.HashableRSAKey(RSA.importKey(pkg_resources.resource_string( @@ -16,10 +16,10 @@ RSA512_KEY = util.HashableRSAKey(RSA.importKey(pkg_resources.resource_string( class JWKOctTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jwk.JWKOct.""" + """Tests for acme.jose.jwk.JWKOct.""" def setUp(self): - from letsencrypt.acme.jose.jwk import JWKOct + from acme.jose.jwk import JWKOct self.jwk = JWKOct(key='foo') self.jobj = {'kty': 'oct', 'k': 'foo'} @@ -27,15 +27,15 @@ class JWKOctTest(unittest.TestCase): self.assertEqual(self.jwk.to_partial_json(), self.jobj) def test_from_json(self): - from letsencrypt.acme.jose.jwk import JWKOct + from acme.jose.jwk import JWKOct self.assertEqual(self.jwk, JWKOct.from_json(self.jobj)) def test_from_json_hashable(self): - from letsencrypt.acme.jose.jwk import JWKOct + from acme.jose.jwk import JWKOct hash(JWKOct.from_json(self.jobj)) def test_load(self): - from letsencrypt.acme.jose.jwk import JWKOct + from acme.jose.jwk import JWKOct self.assertEqual(self.jwk, JWKOct.load('foo')) def test_public(self): @@ -43,10 +43,10 @@ class JWKOctTest(unittest.TestCase): class JWKRSATest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jwk.JWKRSA.""" + """Tests for acme.jose.jwk.JWKRSA.""" def setUp(self): - from letsencrypt.acme.jose.jwk import JWKRSA + from acme.jose.jwk import JWKRSA self.jwk256 = JWKRSA(key=RSA256_KEY.publickey()) self.jwk256_private = JWKRSA(key=RSA256_KEY) self.jwk256json = { @@ -71,7 +71,7 @@ class JWKRSATest(unittest.TestCase): self.assertNotEqual(self.jwk512, self.jwk256) def test_load(self): - from letsencrypt.acme.jose.jwk import JWKRSA + from acme.jose.jwk import JWKRSA self.assertEqual( JWKRSA(key=util.HashableRSAKey(RSA256_KEY)), JWKRSA.load( pkg_resources.resource_string( @@ -85,18 +85,18 @@ class JWKRSATest(unittest.TestCase): self.assertEqual(self.jwk512.to_partial_json(), self.jwk512json) def test_from_json(self): - from letsencrypt.acme.jose.jwk import JWK + from acme.jose.jwk import JWK self.assertEqual(self.jwk256, JWK.from_json(self.jwk256json)) # TODO: fix schemata to allow RSA512 #self.assertEqual(self.jwk512, JWK.from_json(self.jwk512json)) def test_from_json_hashable(self): - from letsencrypt.acme.jose.jwk import JWK + from acme.jose.jwk import JWK hash(JWK.from_json(self.jwk256json)) def test_from_json_non_schema_errors(self): # valid against schema, but still failing - from letsencrypt.acme.jose.jwk import JWK + from acme.jose.jwk import JWK self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'RSA', 'e': 'AQAB', 'n': ''}) self.assertRaises(errors.DeserializationError, JWK.from_json, diff --git a/letsencrypt/acme/jose/jws.py b/acme/jose/jws.py similarity index 96% rename from letsencrypt/acme/jose/jws.py rename to acme/jose/jws.py index fc37227fd..06923e145 100644 --- a/letsencrypt/acme/jose/jws.py +++ b/acme/jose/jws.py @@ -5,12 +5,12 @@ import sys import M2Crypto -from letsencrypt.acme.jose import b64 -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import json_util -from letsencrypt.acme.jose import jwa -from letsencrypt.acme.jose import jwk -from letsencrypt.acme.jose import util +from acme.jose import b64 +from acme.jose import errors +from acme.jose import json_util +from acme.jose import jwa +from acme.jose import jwk +from acme.jose import util class MediaType(object): @@ -103,9 +103,9 @@ class Header(json_util.JSONObjectWithFields): .. todo:: Supports only "jwk" header parameter lookup. :returns: (Public) key found in the header. - :rtype: :class:`letsencrypt.acme.jose.jwk.JWK` + :rtype: :class:`acme.jose.jwk.JWK` - :raises letsencrypt.acme.jose.errors.Error: if key could not be found + :raises acme.jose.errors.Error: if key could not be found """ if self.jwk is None: @@ -180,7 +180,7 @@ class Signature(json_util.JSONObjectWithFields): """Verify. :param key: Key used for verification. - :type key: :class:`letsencrypt.acme.jose.jwk.JWK` + :type key: :class:`acme.jose.jwk.JWK` """ key = self.combined.find_key() if key is None else key @@ -195,7 +195,7 @@ class Signature(json_util.JSONObjectWithFields): """Sign. :param key: Key for signature. - :type key: :class:`letsencrypt.acme.jose.jwk.JWK` + :type key: :class:`acme.jose.jwk.JWK` """ assert isinstance(key, alg.kty) @@ -241,8 +241,6 @@ class Signature(json_util.JSONObjectWithFields): class JWS(json_util.JSONObjectWithFields): """JSON Web Signature. - from letsencrypt.acme.jose import interfaces - :ivar str payload: JWS Payload. :ivar str signaturea: JWS Signatures. diff --git a/letsencrypt/acme/jose/jws_test.py b/acme/jose/jws_test.py similarity index 83% rename from letsencrypt/acme/jose/jws_test.py rename to acme/jose/jws_test.py index dca61c3d9..736391e4c 100644 --- a/letsencrypt/acme/jose/jws_test.py +++ b/acme/jose/jws_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.jose.jws.""" +"""Tests for acme.jose.jws.""" import base64 import os import pkg_resources @@ -8,11 +8,11 @@ import Crypto.PublicKey.RSA import M2Crypto import mock -from letsencrypt.acme.jose import b64 -from letsencrypt.acme.jose import errors -from letsencrypt.acme.jose import jwa -from letsencrypt.acme.jose import jwk -from letsencrypt.acme.jose import util +from acme.jose import b64 +from acme.jose import errors +from acme.jose import jwa +from acme.jose import jwk +from acme.jose import util CERT = util.ComparableX509(M2Crypto.X509.load_cert( @@ -23,34 +23,34 @@ RSA512_KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( class MediaTypeTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jws.MediaType.""" + """Tests for acme.jose.jws.MediaType.""" def test_decode(self): - from letsencrypt.acme.jose.jws import MediaType + from acme.jose.jws import MediaType self.assertEqual('application/app', MediaType.decode('application/app')) self.assertEqual('application/app', MediaType.decode('app')) self.assertRaises( errors.DeserializationError, MediaType.decode, 'app;foo') def test_encode(self): - from letsencrypt.acme.jose.jws import MediaType + from acme.jose.jws import MediaType self.assertEqual('app', MediaType.encode('application/app')) self.assertEqual('application/app;foo', MediaType.encode('application/app;foo')) class HeaderTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jws.Header.""" + """Tests for acme.jose.jws.Header.""" def setUp(self): - from letsencrypt.acme.jose.jws import Header + from acme.jose.jws import Header self.header1 = Header(jwk='foo') self.header2 = Header(jwk='bar') self.crit = Header(crit=('a', 'b')) self.empty = Header() def test_add_non_empty(self): - from letsencrypt.acme.jose.jws import Header + from acme.jose.jws import Header self.assertEqual(Header(jwk='foo', crit=('a', 'b')), self.header1 + self.crit) @@ -65,12 +65,12 @@ class HeaderTest(unittest.TestCase): self.assertRaises(TypeError, self.header1.__add__, 'xxx') def test_crit_decode_always_errors(self): - from letsencrypt.acme.jose.jws import Header + from acme.jose.jws import Header self.assertRaises(errors.DeserializationError, Header.from_json, {'crit': ['a', 'b']}) def test_x5c_decoding(self): - from letsencrypt.acme.jose.jws import Header + from acme.jose.jws import Header header = Header(x5c=(CERT, CERT)) jobj = header.to_partial_json() cert_b64 = base64.b64encode(CERT.as_der()) @@ -86,30 +86,30 @@ class HeaderTest(unittest.TestCase): class SignatureTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jws.Signature.""" + """Tests for acme.jose.jws.Signature.""" def test_from_json(self): - from letsencrypt.acme.jose.jws import Header - from letsencrypt.acme.jose.jws import Signature + from acme.jose.jws import Header + from acme.jose.jws import Signature self.assertEqual( Signature(signature='foo', header=Header(alg=jwa.RS256)), Signature.from_json( {'signature': 'Zm9v', 'header': {'alg': 'RS256'}})) def test_from_json_no_alg_error(self): - from letsencrypt.acme.jose.jws import Signature + from acme.jose.jws import Signature self.assertRaises(errors.DeserializationError, Signature.from_json, {'signature': 'foo'}) class JWSTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.jws.JWS.""" + """Tests for acme.jose.jws.JWS.""" def setUp(self): self.privkey = jwk.JWKRSA(key=RSA512_KEY) self.pubkey = self.privkey.public() - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.unprotected = JWS.sign( payload='foo', key=self.privkey, alg=jwa.RS256) self.protected = JWS.sign( @@ -140,7 +140,7 @@ class JWSTest(unittest.TestCase): '_893n1zQjpim_eLS5J1F61lkvrCrCDErTEJnBGOGesJ72M7b6Ve1cAJA', compact) - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS mixed = JWS.from_compact(compact) self.assertNotEqual(self.mixed, mixed) @@ -148,7 +148,7 @@ class JWSTest(unittest.TestCase): set(['alg']), set(mixed.signature.combined.not_omitted())) def test_from_compact_missing_components(self): - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.assertRaises(errors.DeserializationError, JWS.from_compact, '.') def test_json_omitempty(self): @@ -160,7 +160,7 @@ class JWSTest(unittest.TestCase): unprotected_jobj['header'] = unprotected_jobj['header'].to_json() - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.assertEqual(JWS.from_json(protected_jobj), self.protected) self.assertEqual(JWS.from_json(unprotected_jobj), self.unprotected) @@ -175,7 +175,7 @@ class JWSTest(unittest.TestCase): jobj_from['header'] = jobj_from['header'].to_json() self.assertEqual(self.mixed.to_partial_json(flat=True), jobj_to) - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.assertEqual(self.mixed, JWS.from_json(jobj_from)) def test_json_not_flat(self): @@ -187,16 +187,16 @@ class JWSTest(unittest.TestCase): jobj_from['signatures'] = [jobj_to['signatures'][0].to_json()] self.assertEqual(self.mixed.to_partial_json(flat=False), jobj_to) - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.assertEqual(self.mixed, JWS.from_json(jobj_from)) def test_from_json_mixed_flat(self): - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS self.assertRaises(errors.DeserializationError, JWS.from_json, {'signatures': (), 'signature': 'foo'}) def test_from_json_hashable(self): - from letsencrypt.acme.jose.jws import JWS + from acme.jose.jws import JWS hash(JWS.from_json(self.mixed.to_json())) @@ -207,14 +207,14 @@ class CLITest(unittest.TestCase): __name__, os.path.join('testdata', 'rsa512_key.pem')) def test_unverified(self): - from letsencrypt.acme.jose.jws import CLI + from acme.jose.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = '{"payload": "foo", "signature": "xxx"}' with mock.patch('sys.stdout'): self.assertEqual(-1, CLI.run(['verify'])) def test_json(self): - from letsencrypt.acme.jose.jws import CLI + from acme.jose.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' @@ -225,7 +225,7 @@ class CLITest(unittest.TestCase): self.assertEqual(0, CLI.run(['verify'])) def test_compact(self): - from letsencrypt.acme.jose.jws import CLI + from acme.jose.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' diff --git a/letsencrypt/acme/jose/testdata/README b/acme/jose/testdata/README similarity index 100% rename from letsencrypt/acme/jose/testdata/README rename to acme/jose/testdata/README diff --git a/letsencrypt/acme/jose/testdata/csr2.pem b/acme/jose/testdata/csr2.pem similarity index 100% rename from letsencrypt/acme/jose/testdata/csr2.pem rename to acme/jose/testdata/csr2.pem diff --git a/letsencrypt/acme/jose/testdata/rsa1024_key.pem b/acme/jose/testdata/rsa1024_key.pem similarity index 100% rename from letsencrypt/acme/jose/testdata/rsa1024_key.pem rename to acme/jose/testdata/rsa1024_key.pem diff --git a/letsencrypt/acme/jose/testdata/rsa256_key.pem b/acme/jose/testdata/rsa256_key.pem similarity index 100% rename from letsencrypt/acme/jose/testdata/rsa256_key.pem rename to acme/jose/testdata/rsa256_key.pem diff --git a/letsencrypt/acme/jose/testdata/rsa512_key.pem b/acme/jose/testdata/rsa512_key.pem similarity index 100% rename from letsencrypt/acme/jose/testdata/rsa512_key.pem rename to acme/jose/testdata/rsa512_key.pem diff --git a/letsencrypt/acme/jose/util.py b/acme/jose/util.py similarity index 100% rename from letsencrypt/acme/jose/util.py rename to acme/jose/util.py diff --git a/letsencrypt/acme/jose/util_test.py b/acme/jose/util_test.py similarity index 87% rename from letsencrypt/acme/jose/util_test.py rename to acme/jose/util_test.py index fc75497e0..1c179ee6b 100644 --- a/letsencrypt/acme/jose/util_test.py +++ b/acme/jose/util_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.jose.util.""" +"""Tests for acme.jose.util.""" import functools import os import pkg_resources @@ -8,10 +8,10 @@ import Crypto.PublicKey.RSA class HashableRSAKeyTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.util.HashableRSAKey.""" + """Tests for acme.jose.util.HashableRSAKey.""" def setUp(self): - from letsencrypt.acme.jose.util import HashableRSAKey + from acme.jose.util import HashableRSAKey self.key = HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( __name__, os.path.join('testdata', 'rsa256_key.pem')))) @@ -28,17 +28,17 @@ class HashableRSAKeyTest(unittest.TestCase): self.assertTrue(isinstance(hash(self.key), int)) def test_publickey(self): - from letsencrypt.acme.jose.util import HashableRSAKey + from acme.jose.util import HashableRSAKey self.assertTrue(isinstance(self.key.publickey(), HashableRSAKey)) class ImmutableMapTest(unittest.TestCase): - """Tests for letsencrypt.acme.jose.util.ImmutableMap.""" + """Tests for acme.jose.util.ImmutableMap.""" def setUp(self): # pylint: disable=invalid-name,too-few-public-methods # pylint: disable=missing-docstring - from letsencrypt.acme.jose.util import ImmutableMap + from acme.jose.util import ImmutableMap class A(ImmutableMap): __slots__ = ('x', 'y') @@ -101,18 +101,18 @@ class ImmutableMapTest(unittest.TestCase): class frozendictTest(unittest.TestCase): # pylint: disable=invalid-name - """Tests for letsencrypt.acme.jose.util.frozendict.""" + """Tests for acme.jose.util.frozendict.""" def setUp(self): - from letsencrypt.acme.jose.util import frozendict + from acme.jose.util import frozendict self.fdict = frozendict(x=1, y='2') def test_init_dict(self): - from letsencrypt.acme.jose.util import frozendict + from acme.jose.util import frozendict self.assertEqual(self.fdict, frozendict({'x': 1, 'y': '2'})) def test_init_other_raises_type_error(self): - from letsencrypt.acme.jose.util import frozendict + from acme.jose.util import frozendict # specifically fail for generators... self.assertRaises(TypeError, frozendict, {'a': 'b'}.iteritems()) diff --git a/letsencrypt/acme/messages.py b/acme/messages.py similarity index 89% rename from letsencrypt/acme/messages.py rename to acme/messages.py index 41b7389a7..6d46f894c 100644 --- a/letsencrypt/acme/messages.py +++ b/acme/messages.py @@ -22,11 +22,11 @@ """ import jsonschema -from letsencrypt.acme import challenges -from letsencrypt.acme import errors -from letsencrypt.acme import jose -from letsencrypt.acme import other -from letsencrypt.acme import util +from acme import challenges +from acme import errors +from acme import jose +from acme import other +from acme import util class Message(jose.TypedJSONObjectWithFields): @@ -41,7 +41,7 @@ class Message(jose.TypedJSONObjectWithFields): Subclasses must overrride it with a value that is acceptable by :func:`jsonschema.validate`, most probably using - :func:`letsencrypt.acme.util.load_schema`. + :func:`acme.util.load_schema`. """ @@ -53,10 +53,10 @@ class Message(jose.TypedJSONObjectWithFields): :param jobj: JSON object. - :raises letsencrypt.acme.errors.SchemaValidationError: if the input + :raises acme.errors.SchemaValidationError: if the input JSON object could not be validated against JSON schema specified in :attr:`schema`. - :raises letsencrypt.acme.jose.errors.DeserializationError: for any + :raises acme.jose.errors.DeserializationError: for any other generic error in decoding. :returns: instance of the class @@ -79,7 +79,7 @@ class Challenge(Message): :ivar str nonce: Random data, **not** base64-encoded. :ivar list challenges: List of - :class:`~letsencrypt.acme.challenges.Challenge` objects. + :class:`~acme.challenges.Challenge` objects. .. todo:: 1. can challenges contain two challenges of the same type? @@ -121,7 +121,7 @@ class ChallengeRequest(Message): class Authorization(Message): """ACME "authorization" message. - :ivar jwk: :class:`letsencrypt.acme.jose.JWK` + :ivar jwk: :class:`acme.jose.JWK` """ typ = "authorization" @@ -139,8 +139,8 @@ class AuthorizationRequest(Message): :ivar str nonce: Random data from the corresponding :attr:`Challenge.nonce`, **not** base64-encoded. :ivar list responses: List of completed challenges ( - :class:`letsencrypt.acme.challenges.ChallengeResponse`). - :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). + :class:`acme.challenges.ChallengeResponse`). + :ivar signature: Signature (:class:`acme.other.Signature`). """ typ = "authorizationRequest" @@ -184,7 +184,7 @@ class AuthorizationRequest(Message): """Verify signature. .. warning:: Caller must check that the public key encoded in the - :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object + :attr:`signature`'s :class:`acme.jose.JWK` object is the correct key for a given context. :param str name: Hostname @@ -202,10 +202,10 @@ class Certificate(Message): """ACME "certificate" message. :ivar certificate: The certificate (:class:`M2Crypto.X509.X509` - wrapped in :class:`letsencrypt.acme.util.ComparableX509`). + wrapped in :class:`acme.util.ComparableX509`). :ivar list chain: Chain of certificates (:class:`M2Crypto.X509.X509` - wrapped in :class:`letsencrypt.acme.util.ComparableX509` ). + wrapped in :class:`acme.util.ComparableX509` ). """ typ = "certificate" @@ -230,8 +230,8 @@ class CertificateRequest(Message): """ACME "certificateRequest" message. :ivar csr: Certificate Signing Request (:class:`M2Crypto.X509.Request` - wrapped in :class:`letsencrypt.acme.util.ComparableX509`. - :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). + wrapped in :class:`acme.util.ComparableX509`. + :ivar signature: Signature (:class:`acme.other.Signature`). """ typ = "certificateRequest" @@ -262,7 +262,7 @@ class CertificateRequest(Message): """Verify signature. .. warning:: Caller must check that the public key encoded in the - :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object + :attr:`signature`'s :class:`acme.jose.JWK` object is the correct key for a given context. :returns: True iff ``signature`` can be verified, False otherwise. @@ -316,8 +316,8 @@ class RevocationRequest(Message): """ACME "revocationRequest" message. :ivar certificate: Certificate (:class:`M2Crypto.X509.X509` - wrapped in :class:`letsencrypt.acme.util.ComparableX509`). - :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). + wrapped in :class:`acme.util.ComparableX509`). + :ivar signature: Signature (:class:`acme.other.Signature`). """ typ = "revocationRequest" @@ -348,7 +348,7 @@ class RevocationRequest(Message): """Verify signature. .. warning:: Caller must check that the public key encoded in the - :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object + :attr:`signature`'s :class:`acme.jose.JWK` object is the correct key for a given context. :returns: True iff ``signature`` can be verified, False otherwise. diff --git a/letsencrypt/acme/messages2.py b/acme/messages2.py similarity index 91% rename from letsencrypt/acme/messages2.py rename to acme/messages2.py index a2829ff57..419bb0b4e 100644 --- a/letsencrypt/acme/messages2.py +++ b/acme/messages2.py @@ -1,7 +1,7 @@ """ACME protocol messages.""" -from letsencrypt.acme import challenges -from letsencrypt.acme import fields -from letsencrypt.acme import jose +from acme import challenges +from acme import fields +from acme import jose class Error(jose.JSONObjectWithFields, Exception): @@ -101,7 +101,7 @@ IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder class Identifier(jose.JSONObjectWithFields): """ACME identifier. - :ivar letsencrypt.acme.messages2.IdentifierType typ: + :ivar acme.messages2.IdentifierType typ: """ typ = jose.Field('type', decoder=IdentifierType.from_json) @@ -111,7 +111,7 @@ class Identifier(jose.JSONObjectWithFields): class Resource(jose.ImmutableMap): """ACME Resource. - :ivar letsencrypt.acme.messages2.ResourceBody body: Resource body. + :ivar acme.messages2.ResourceBody body: Resource body. :ivar str uri: Location of the resource. """ @@ -125,7 +125,7 @@ class ResourceBody(jose.JSONObjectWithFields): class RegistrationResource(Resource): """Registration Resource. - :ivar letsencrypt.acme.messages2.Registration body: + :ivar acme.messages2.Registration body: :ivar str new_authzr_uri: URI found in the 'next' ``Link`` header :ivar str terms_of_service: URL for the CA TOS. @@ -136,7 +136,7 @@ class RegistrationResource(Resource): class Registration(ResourceBody): """Registration Resource Body. - :ivar letsencrypt.acme.jose.jwk.JWK key: Public key. + :ivar acme.jose.jwk.JWK key: Public key. :ivar tuple contact: Contact information following ACME spec """ @@ -151,7 +151,7 @@ class Registration(ResourceBody): class ChallengeResource(Resource, jose.JSONObjectWithFields): """Challenge Resource. - :ivar letsencrypt.acme.messages2.ChallengeBody body: + :ivar acme.messages2.ChallengeBody body: :ivar str authzr_uri: URI found in the 'up' ``Link`` header. """ @@ -173,10 +173,10 @@ class ChallengeBody(ResourceBody): such as ``challb`` to distinguish instances of this class from ``achall``. - :ivar letsencrypt.acme.challenges.Challenge: Wrapped challenge. + :ivar acme.challenges.Challenge: Wrapped challenge. Conveniently, all challenge fields are proxied, i.e. you can call ``challb.x`` to get ``challb.chall.x`` contents. - :ivar letsencrypt.acme.messages2.Status status: + :ivar acme.messages2.Status status: :ivar datetime.datetime validated: """ @@ -203,7 +203,7 @@ class ChallengeBody(ResourceBody): class AuthorizationResource(Resource): """Authorization Resource. - :ivar letsencrypt.acme.messages2.Authorization body: + :ivar acme.messages2.Authorization body: :ivar str new_cert_uri: URI found in the 'next' ``Link`` header """ @@ -213,13 +213,13 @@ class AuthorizationResource(Resource): class Authorization(ResourceBody): """Authorization Resource Body. - :ivar letsencrypt.acme.messages2.Identifier identifier: + :ivar acme.messages2.Identifier identifier: :ivar list challenges: `list` of `.ChallengeBody` :ivar tuple combinations: Challenge combinations (`tuple` of `tuple` of `int`, as opposed to `list` of `list` from the spec). - :ivar letsencrypt.acme.jose.jwk.JWK key: Public key. + :ivar acme.jose.jwk.JWK key: Public key. :ivar tuple contact: - :ivar letsencrypt.acme.messages2.Status status: + :ivar acme.messages2.Status status: :ivar datetime.datetime expires: """ @@ -252,7 +252,7 @@ class Authorization(ResourceBody): class CertificateRequest(jose.JSONObjectWithFields): """ACME new-cert request. - :ivar letsencrypt.acme.jose.util.ComparableX509 csr: + :ivar acme.jose.util.ComparableX509 csr: `M2Crypto.X509.Request` wrapped in `.ComparableX509` :ivar tuple authorizations: `tuple` of URIs (`str`) @@ -264,7 +264,7 @@ class CertificateRequest(jose.JSONObjectWithFields): class CertificateResource(Resource): """Certificate Resource. - :ivar letsencrypt.acme.jose.util.ComparableX509 body: + :ivar acme.jose.util.ComparableX509 body: `M2Crypto.X509.X509` wrapped in `.ComparableX509` :ivar str cert_chain_uri: URI found in the 'up' ``Link`` header :ivar tuple authzrs: `tuple` of `AuthorizationResource`. diff --git a/letsencrypt/acme/messages2_test.py b/acme/messages2_test.py similarity index 78% rename from letsencrypt/acme/messages2_test.py rename to acme/messages2_test.py index 9e8ef33c8..d0c7fdea1 100644 --- a/letsencrypt/acme/messages2_test.py +++ b/acme/messages2_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.messages2.""" +"""Tests for acme.messages2.""" import datetime import os import pkg_resources @@ -8,19 +8,19 @@ import mock import pytz from Crypto.PublicKey import RSA -from letsencrypt.acme import challenges -from letsencrypt.acme import jose +from acme import challenges +from acme import jose KEY = jose.util.HashableRSAKey(RSA.importKey(pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) class ErrorTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.Error.""" + """Tests for acme.messages2.Error.""" def setUp(self): - from letsencrypt.acme.messages2 import Error + from acme.messages2 import Error self.error = Error(detail='foo', typ='malformed') def test_typ_prefix(self): @@ -31,14 +31,14 @@ class ErrorTest(unittest.TestCase): 'malformed', self.error.from_json(self.error.to_partial_json()).typ) def test_typ_decoder_missing_prefix(self): - from letsencrypt.acme.messages2 import Error + from acme.messages2 import Error self.assertRaises(jose.DeserializationError, Error.from_json, {'detail': 'foo', 'type': 'malformed'}) self.assertRaises(jose.DeserializationError, Error.from_json, {'detail': 'foo', 'type': 'not valid bare type'}) def test_typ_decoder_not_recognized(self): - from letsencrypt.acme.messages2 import Error + from acme.messages2 import Error self.assertRaises(jose.DeserializationError, Error.from_json, {'detail': 'foo', 'type': 'urn:acme:error:baz'}) @@ -47,7 +47,7 @@ class ErrorTest(unittest.TestCase): 'The request message was malformed', self.error.description) def test_from_json_hashable(self): - from letsencrypt.acme.messages2 import Error + from acme.messages2 import Error hash(Error.from_json(self.error.to_json())) def test_str(self): @@ -58,10 +58,10 @@ class ErrorTest(unittest.TestCase): class ConstantTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2._Constant.""" + """Tests for acme.messages2._Constant.""" def setUp(self): - from letsencrypt.acme.messages2 import _Constant + from acme.messages2 import _Constant class MockConstant(_Constant): # pylint: disable=missing-docstring POSSIBLE_NAMES = {} @@ -94,7 +94,7 @@ class ConstantTest(unittest.TestCase): self.assertFalse(self.const_a != const_a_prime) class RegistrationTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.Registration.""" + """Tests for acme.messages2.Registration.""" def setUp(self): key = jose.jwk.JWKRSA(key=KEY.publickey()) @@ -102,7 +102,7 @@ class RegistrationTest(unittest.TestCase): recovery_token = 'XYZ' agreement = 'https://letsencrypt.org/terms' - from letsencrypt.acme.messages2 import Registration + from acme.messages2 import Registration self.reg = Registration( key=key, contact=contact, recovery_token=recovery_token, agreement=agreement) @@ -120,31 +120,31 @@ class RegistrationTest(unittest.TestCase): self.assertEqual(self.jobj_to, self.reg.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.messages2 import Registration + from acme.messages2 import Registration self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) def test_from_json_hashable(self): - from letsencrypt.acme.messages2 import Registration + from acme.messages2 import Registration hash(Registration.from_json(self.jobj_from)) class ChallengeResourceTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.ChallengeResource.""" + """Tests for acme.messages2.ChallengeResource.""" def test_uri(self): - from letsencrypt.acme.messages2 import ChallengeResource + from acme.messages2 import ChallengeResource self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( uri='http://challb'), authzr_uri='http://authz').uri) class ChallengeBodyTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.ChallengeBody.""" + """Tests for acme.messages2.ChallengeBody.""" def setUp(self): self.chall = challenges.DNS(token='foo') - from letsencrypt.acme.messages2 import ChallengeBody - from letsencrypt.acme.messages2 import STATUS_VALID + from acme.messages2 import ChallengeBody + from acme.messages2 import STATUS_VALID self.status = STATUS_VALID self.challb = ChallengeBody( uri='http://challb', chall=self.chall, status=self.status) @@ -162,11 +162,11 @@ class ChallengeBodyTest(unittest.TestCase): self.assertEqual(self.jobj_to, self.challb.to_partial_json()) def test_from_json(self): - from letsencrypt.acme.messages2 import ChallengeBody + from acme.messages2 import ChallengeBody self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) def test_from_json_hashable(self): - from letsencrypt.acme.messages2 import ChallengeBody + from acme.messages2 import ChallengeBody hash(ChallengeBody.from_json(self.jobj_from)) def test_proxy(self): @@ -174,11 +174,11 @@ class ChallengeBodyTest(unittest.TestCase): class AuthorizationTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.Authorization.""" + """Tests for acme.messages2.Authorization.""" def setUp(self): - from letsencrypt.acme.messages2 import ChallengeBody - from letsencrypt.acme.messages2 import STATUS_VALID + from acme.messages2 import ChallengeBody + from acme.messages2 import STATUS_VALID self.challbs = ( ChallengeBody( uri='http://challb1', status=STATUS_VALID, @@ -190,9 +190,9 @@ class AuthorizationTest(unittest.TestCase): ) combinations = ((0, 2), (1, 2)) - from letsencrypt.acme.messages2 import Authorization - from letsencrypt.acme.messages2 import Identifier - from letsencrypt.acme.messages2 import IDENTIFIER_FQDN + from acme.messages2 import Authorization + from acme.messages2 import Identifier + from acme.messages2 import IDENTIFIER_FQDN identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com') self.authz = Authorization( identifier=identifier, combinations=combinations, @@ -205,11 +205,11 @@ class AuthorizationTest(unittest.TestCase): } def test_from_json(self): - from letsencrypt.acme.messages2 import Authorization + from acme.messages2 import Authorization Authorization.from_json(self.jobj_from) def test_from_json_hashable(self): - from letsencrypt.acme.messages2 import Authorization + from acme.messages2 import Authorization hash(Authorization.from_json(self.jobj_from)) def test_resolved_combinations(self): @@ -220,10 +220,10 @@ class AuthorizationTest(unittest.TestCase): class RevocationTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages2.RevocationTest.""" + """Tests for acme.messages2.RevocationTest.""" def setUp(self): - from letsencrypt.acme.messages2 import Revocation + from acme.messages2 import Revocation self.rev_now = Revocation(authorizations=(), revoke=Revocation.NOW) self.rev_date = Revocation(authorizations=(), revoke=datetime.datetime( 2015, 3, 27, tzinfo=pytz.utc)) @@ -232,7 +232,7 @@ class RevocationTest(unittest.TestCase): 'revoke': '2015-03-27T00:00:00Z'} def test_revoke_decoder(self): - from letsencrypt.acme.messages2 import Revocation + from acme.messages2 import Revocation self.assertEqual(self.rev_now, Revocation.from_json(self.jobj_now)) self.assertEqual(self.rev_date, Revocation.from_json(self.jobj_date)) @@ -241,7 +241,7 @@ class RevocationTest(unittest.TestCase): self.assertEqual(self.jobj_date, self.rev_date.to_partial_json()) def test_from_json_hashable(self): - from letsencrypt.acme.messages2 import Revocation + from acme.messages2 import Revocation hash(Revocation.from_json(self.rev_now.to_json())) diff --git a/letsencrypt/acme/messages_test.py b/acme/messages_test.py similarity index 86% rename from letsencrypt/acme/messages_test.py rename to acme/messages_test.py index 56781db18..1acb0e838 100644 --- a/letsencrypt/acme/messages_test.py +++ b/acme/messages_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.acme.messages.""" +"""Tests for acme.messages.""" import os import pkg_resources import unittest @@ -6,15 +6,15 @@ import unittest import Crypto.PublicKey.RSA import M2Crypto -from letsencrypt.acme import challenges -from letsencrypt.acme import errors -from letsencrypt.acme import jose -from letsencrypt.acme import other +from acme import challenges +from acme import errors +from acme import jose +from acme import other KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) CERT = jose.ComparableX509(M2Crypto.X509.load_cert( pkg_resources.resource_filename( 'letsencrypt.client.tests', os.path.join('testdata', 'cert.pem')))) @@ -23,15 +23,15 @@ CSR = jose.ComparableX509(M2Crypto.X509.load_request( 'letsencrypt.client.tests', os.path.join('testdata', 'csr.pem')))) CSR2 = jose.ComparableX509(M2Crypto.X509.load_request( pkg_resources.resource_filename( - 'letsencrypt.acme.jose', os.path.join('testdata', 'csr2.pem')))) + 'acme.jose', os.path.join('testdata', 'csr2.pem')))) class MessageTest(unittest.TestCase): - """Tests for letsencrypt.acme.messages.Message.""" + """Tests for acme.messages.Message.""" def setUp(self): # pylint: disable=missing-docstring,too-few-public-methods - from letsencrypt.acme.messages import Message + from acme.messages import Message class MockParentMessage(Message): # pylint: disable=abstract-method @@ -69,7 +69,7 @@ class ChallengeTest(unittest.TestCase): ) combinations = ((0, 2), (1, 2)) - from letsencrypt.acme.messages import Challenge + from acme.messages import Challenge self.msg = Challenge( session_id='aefoGaavieG9Wihuk2aufai3aeZ5EeW4', nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9', @@ -107,14 +107,14 @@ class ChallengeTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg_to) def test_from_json(self): - from letsencrypt.acme.messages import Challenge + from acme.messages import Challenge self.assertEqual(Challenge.from_json(self.jmsg_from), self.msg) def test_json_without_optionals(self): del self.jmsg_from['combinations'] del self.jmsg_to['combinations'] - from letsencrypt.acme.messages import Challenge + from acme.messages import Challenge msg = Challenge.from_json(self.jmsg_from) self.assertEqual(msg.combinations, ()) @@ -124,7 +124,7 @@ class ChallengeTest(unittest.TestCase): class ChallengeRequestTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.messages import ChallengeRequest + from acme.messages import ChallengeRequest self.msg = ChallengeRequest(identifier='example.com') self.jmsg = { @@ -136,7 +136,7 @@ class ChallengeRequestTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg) def test_from_json(self): - from letsencrypt.acme.messages import ChallengeRequest + from acme.messages import ChallengeRequest self.assertEqual(ChallengeRequest.from_json(self.jmsg), self.msg) @@ -145,7 +145,7 @@ class AuthorizationTest(unittest.TestCase): def setUp(self): jwk = jose.JWKRSA(key=KEY.publickey()) - from letsencrypt.acme.messages import Authorization + from acme.messages import Authorization self.msg = Authorization(recovery_token='tok', jwk=jwk, identifier='example.com') @@ -162,7 +162,7 @@ class AuthorizationTest(unittest.TestCase): def test_from_json(self): self.jmsg['jwk'] = self.jmsg['jwk'].to_partial_json() - from letsencrypt.acme.messages import Authorization + from acme.messages import Authorization self.assertEqual(Authorization.from_json(self.jmsg), self.msg) def test_json_without_optionals(self): @@ -170,7 +170,7 @@ class AuthorizationTest(unittest.TestCase): del self.jmsg['identifier'] del self.jmsg['jwk'] - from letsencrypt.acme.messages import Authorization + from acme.messages import Authorization msg = Authorization.from_json(self.jmsg) self.assertTrue(msg.recovery_token is None) @@ -196,7 +196,7 @@ class AuthorizationRequestTest(unittest.TestCase): '\x92\xe9\x96\x11\xc2\xefx\x0bR', nonce='\xab?\x08o\xe6\x81$\x9f\xa1\xc9\x025\x1c\x1b\xa5+') - from letsencrypt.acme.messages import AuthorizationRequest + from acme.messages import AuthorizationRequest self.msg = AuthorizationRequest( session_id='aefoGaavieG9Wihuk2aufai3aeZ5EeW4', nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9', @@ -226,7 +226,7 @@ class AuthorizationRequestTest(unittest.TestCase): } def test_create(self): - from letsencrypt.acme.messages import AuthorizationRequest + from acme.messages import AuthorizationRequest self.assertEqual(self.msg, AuthorizationRequest.create( name='example.com', key=KEY, responses=self.responses, nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9', @@ -241,7 +241,7 @@ class AuthorizationRequestTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg_to) def test_from_json(self): - from letsencrypt.acme.messages import AuthorizationRequest + from acme.messages import AuthorizationRequest self.assertEqual( self.msg, AuthorizationRequest.from_json(self.jmsg_from)) @@ -249,7 +249,7 @@ class AuthorizationRequestTest(unittest.TestCase): del self.jmsg_from['contact'] del self.jmsg_to['contact'] - from letsencrypt.acme.messages import AuthorizationRequest + from acme.messages import AuthorizationRequest msg = AuthorizationRequest.from_json(self.jmsg_from) self.assertEqual(msg.contact, ()) @@ -261,7 +261,7 @@ class CertificateTest(unittest.TestCase): def setUp(self): refresh = 'https://example.com/refresh/Dr8eAwTVQfSS/' - from letsencrypt.acme.messages import Certificate + from acme.messages import Certificate self.msg = Certificate( certificate=CERT, chain=(CERT,), refresh=refresh) @@ -279,7 +279,7 @@ class CertificateTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg_to) def test_from_json(self): - from letsencrypt.acme.messages import Certificate + from acme.messages import Certificate self.assertEqual(Certificate.from_json(self.jmsg_from), self.msg) def test_json_without_optionals(self): @@ -288,7 +288,7 @@ class CertificateTest(unittest.TestCase): del self.jmsg_to['chain'] del self.jmsg_to['refresh'] - from letsencrypt.acme.messages import Certificate + from acme.messages import Certificate msg = Certificate.from_json(self.jmsg_from) self.assertEqual(msg.chain, ()) @@ -307,7 +307,7 @@ class CertificateRequestTest(unittest.TestCase): 'k\xfe\xee\xb4\xe4\xc8\x05\x9a\x08\xa7', nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9') - from letsencrypt.acme.messages import CertificateRequest + from acme.messages import CertificateRequest self.msg = CertificateRequest(csr=CSR, signature=signature) self.jmsg_to = { @@ -319,7 +319,7 @@ class CertificateRequestTest(unittest.TestCase): self.jmsg_from['signature'] = self.jmsg_from['signature'].to_json() def test_create(self): - from letsencrypt.acme.messages import CertificateRequest + from acme.messages import CertificateRequest self.assertEqual(self.msg, CertificateRequest.create( csr=CSR, key=KEY, sig_nonce='\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9')) @@ -331,14 +331,14 @@ class CertificateRequestTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg_to) def test_from_json(self): - from letsencrypt.acme.messages import CertificateRequest + from acme.messages import CertificateRequest self.assertEqual(self.msg, CertificateRequest.from_json(self.jmsg_from)) class DeferTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.messages import Defer + from acme.messages import Defer self.msg = Defer( token='O7-s9MNq1siZHlgrMzi9_A', interval=60, message='Warming up the HSM') @@ -354,14 +354,14 @@ class DeferTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg) def test_from_json(self): - from letsencrypt.acme.messages import Defer + from acme.messages import Defer self.assertEqual(Defer.from_json(self.jmsg), self.msg) def test_json_without_optionals(self): del self.jmsg['interval'] del self.jmsg['message'] - from letsencrypt.acme.messages import Defer + from acme.messages import Defer msg = Defer.from_json(self.jmsg) self.assertTrue(msg.interval is None) @@ -372,7 +372,7 @@ class DeferTest(unittest.TestCase): class ErrorTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.messages import Error + from acme.messages import Error self.msg = Error( error='badCSR', message='RSA keys must be at least 2048 bits long', more_info='https://ca.example.com/documentation/csr-requirements') @@ -388,14 +388,14 @@ class ErrorTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg) def test_from_json(self): - from letsencrypt.acme.messages import Error + from acme.messages import Error self.assertEqual(Error.from_json(self.jmsg), self.msg) def test_json_without_optionals(self): del self.jmsg['message'] del self.jmsg['moreInfo'] - from letsencrypt.acme.messages import Error + from acme.messages import Error msg = Error.from_json(self.jmsg) self.assertTrue(msg.message is None) @@ -406,7 +406,7 @@ class ErrorTest(unittest.TestCase): class RevocationTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.messages import Revocation + from acme.messages import Revocation self.msg = Revocation() self.jmsg = {'type': 'revocation'} @@ -414,7 +414,7 @@ class RevocationTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg) def test_from_json(self): - from letsencrypt.acme.messages import Revocation + from acme.messages import Revocation self.assertEqual(Revocation.from_json(self.jmsg), self.msg) @@ -431,7 +431,7 @@ class RevocationRequestTest(unittest.TestCase): 's\xd9\xd0\xe7', nonce=self.sig_nonce) - from letsencrypt.acme.messages import RevocationRequest + from acme.messages import RevocationRequest self.msg = RevocationRequest(certificate=CERT, signature=signature) self.jmsg_to = { @@ -443,7 +443,7 @@ class RevocationRequestTest(unittest.TestCase): self.jmsg_from['signature'] = self.jmsg_from['signature'].to_json() def test_create(self): - from letsencrypt.acme.messages import RevocationRequest + from acme.messages import RevocationRequest self.assertEqual(self.msg, RevocationRequest.create( certificate=CERT, key=KEY, sig_nonce=self.sig_nonce)) @@ -454,14 +454,14 @@ class RevocationRequestTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg_to) def test_from_json(self): - from letsencrypt.acme.messages import RevocationRequest + from acme.messages import RevocationRequest self.assertEqual(self.msg, RevocationRequest.from_json(self.jmsg_from)) class StatusRequestTest(unittest.TestCase): def setUp(self): - from letsencrypt.acme.messages import StatusRequest + from acme.messages import StatusRequest self.msg = StatusRequest(token=u'O7-s9MNq1siZHlgrMzi9_A') self.jmsg = { 'type': 'statusRequest', @@ -472,7 +472,7 @@ class StatusRequestTest(unittest.TestCase): self.assertEqual(self.msg.to_partial_json(), self.jmsg) def test_from_json(self): - from letsencrypt.acme.messages import StatusRequest + from acme.messages import StatusRequest self.assertEqual(StatusRequest.from_json(self.jmsg), self.msg) diff --git a/letsencrypt/acme/other.py b/acme/other.py similarity index 98% rename from letsencrypt/acme/other.py rename to acme/other.py index 99a4ec551..d7eb49156 100644 --- a/letsencrypt/acme/other.py +++ b/acme/other.py @@ -5,7 +5,7 @@ import logging import Crypto.Random import Crypto.PublicKey.RSA -from letsencrypt.acme import jose +from acme import jose class Signature(jose.JSONObjectWithFields): diff --git a/letsencrypt/acme/other_test.py b/acme/other_test.py similarity index 87% rename from letsencrypt/acme/other_test.py rename to acme/other_test.py index eefcb2fc5..d106c50ab 100644 --- a/letsencrypt/acme/other_test.py +++ b/acme/other_test.py @@ -1,21 +1,21 @@ -"""Tests for letsencrypt.acme.sig.""" +"""Tests for acme.sig.""" import os import pkg_resources import unittest import Crypto.PublicKey.RSA -from letsencrypt.acme import jose +from acme import jose KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) class SignatureTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes - """Tests for letsencrypt.acme.sig.Signature.""" + """Tests for acme.sig.Signature.""" def setUp(self): self.msg = 'message' @@ -45,7 +45,7 @@ class SignatureTest(unittest.TestCase): 'sig': b64sig, } - from letsencrypt.acme.other import Signature + from acme.other import Signature self.signature = Signature( alg=self.alg, sig=self.sig, nonce=self.nonce, jwk=self.jwk) @@ -63,7 +63,7 @@ class SignatureTest(unittest.TestCase): @classmethod def _from_msg(cls, *args, **kwargs): - from letsencrypt.acme.other import Signature + from acme.other import Signature return Signature.from_msg(*args, **kwargs) def test_create_from_msg(self): @@ -80,12 +80,12 @@ class SignatureTest(unittest.TestCase): self.assertEqual(self.signature.to_partial_json(), self.jsig_to) def test_from_json(self): - from letsencrypt.acme.other import Signature + from acme.other import Signature self.assertEqual( self.signature, Signature.from_json(self.jsig_from)) def test_from_json_non_schema_errors(self): - from letsencrypt.acme.other import Signature + from acme.other import Signature jwk = self.jwk.to_partial_json() self.assertRaises( jose.DeserializationError, Signature.from_json, { diff --git a/letsencrypt/acme/schemata/authorization.json b/acme/schemata/authorization.json similarity index 88% rename from letsencrypt/acme/schemata/authorization.json rename to acme/schemata/authorization.json index 742a9c0d5..122f263e1 100644 --- a/letsencrypt/acme/schemata/authorization.json +++ b/acme/schemata/authorization.json @@ -15,7 +15,7 @@ "type": "string" }, "jwk": { - "$ref": "file:letsencrypt/acme/schemata/jwk.json" + "$ref": "file:acme/schemata/jwk.json" } } } diff --git a/letsencrypt/acme/schemata/authorizationRequest.json b/acme/schemata/authorizationRequest.json similarity index 85% rename from letsencrypt/acme/schemata/authorizationRequest.json rename to acme/schemata/authorizationRequest.json index ee22808bc..2d4371cb8 100644 --- a/letsencrypt/acme/schemata/authorizationRequest.json +++ b/acme/schemata/authorizationRequest.json @@ -15,14 +15,14 @@ "type": "string" }, "signature" : { - "$ref": "file:letsencrypt/acme/schemata/signature.json" + "$ref": "file:acme/schemata/signature.json" }, "responses": { "type": "array", "minItems": 1, "items": { "anyOf": [ - { "$ref": "file:letsencrypt/acme/schemata/responseobject.json" }, + { "$ref": "file:acme/schemata/responseobject.json" }, { "type": "null" } ] } diff --git a/letsencrypt/acme/schemata/certificate.json b/acme/schemata/certificate.json similarity index 100% rename from letsencrypt/acme/schemata/certificate.json rename to acme/schemata/certificate.json diff --git a/letsencrypt/acme/schemata/certificateRequest.json b/acme/schemata/certificateRequest.json similarity index 88% rename from letsencrypt/acme/schemata/certificateRequest.json rename to acme/schemata/certificateRequest.json index c75e93bd9..ef3e18f98 100644 --- a/letsencrypt/acme/schemata/certificateRequest.json +++ b/acme/schemata/certificateRequest.json @@ -13,7 +13,7 @@ "pattern": "^[-_=0-9A-Za-z]+$" }, "signature" : { - "$ref": "file:letsencrypt/acme/schemata/signature.json" + "$ref": "file:acme/schemata/signature.json" } } } diff --git a/letsencrypt/acme/schemata/challenge.json b/acme/schemata/challenge.json similarity index 91% rename from letsencrypt/acme/schemata/challenge.json rename to acme/schemata/challenge.json index b4b2a5205..978fcd4c4 100644 --- a/letsencrypt/acme/schemata/challenge.json +++ b/acme/schemata/challenge.json @@ -18,7 +18,7 @@ "type": "array", "minItems": 1, "items": { - "$ref": "file:letsencrypt/acme/schemata/challengeobject.json" + "$ref": "file:acme/schemata/challengeobject.json" } }, "combinations": { diff --git a/letsencrypt/acme/schemata/challengeRequest.json b/acme/schemata/challengeRequest.json similarity index 100% rename from letsencrypt/acme/schemata/challengeRequest.json rename to acme/schemata/challengeRequest.json diff --git a/letsencrypt/acme/schemata/challengeobject.json b/acme/schemata/challengeobject.json similarity index 100% rename from letsencrypt/acme/schemata/challengeobject.json rename to acme/schemata/challengeobject.json diff --git a/letsencrypt/acme/schemata/defer.json b/acme/schemata/defer.json similarity index 100% rename from letsencrypt/acme/schemata/defer.json rename to acme/schemata/defer.json diff --git a/letsencrypt/acme/schemata/error.json b/acme/schemata/error.json similarity index 100% rename from letsencrypt/acme/schemata/error.json rename to acme/schemata/error.json diff --git a/letsencrypt/acme/schemata/jwk.json b/acme/schemata/jwk.json similarity index 100% rename from letsencrypt/acme/schemata/jwk.json rename to acme/schemata/jwk.json diff --git a/letsencrypt/acme/schemata/responseobject.json b/acme/schemata/responseobject.json similarity index 96% rename from letsencrypt/acme/schemata/responseobject.json rename to acme/schemata/responseobject.json index c6d6c9c1b..5ca6babf1 100644 --- a/letsencrypt/acme/schemata/responseobject.json +++ b/acme/schemata/responseobject.json @@ -59,7 +59,7 @@ "pattern": "^[-_=0-9A-Za-z]+$" }, "signature": { - "$ref": "file:letsencrypt/acme/schemata/signature.json" + "$ref": "file:acme/schemata/signature.json" } } }, diff --git a/letsencrypt/acme/schemata/revocation.json b/acme/schemata/revocation.json similarity index 100% rename from letsencrypt/acme/schemata/revocation.json rename to acme/schemata/revocation.json diff --git a/letsencrypt/acme/schemata/revocationRequest.json b/acme/schemata/revocationRequest.json similarity index 87% rename from letsencrypt/acme/schemata/revocationRequest.json rename to acme/schemata/revocationRequest.json index 5eb604fd9..7559d0ee0 100644 --- a/letsencrypt/acme/schemata/revocationRequest.json +++ b/acme/schemata/revocationRequest.json @@ -12,7 +12,7 @@ "type" : "string" }, "signature" : { - "$ref": "file:letsencrypt/acme/schemata/signature.json" + "$ref": "file:acme/schemata/signature.json" } } } diff --git a/letsencrypt/acme/schemata/signature.json b/acme/schemata/signature.json similarity index 100% rename from letsencrypt/acme/schemata/signature.json rename to acme/schemata/signature.json diff --git a/letsencrypt/acme/schemata/statusRequest.json b/acme/schemata/statusRequest.json similarity index 100% rename from letsencrypt/acme/schemata/statusRequest.json rename to acme/schemata/statusRequest.json diff --git a/letsencrypt/acme/util.py b/acme/util.py similarity index 100% rename from letsencrypt/acme/util.py rename to acme/util.py diff --git a/docs/acme/index.rst b/docs/acme/index.rst new file mode 100644 index 000000000..9cca3b795 --- /dev/null +++ b/docs/acme/index.rst @@ -0,0 +1,61 @@ +:mod:`acme` +=========== + +.. contents:: + +.. automodule:: acme + :members: + + +Messages +-------- + +v00 +~~~ + +.. automodule:: acme.messages + :members: + +v02 +~~~ + +.. automodule:: acme.messages2 + :members: + + +Challenges +---------- + +.. automodule:: acme.challenges + :members: + + +Other ACME objects +------------------ + +.. automodule:: acme.other + :members: + + +Fields +------ + +.. automodule:: acme.fields + :members: + + +Errors +------ + +.. automodule:: acme.errors + :members: + + + :members: + + +Utilities +--------- + +.. automodule:: acme.util + :members: diff --git a/docs/acme/jose.rst b/docs/acme/jose.rst new file mode 100644 index 000000000..fa3a0e9bb --- /dev/null +++ b/docs/acme/jose.rst @@ -0,0 +1,67 @@ +:mod:`acme.jose` +================ + +.. contents:: + +.. automodule:: acme.jose + :members: + + +JSON Web Algorithms +------------------- + +.. automodule:: acme.jose.jwa + :members: + + +JSON Web Key +------------ + +.. automodule:: acme.jose.jwk + :members: + + +JSON Web Signature +------------------ + +.. automodule:: acme.jose.jws + :members: + + +Implementation details +---------------------- + + +Interfaces +~~~~~~~~~~ + +.. automodule:: acme.jose.interfaces + :members: + + +Errors +~~~~~~ + +.. automodule:: acme.jose.errors + :members: + + +JSON utilities +~~~~~~~~~~~~~~ + +.. automodule:: acme.jose.json_util + :members: + + +JOSE Base64 +~~~~~~~~~~~ + +.. automodule:: acme.jose.b64 + :members: + + +Utilities +~~~~~~~~~ + +.. automodule:: acme.jose.util + :members: diff --git a/docs/api/acme/index.rst b/docs/api/acme/index.rst deleted file mode 100644 index 20206183a..000000000 --- a/docs/api/acme/index.rst +++ /dev/null @@ -1,61 +0,0 @@ -:mod:`letsencrypt.acme` -======================= - -.. contents:: - -.. automodule:: letsencrypt.acme - :members: - - -Messages --------- - -v00 -~~~ - -.. automodule:: letsencrypt.acme.messages - :members: - -v02 -~~~ - -.. automodule:: letsencrypt.acme.messages2 - :members: - - -Challenges ----------- - -.. automodule:: letsencrypt.acme.challenges - :members: - - -Other ACME objects ------------------- - -.. automodule:: letsencrypt.acme.other - :members: - - -Fields ------- - -.. automodule:: letsencrypt.acme.fields - :members: - - -Errors ------- - -.. automodule:: letsencrypt.acme.errors - :members: - - - :members: - - -Utilities ---------- - -.. automodule:: letsencrypt.acme.util - :members: diff --git a/docs/api/acme/jose.rst b/docs/api/acme/jose.rst deleted file mode 100644 index 9a64d33d3..000000000 --- a/docs/api/acme/jose.rst +++ /dev/null @@ -1,67 +0,0 @@ -:mod:`letsencrypt.acme.jose` -============================ - -.. contents:: - -.. automodule:: letsencrypt.acme.jose - :members: - - -JSON Web Algorithms -------------------- - -.. automodule:: letsencrypt.acme.jose.jwa - :members: - - -JSON Web Key ------------- - -.. automodule:: letsencrypt.acme.jose.jwk - :members: - - -JSON Web Signature ------------------- - -.. automodule:: letsencrypt.acme.jose.jws - :members: - - -Implementation details ----------------------- - - -Interfaces -~~~~~~~~~~ - -.. automodule:: letsencrypt.acme.jose.interfaces - :members: - - -Errors -~~~~~~ - -.. automodule:: letsencrypt.acme.jose.errors - :members: - - -JSON utilities -~~~~~~~~~~~~~~ - -.. automodule:: letsencrypt.acme.jose.json_util - :members: - - -JOSE Base64 -~~~~~~~~~~~ - -.. automodule:: letsencrypt.acme.jose.b64 - :members: - - -Utilities -~~~~~~~~~ - -.. automodule:: letsencrypt.acme.jose.util - :members: diff --git a/docs/contributing.rst b/docs/contributing.rst index d5088705b..4a61f7388 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -67,7 +67,7 @@ Support for other Linux distributions coming soon. Code components and layout ========================== -letsencrypt/acme +acme contains all protocol specific code letsencrypt/client all client code diff --git a/docs/index.rst b/docs/index.rst index 72be096f9..2edf74636 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Welcome to the Let's Encrypt client documentation! using contributing plugins + acme .. toctree:: :maxdepth: 1 diff --git a/letsencrypt/client/account.py b/letsencrypt/client/account.py index 6c0ca9262..053f73a87 100644 --- a/letsencrypt/client/account.py +++ b/letsencrypt/client/account.py @@ -6,7 +6,7 @@ import re import configobj import zope.component -from letsencrypt.acme import messages2 +from acme import messages2 from letsencrypt.client import crypto_util from letsencrypt.client import errors @@ -28,7 +28,7 @@ class Account(object): :ivar str phone: Client's phone number :ivar regr: Registration Resource - :type regr: :class:`~letsencrypt.acme.messages2.RegistrationResource` + :type regr: :class:`~acme.messages2.RegistrationResource` """ diff --git a/letsencrypt/client/achallenges.py b/letsencrypt/client/achallenges.py index 1a5cf9c8e..a88b0dddb 100644 --- a/letsencrypt/client/achallenges.py +++ b/letsencrypt/client/achallenges.py @@ -1,11 +1,11 @@ """Client annotated ACME challenges. Please use names such as ``achall`` to distiguish from variables "of type" -:class:`letsencrypt.acme.challenges.Challenge` (denoted by ``chall``) +:class:`acme.challenges.Challenge` (denoted by ``chall``) and :class:`.ChallengeBody` (denoted by ``challb``):: - from letsencrypt.acme import challenges - from letsencrypt.acme import messages2 + from acme import challenges + from acme import messages2 from letsencrypt.client import achallenges chall = challenges.DNS(token='foo') @@ -17,8 +17,8 @@ Note, that all annotated challenges act as a proxy objects:: achall.token == challb.token """ -from letsencrypt.acme import challenges -from letsencrypt.acme.jose import util as jose_util +from acme import challenges +from acme.jose import util as jose_util from letsencrypt.client import crypto_util @@ -52,7 +52,7 @@ class DVSNI(AnnotatedChallenge): :returns: ``(cert_pem, response)`` tuple, where ``cert_pem`` is the PEM encoded certificate and ``response`` is an instance - :class:`letsencrypt.acme.challenges.DVSNIResponse`. + :class:`acme.challenges.DVSNIResponse`. :rtype: tuple """ diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index 0f2d76653..52e2df0c6 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -3,8 +3,8 @@ import itertools import logging import time -from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 +from acme import challenges +from acme import messages2 from letsencrypt.client import achallenges from letsencrypt.client import constants @@ -15,11 +15,11 @@ class AuthHandler(object): """ACME Authorization Handler for a client. :ivar dv_auth: Authenticator capable of solving - :class:`~letsencrypt.acme.challenges.DVChallenge` types + :class:`~acme.challenges.DVChallenge` types :type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` :ivar cont_auth: Authenticator capable of solving - :class:`~letsencrypt.acme.challenges.ContinuityChallenge` types + :class:`~acme.challenges.ContinuityChallenge` types :type cont_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` :ivar network: Network object for sending and receiving authorization @@ -30,7 +30,7 @@ class AuthHandler(object): :type account: :class:`letsencrypt.client.account.Account` :ivar dict authzr: ACME Authorization Resource dict where keys are domains - and values are :class:`letsencrypt.acme.messages2.AuthorizationResource` + and values are :class:`acme.messages2.AuthorizationResource` :ivar list dv_c: DV challenges in the form of :class:`letsencrypt.client.achallenges.AnnotatedChallenge` :ivar list cont_c: Continuity challenges in the @@ -219,7 +219,7 @@ class AuthHandler(object): each challenge resource. :param authzr: Authorization Resource - :type authzr: :class:`letsencrypt.acme.messages2.AuthorizationResource` + :type authzr: :class:`acme.messages2.AuthorizationResource` :param achall: Annotated challenge for which to get status :type achall: :class:`letsencrypt.client.achallenges.AnnotatedChallenge` @@ -319,7 +319,7 @@ def challb_to_achall(challb, key, domain): """Converts a ChallengeBody object to an AnnotatedChallenge. :param challb: ChallengeBody - :type challb: :class:`letsencrypt.acme.messages2.ChallengeBody` + :type challb: :class:`acme.messages2.ChallengeBody` :param key: Key :type key: :class:`letsencrypt.client.le_util.Key` @@ -368,16 +368,16 @@ def gen_challenge_path(challbs, preferences, combinations): .. todo:: This can be possibly be rewritten to use resolved_combinations. :param tuple challbs: A tuple of challenges - (:class:`letsencrypt.acme.messages2.Challenge`) from - :class:`letsencrypt.acme.messages2.AuthorizationResource` to be + (:class:`acme.messages2.Challenge`) from + :class:`acme.messages2.AuthorizationResource` to be fulfilled by the client in order to prove possession of the identifier. :param list preferences: List of challenge preferences for domain - (:class:`letsencrypt.acme.challenges.Challenge` subclasses) + (:class:`acme.challenges.Challenge` subclasses) :param tuple combinations: A collection of sets of challenges from - :class:`letsencrypt.acme.messages.Challenge`, each of which would + :class:`acme.messages.Challenge`, each of which would be sufficient to prove possession of the identifier. :returns: tuple of indices from ``challenges``. diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 6622ea8de..b658df90e 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -6,8 +6,8 @@ import pkg_resources import M2Crypto import zope.component -from letsencrypt.acme import jose -from letsencrypt.acme.jose import jwk +from acme import jose +from acme.jose import jwk from letsencrypt.client import account from letsencrypt.client import auth_handler @@ -148,7 +148,7 @@ class Client(object): """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. - :type certr: :class:`letsencrypt.acme.messages.Certificate` + :type certr: :class:`acme.messages.Certificate` :param str cert_path: Path to attempt to save the cert file :param str chain_path: Path to attempt to save the chain file diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 3f8cf4f05..513b76829 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -1,7 +1,7 @@ """Let's Encrypt constants.""" import logging -from letsencrypt.acme import challenges +from acme import challenges SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" diff --git a/letsencrypt/client/continuity_auth.py b/letsencrypt/client/continuity_auth.py index 063d3d408..4d75b43cf 100644 --- a/letsencrypt/client/continuity_auth.py +++ b/letsencrypt/client/continuity_auth.py @@ -1,7 +1,7 @@ """Continuity Authenticator""" import zope.interface -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import errors @@ -11,7 +11,7 @@ from letsencrypt.client import recovery_token class ContinuityAuthenticator(object): """IAuthenticator for - :const:`~letsencrypt.acme.challenges.ContinuityChallenge` class challenges. + :const:`~acme.challenges.ContinuityChallenge` class challenges. :ivar rec_token: Performs "recoveryToken" challenges :type rec_token: :class:`letsencrypt.client.recovery_token.RecoveryToken` diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index b005eb02d..e28264759 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -100,7 +100,7 @@ class IAuthenticator(IPlugin): :param str domain: Domain for which challenge preferences are sought. :returns: List of challege types (subclasses of - :class:`letsencrypt.acme.challenges.Challenge`) with the most + :class:`acme.challenges.Challenge`) with the most preferred challenges first. If a type is not specified, it means the Authenticator cannot perform the challenge. :rtype: list @@ -116,8 +116,8 @@ class IAuthenticator(IPlugin): :func:`get_chall_pref` only. :returns: List of ACME - :class:`~letsencrypt.acme.challenges.ChallengeResponse` instances - or if the :class:`~letsencrypt.acme.challenges.Challenge` cannot + :class:`~acme.challenges.ChallengeResponse` instances + or if the :class:`~acme.challenges.Challenge` cannot be fulfilled then: ``None`` @@ -126,7 +126,7 @@ class IAuthenticator(IPlugin): Authenticator will never be able to perform (error). :rtype: :class:`list` of - :class:`letsencrypt.acme.challenges.ChallengeResponse` + :class:`acme.challenges.ChallengeResponse` """ diff --git a/letsencrypt/client/network.py b/letsencrypt/client/network.py index 2719583c3..81a3fccdc 100644 --- a/letsencrypt/client/network.py +++ b/letsencrypt/client/network.py @@ -5,8 +5,8 @@ import time import requests -from letsencrypt.acme import jose -from letsencrypt.acme import messages +from acme import jose +from acme import messages from letsencrypt.client import errors @@ -35,12 +35,12 @@ class Network(object): """Send ACME message to server. :param msg: ACME message. - :type msg: :class:`letsencrypt.acme.messages.Message` + :type msg: :class:`acme.messages.Message` :returns: Server response message. - :rtype: :class:`letsencrypt.acme.messages.Message` + :rtype: :class:`acme.messages.Message` - :raises letsencrypt.acme.errors.ValidationError: if `msg` is not + :raises acme.errors.ValidationError: if `msg` is not valid serializable ACME JSON message. :raises errors.LetsEncryptClientError: in case of connection error or if response from server is not a valid ACME message. @@ -68,10 +68,10 @@ class Network(object): """Send ACME message to server and return expected message. :param msg: ACME message. - :type msg: :class:`letsencrypt.acme.Message` + :type msg: :class:`acme.Message` :returns: ACME response message of expected type. - :rtype: :class:`letsencrypt.acme.messages.Message` + :rtype: :class:`acme.messages.Message` :raises errors.LetsEncryptClientError: An exception is thrown @@ -84,10 +84,10 @@ class Network(object): """Is response expected ACME message? :param response: ACME response message from server. - :type response: :class:`letsencrypt.acme.messages.Message` + :type response: :class:`acme.messages.Message` :param expected: Expected response type. - :type expected: subclass of :class:`letsencrypt.acme.messages.Message` + :type expected: subclass of :class:`acme.messages.Message` :param int delay: Number of seconds to delay before next round in case of ACME "defer" response message. @@ -95,7 +95,7 @@ class Network(object): response message. :returns: ACME response message from server. - :rtype: :class:`letsencrypt.acme.messages.Message` + :rtype: :class:`acme.messages.Message` :raises LetsEncryptClientError: if server sent ACME "error" message diff --git a/letsencrypt/client/network2.py b/letsencrypt/client/network2.py index eaa485a8d..2b85ebd3c 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/client/network2.py @@ -9,8 +9,8 @@ import M2Crypto import requests import werkzeug -from letsencrypt.acme import jose -from letsencrypt.acme import messages2 +from acme import jose +from acme import messages2 from letsencrypt.client import errors @@ -127,7 +127,7 @@ class Network(object): :param str content_type: Expected ``Content-Type``, fails if not set. - :raises letsencrypt.acme.messages2.NetworkError: + :raises acme.messages2.NetworkError: :returns: HTTP Response :rtype: `requests.Response` @@ -467,7 +467,7 @@ class Network(object): :param str uri: URI of certificate :returns: tuple of the form - (response, :class:`letsencrypt.acme.jose.ComparableX509`) + (response, :class:`acme.jose.ComparableX509`) :rtype: tuple """ diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt/client/plugins/apache/configurator.py index 82d6f323c..7006e7d2f 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt/client/plugins/apache/configurator.py @@ -9,7 +9,7 @@ import sys import zope.interface -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import augeas_configurator diff --git a/letsencrypt/client/plugins/apache/tests/configurator_test.py b/letsencrypt/client/plugins/apache/tests/configurator_test.py index ae2097b3e..b4881d63b 100644 --- a/letsencrypt/client/plugins/apache/tests/configurator_test.py +++ b/letsencrypt/client/plugins/apache/tests/configurator_test.py @@ -6,7 +6,7 @@ import unittest import mock -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import errors diff --git a/letsencrypt/client/plugins/apache/tests/dvsni_test.py b/letsencrypt/client/plugins/apache/tests/dvsni_test.py index 2780749b5..9694ce69b 100644 --- a/letsencrypt/client/plugins/apache/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/apache/tests/dvsni_test.py @@ -5,7 +5,7 @@ import shutil import mock -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import le_util @@ -34,9 +34,9 @@ class DvsniPerformTest(util.ApacheTest): self.sni = dvsni.ApacheDvsni(config) rsa256_file = pkg_resources.resource_filename( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") rsa256_pem = pkg_resources.resource_string( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") auth_key = le_util.Key(rsa256_file, rsa256_pem) self.achalls = [ diff --git a/letsencrypt/client/plugins/apache/tests/util.py b/letsencrypt/client/plugins/apache/tests/util.py index 618b0975a..98c06af40 100644 --- a/letsencrypt/client/plugins/apache/tests/util.py +++ b/letsencrypt/client/plugins/apache/tests/util.py @@ -26,9 +26,9 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2") self.rsa256_file = pkg_resources.resource_filename( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") self.rsa256_pem = pkg_resources.resource_string( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"): diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/client/plugins/common.py index 60b868c37..08e9cf658 100644 --- a/letsencrypt/client/plugins/common.py +++ b/letsencrypt/client/plugins/common.py @@ -1,7 +1,7 @@ """Plugin common functions.""" import zope.interface -from letsencrypt.acme.jose import util as jose_util +from acme.jose import util as jose_util from letsencrypt.client import interfaces diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index 49d5a6dd0..f7e64ebdb 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -9,7 +9,7 @@ import sys import zope.interface -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import constants as core_constants diff --git a/letsencrypt/client/plugins/nginx/tests/configurator_test.py b/letsencrypt/client/plugins/nginx/tests/configurator_test.py index cb5fef6bf..7be3177e3 100644 --- a/letsencrypt/client/plugins/nginx/tests/configurator_test.py +++ b/letsencrypt/client/plugins/nginx/tests/configurator_test.py @@ -4,8 +4,8 @@ import unittest import mock -from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 +from acme import challenges +from acme import messages2 from letsencrypt.client import achallenges from letsencrypt.client import errors diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index bf66367e6..24e32bafe 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -5,8 +5,8 @@ import shutil import mock -from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 +from acme import challenges +from acme import messages2 from letsencrypt.client import achallenges from letsencrypt.client import le_util @@ -25,9 +25,9 @@ class DvsniPerformTest(util.NginxTest): self.ssl_options) rsa256_file = pkg_resources.resource_filename( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") rsa256_pem = pkg_resources.resource_string( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") auth_key = le_util.Key(rsa256_file, rsa256_pem) diff --git a/letsencrypt/client/plugins/nginx/tests/util.py b/letsencrypt/client/plugins/nginx/tests/util.py index a1630b61f..47577daa1 100644 --- a/letsencrypt/client/plugins/nginx/tests/util.py +++ b/letsencrypt/client/plugins/nginx/tests/util.py @@ -25,9 +25,9 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods self.temp_dir, "testdata") self.rsa256_file = pkg_resources.resource_filename( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") self.rsa256_pem = pkg_resources.resource_string( - "letsencrypt.acme.jose", "testdata/rsa256_key.pem") + "acme.jose", "testdata/rsa256_key.pem") def get_data_filename(filename): diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/client/plugins/standalone/authenticator.py index a10ffd32d..dc8b7eac7 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/client/plugins/standalone/authenticator.py @@ -12,7 +12,7 @@ import OpenSSL.SSL import zope.component import zope.interface -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import interfaces diff --git a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py index 288a04fcc..230756e1d 100644 --- a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py @@ -10,7 +10,7 @@ import mock import OpenSSL.crypto import OpenSSL.SSL -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import le_util @@ -19,7 +19,7 @@ from letsencrypt.client.tests import acme_util KEY = le_util.Key("foo", pkg_resources.resource_string( - "letsencrypt.acme.jose", os.path.join("testdata", "rsa512_key.pem"))) + "acme.jose", os.path.join("testdata", "rsa512_key.pem"))) PRIVATE_KEY = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, KEY.pem) diff --git a/letsencrypt/client/recovery_token.py b/letsencrypt/client/recovery_token.py index f0c7d5839..3be0471ab 100644 --- a/letsencrypt/client/recovery_token.py +++ b/letsencrypt/client/recovery_token.py @@ -4,7 +4,7 @@ import os import zope.component -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import le_util from letsencrypt.client import interfaces diff --git a/letsencrypt/client/revoker.py b/letsencrypt/client/revoker.py index c18b5ffa6..31f2d85ce 100644 --- a/letsencrypt/client/revoker.py +++ b/letsencrypt/client/revoker.py @@ -16,8 +16,8 @@ import tempfile import Crypto.PublicKey.RSA import M2Crypto -from letsencrypt.acme import messages -from letsencrypt.acme.jose import util as jose_util +from acme import messages +from acme.jose import util as jose_util from letsencrypt.client import errors from letsencrypt.client import le_util diff --git a/letsencrypt/client/tests/account_test.py b/letsencrypt/client/tests/account_test.py index 6a79a94c7..c99edc8ee 100644 --- a/letsencrypt/client/tests/account_test.py +++ b/letsencrypt/client/tests/account_test.py @@ -7,7 +7,7 @@ import shutil import tempfile import unittest -from letsencrypt.acme import messages2 +from acme import messages2 from letsencrypt.client import configuration from letsencrypt.client import errors @@ -34,9 +34,9 @@ class AccountTest(unittest.TestCase): server="letsencrypt-demo.org") key_file = pkg_resources.resource_filename( - "letsencrypt.acme.jose", os.path.join("testdata", "rsa512_key.pem")) + "acme.jose", os.path.join("testdata", "rsa512_key.pem")) key_pem = pkg_resources.resource_string( - "letsencrypt.acme.jose", os.path.join("testdata", "rsa512_key.pem")) + "acme.jose", os.path.join("testdata", "rsa512_key.pem")) self.key = le_util.Key(key_file, key_pem) self.email = "client@letsencrypt.org" diff --git a/letsencrypt/client/tests/achallenges_test.py b/letsencrypt/client/tests/achallenges_test.py index 72c610f31..476ff7cab 100644 --- a/letsencrypt/client/tests/achallenges_test.py +++ b/letsencrypt/client/tests/achallenges_test.py @@ -6,7 +6,7 @@ import unittest import M2Crypto -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import le_util from letsencrypt.client.tests import acme_util @@ -19,8 +19,7 @@ class DVSNITest(unittest.TestCase): challenges.DVSNI(r="r_value", nonce="12345ABCDE"), "pending") self.response = challenges.DVSNIResponse() key = le_util.Key("path", pkg_resources.resource_string( - "letsencrypt.acme.jose", - os.path.join("testdata", "rsa512_key.pem"))) + "acme.jose", os.path.join("testdata", "rsa512_key.pem"))) from letsencrypt.client.achallenges import DVSNI self.achall = DVSNI(challb=self.chall, domain="example.com", key=key) diff --git a/letsencrypt/client/tests/acme_util.py b/letsencrypt/client/tests/acme_util.py index 0036844e0..93cc35e47 100644 --- a/letsencrypt/client/tests/acme_util.py +++ b/letsencrypt/client/tests/acme_util.py @@ -6,14 +6,14 @@ import pkg_resources import Crypto.PublicKey.RSA -from letsencrypt.acme import challenges -from letsencrypt.acme import jose -from letsencrypt.acme import messages2 +from acme import challenges +from acme import jose +from acme import messages2 KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( - "letsencrypt.acme.jose", os.path.join("testdata", "rsa512_key.pem")))) + "acme.jose", os.path.join("testdata", "rsa512_key.pem")))) # Challenges SIMPLE_HTTPS = challenges.SimpleHTTPS( @@ -106,7 +106,7 @@ def gen_authzr(authz_status, domain, challs, statuses, combos=True): """Generate an authorization resource. :param authz_status: Status object - :type authz_status: :class:`letsencrypt.acme.messages2.Status` + :type authz_status: :class:`acme.messages2.Status` :param list challs: Challenge objects :param list statuses: status of each challenge object :param bool combos: Whether or not to add combinations diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index c6e3b6153..b03f25735 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -5,8 +5,8 @@ import unittest import mock -from letsencrypt.acme import challenges -from letsencrypt.acme import messages2 +from acme import challenges +from acme import messages2 from letsencrypt.client import errors from letsencrypt.client import le_util @@ -447,9 +447,9 @@ def gen_path(required, challs): """Generate a combination by picking ``required`` from ``challs``. :param required: Required types of challenges (subclasses of - :class:`~letsencrypt.acme.challenges.Challenge`). + :class:`~acme.challenges.Challenge`). :param challs: Sequence of ACME challenge messages, corresponding to - :attr:`letsencrypt.acme.messages.Challenge.challenges`. + :attr:`acme.messages.Challenge.challenges`. :return: :class:`list` of :class:`int` diff --git a/letsencrypt/client/tests/continuity_auth_test.py b/letsencrypt/client/tests/continuity_auth_test.py index 7a2279bcd..b8b5e7402 100644 --- a/letsencrypt/client/tests/continuity_auth_test.py +++ b/letsencrypt/client/tests/continuity_auth_test.py @@ -3,7 +3,7 @@ import unittest import mock -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import errors diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index a36b96c99..f0c234598 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -11,9 +11,9 @@ import mock RSA256_KEY = pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa256_key.pem')) + 'acme.jose', os.path.join('testdata', 'rsa256_key.pem')) RSA512_KEY = pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem')) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')) class InitSaveKeyTest(unittest.TestCase): diff --git a/letsencrypt/client/tests/network2_test.py b/letsencrypt/client/tests/network2_test.py index d14d27f6a..1a5f7fd27 100644 --- a/letsencrypt/client/tests/network2_test.py +++ b/letsencrypt/client/tests/network2_test.py @@ -9,9 +9,9 @@ import M2Crypto import mock import requests -from letsencrypt.acme import challenges -from letsencrypt.acme import jose -from letsencrypt.acme import messages2 +from acme import challenges +from acme import jose +from acme import messages2 from letsencrypt.client import account from letsencrypt.client import errors @@ -27,9 +27,9 @@ CSR = jose.ComparableX509(M2Crypto.X509.load_request_string( pkg_resources.resource_string( __name__, os.path.join('testdata', 'csr.pem')))) KEY = jose.JWKRSA.load(pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem'))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem'))) KEY2 = jose.JWKRSA.load(pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa256_key.pem'))) + 'acme.jose', os.path.join('testdata', 'rsa256_key.pem'))) class NetworkTest(unittest.TestCase): diff --git a/letsencrypt/client/tests/recovery_token_test.py b/letsencrypt/client/tests/recovery_token_test.py index 0de31a8d0..c0d692c8c 100644 --- a/letsencrypt/client/tests/recovery_token_test.py +++ b/letsencrypt/client/tests/recovery_token_test.py @@ -6,7 +6,7 @@ import tempfile import mock -from letsencrypt.acme import challenges +from acme import challenges from letsencrypt.client import achallenges diff --git a/letsencrypt/client/tests/revoker_test.py b/letsencrypt/client/tests/revoker_test.py index 1ceb8ae9a..75510cc75 100644 --- a/letsencrypt/client/tests/revoker_test.py +++ b/letsencrypt/client/tests/revoker_test.py @@ -99,8 +99,7 @@ class RevokerTest(RevokerBase): mock_display().confirm_revocation.return_value = True key_path = pkg_resources.resource_filename( - "letsencrypt.acme.jose", os.path.join( - "testdata", "rsa256_key.pem")) + "acme.jose", os.path.join("testdata", "rsa256_key.pem")) wrong_key = le_util.Key(key_path, open(key_path).read()) self.revoker.revoke_from_key(wrong_key) diff --git a/setup.cfg b/setup.cfg index 75b1ef1a8..a9fa682f2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,5 +6,5 @@ dev = develop easy_install letsencrypt[dev,docs,testing] [nosetests] nocapture=1 -cover-package=letsencrypt +cover-package=letsencrypt,acme cover-erase=1 diff --git a/setup.py b/setup.py index 0302f1435..9256f48ff 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ setup( }, tests_require=install_requires, - test_suite='letsencrypt', + test_suite='letsencrypt', # TODO: test acme (this is run by tox.ini) entry_points={ 'console_scripts': [ diff --git a/tox.ini b/tox.ini index cd6f3c7b0..5c869d289 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ envlist = py26,py27,cover,lint commands = pip install -r requirements.txt -e .[testing] python setup.py test -q # -q does not suppress errors + # TODO: test_suite is set to letsencrypt only setenv = PYTHONPATH = {toxinidir} @@ -26,4 +27,4 @@ commands = basepython = python2.7 commands = pip install -e .[dev] - pylint --rcfile=.pylintrc letsencrypt + pylint --rcfile=.pylintrc letsencrypt acme From d408ec5a95efec94fcd936baef4018febdd162e9 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 10:47:58 +0000 Subject: [PATCH 63/76] Move plugins to top-level --- MANIFEST.in | 8 +-- docs/api/client/plugins/apache.rst | 29 ---------- docs/api/client/plugins/nginx.rst | 35 ----------- docs/api/{client => }/plugins/common.rst | 0 docs/api/{client => }/plugins/disco.rst | 0 docs/api/{client => }/plugins/standalone.rst | 0 docs/index.rst | 2 + docs/letsencrypt_apache.rst | 29 ++++++++++ docs/letsencrypt_nginx.rst | 35 +++++++++++ letsencrypt/client/plugins/apache/__init__.py | 1 - letsencrypt/client/plugins/nginx/__init__.py | 1 - letsencrypt/client/tests/client_test.py | 3 +- letsencrypt/client/tests/revoker_test.py | 3 +- letsencrypt_apache/__init__.py | 1 + .../configurator.py | 58 ++++++++----------- .../constants.py | 2 +- .../apache => letsencrypt_apache}/dvsni.py | 2 +- .../apache => letsencrypt_apache}/obj.py | 0 .../options-ssl.conf | 0 .../apache => letsencrypt_apache}/parser.py | 0 .../tests/__init__.py | 0 .../tests/configurator_test.py | 25 ++++---- .../tests/dvsni_test.py | 20 +++---- .../tests/obj_test.py | 14 ++--- .../tests/parser_test.py | 21 ++++--- .../default_vhost/apache2/apache2.conf | 0 .../other-vhosts-access-log.conf | 0 .../apache2/conf-available/security.conf | 0 .../apache2/conf-available/serve-cgi-bin.conf | 0 .../conf-enabled/other-vhosts-access-log.conf | 0 .../apache2/conf-enabled/security.conf | 0 .../apache2/conf-enabled/serve-cgi-bin.conf | 0 .../default_vhost/apache2/envvars | 0 .../apache2/mods-available/ssl.conf | 0 .../apache2/mods-available/ssl.load | 0 .../default_vhost/apache2/ports.conf | 0 .../apache2/sites-available/000-default.conf | 0 .../apache2/sites-available/default-ssl.conf | 0 .../apache2/sites-enabled/000-default.conf | 0 .../debian_apache_2_4/default_vhost/sites | 0 .../two_vhost_80/apache2/apache2.conf | 0 .../other-vhosts-access-log.conf | 0 .../apache2/conf-available/security.conf | 0 .../apache2/conf-available/serve-cgi-bin.conf | 0 .../conf-enabled/other-vhosts-access-log.conf | 0 .../apache2/conf-enabled/security.conf | 0 .../apache2/conf-enabled/serve-cgi-bin.conf | 0 .../two_vhost_80/apache2/envvars | 0 .../apache2/mods-available/ssl.conf | 0 .../apache2/mods-available/ssl.load | 0 .../two_vhost_80/apache2/ports.conf | 0 .../apache2/sites-available/000-default.conf | 0 .../apache2/sites-available/default-ssl.conf | 0 .../sites-available/encryption-example.conf | 0 .../apache2/sites-available/letsencrypt.conf | 0 .../apache2/sites-enabled/000-default.conf | 0 .../sites-enabled/encryption-example.conf | 0 .../apache2/sites-enabled/letsencrypt.conf | 0 .../debian_apache_2_4/two_vhost_80/sites | 0 .../tests/util.py | 12 ++-- letsencrypt_nginx/__init__.py | 1 + .../configurator.py | 10 ++-- .../nginx => letsencrypt_nginx}/constants.py | 2 +- .../nginx => letsencrypt_nginx}/dvsni.py | 2 +- .../nginxparser.py | 0 .../nginx => letsencrypt_nginx}/obj.py | 2 +- .../options-ssl.conf | 0 .../nginx => letsencrypt_nginx}/parser.py | 14 ++--- .../tests/__init__.py | 0 .../tests/configurator_test.py | 19 +++--- .../tests/dvsni_test.py | 12 ++-- .../tests/nginxparser_test.py | 8 +-- .../tests/obj_test.py | 16 ++--- .../tests/parser_test.py | 11 ++-- .../tests/testdata/foo.conf | 0 .../tests/testdata/mime.types | 0 .../tests/testdata/nginx.conf | 0 .../tests/testdata/nginx.new.conf | 0 .../tests/testdata/server.conf | 0 .../tests/testdata/sites-enabled/default | 0 .../tests/testdata/sites-enabled/example.com | 0 .../default_vhost/nginx/fastcgi_params | 0 .../default_vhost/nginx/koi-utf | 0 .../default_vhost/nginx/koi-win | 0 .../default_vhost/nginx/mime.types | 0 .../default_vhost/nginx/naxsi-ui.conf.1.4.1 | 0 .../default_vhost/nginx/naxsi.rules | 0 .../default_vhost/nginx/naxsi_core.rules | 0 .../default_vhost/nginx/nginx.conf | 0 .../default_vhost/nginx/proxy_params | 0 .../default_vhost/nginx/scgi_params | 0 .../nginx/sites-available/default | 0 .../default_vhost/nginx/sites-enabled/default | 0 .../default_vhost/nginx/uwsgi_params | 0 .../default_vhost/nginx/win-utf | 0 .../nginx => letsencrypt_nginx}/tests/util.py | 8 +-- setup.cfg | 2 +- setup.py | 8 +-- tox.ini | 2 +- 99 files changed, 199 insertions(+), 219 deletions(-) delete mode 100644 docs/api/client/plugins/apache.rst delete mode 100644 docs/api/client/plugins/nginx.rst rename docs/api/{client => }/plugins/common.rst (100%) rename docs/api/{client => }/plugins/disco.rst (100%) rename docs/api/{client => }/plugins/standalone.rst (100%) create mode 100644 docs/letsencrypt_apache.rst create mode 100644 docs/letsencrypt_nginx.rst delete mode 100644 letsencrypt/client/plugins/apache/__init__.py delete mode 100644 letsencrypt/client/plugins/nginx/__init__.py create mode 100644 letsencrypt_apache/__init__.py rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/configurator.py (95%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/constants.py (90%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/dvsni.py (99%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/obj.py (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/options-ssl.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/parser.py (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/__init__.py (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/configurator_test.py (91%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/dvsni_test.py (91%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/obj_test.py (82%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/parser_test.py (83%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/default_vhost/sites (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/apache2.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/other-vhosts-access-log.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/security.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/serve-cgi-bin.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/other-vhosts-access-log.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/security.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/serve-cgi-bin.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/envvars (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.load (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/ports.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/000-default.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/encryption-example.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/letsencrypt.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/000-default.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/encryption-example.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/letsencrypt.conf (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/testdata/debian_apache_2_4/two_vhost_80/sites (100%) rename {letsencrypt/client/plugins/apache => letsencrypt_apache}/tests/util.py (91%) create mode 100644 letsencrypt_nginx/__init__.py rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/configurator.py (98%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/constants.py (84%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/dvsni.py (97%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/nginxparser.py (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/obj.py (98%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/options-ssl.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/parser.py (97%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/__init__.py (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/configurator_test.py (94%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/dvsni_test.py (88%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/nginxparser_test.py (94%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/obj_test.py (87%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/parser_test.py (97%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/foo.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/mime.types (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/nginx.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/nginx.new.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/server.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/sites-enabled/default (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/sites-enabled/example.com (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf (100%) rename {letsencrypt/client/plugins/nginx => letsencrypt_nginx}/tests/util.py (90%) diff --git a/MANIFEST.in b/MANIFEST.in index 79c87e8f0..b628121e1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,8 +9,8 @@ recursive-include letsencrypt/client/tests/testdata * recursive-include acme/schemata *.json recursive-include acme/jose/testdata * -recursive-include letsencrypt/client/plugins/apache/tests/testdata * -include letsencrypt/client/plugins/apache/options-ssl.conf +recursive-include letsencrypt_apache/tests/testdata * +include letsencrypt_apache/options-ssl.conf -recursive-include letsencrypt/client/plugins/nginx/tests/testdata * -include letsencrypt/client/plugins/nginx/options-ssl.conf +recursive-include letsencrypt_nginx/tests/testdata * +include letsencrypt_nginx/options-ssl.conf diff --git a/docs/api/client/plugins/apache.rst b/docs/api/client/plugins/apache.rst deleted file mode 100644 index 6e6e6c462..000000000 --- a/docs/api/client/plugins/apache.rst +++ /dev/null @@ -1,29 +0,0 @@ -:mod:`letsencrypt.client.plugins.apache` ----------------------------------------- - -.. automodule:: letsencrypt.client.plugins.apache - :members: - -:mod:`letsencrypt.client.plugins.apache.configurator` -===================================================== - -.. automodule:: letsencrypt.client.plugins.apache.configurator - :members: - -:mod:`letsencrypt.client.plugins.apache.dvsni` -============================================== - -.. automodule:: letsencrypt.client.plugins.apache.dvsni - :members: - -:mod:`letsencrypt.client.plugins.apache.obj` -============================================ - -.. automodule:: letsencrypt.client.plugins.apache.obj - :members: - -:mod:`letsencrypt.client.plugins.apache.parser` -=============================================== - -.. automodule:: letsencrypt.client.plugins.apache.parser - :members: diff --git a/docs/api/client/plugins/nginx.rst b/docs/api/client/plugins/nginx.rst deleted file mode 100644 index cd64846bf..000000000 --- a/docs/api/client/plugins/nginx.rst +++ /dev/null @@ -1,35 +0,0 @@ -:mod:`letsencrypt.client.plugins.nginx` ----------------------------------------- - -.. automodule:: letsencrypt.client.plugins.nginx - :members: - -:mod:`letsencrypt.client.plugins.nginx.configurator` -===================================================== - -.. automodule:: letsencrypt.client.plugins.nginx.configurator - :members: - -:mod:`letsencrypt.client.plugins.nginx.dvsni` -============================================== - -.. automodule:: letsencrypt.client.plugins.nginx.dvsni - :members: - -:mod:`letsencrypt.client.plugins.nginx.obj` -============================================ - -.. automodule:: letsencrypt.client.plugins.nginx.obj - :members: - -:mod:`letsencrypt.client.plugins.nginx.parser` -=============================================== - -.. automodule:: letsencrypt.client.plugins.nginx.parser - :members: - -:mod:`letsencrypt.client.plugins.nginx.nginxparser` -==================================================== - -.. automodule:: letsencrypt.client.plugins.nginx.nginxparser - :members: diff --git a/docs/api/client/plugins/common.rst b/docs/api/plugins/common.rst similarity index 100% rename from docs/api/client/plugins/common.rst rename to docs/api/plugins/common.rst diff --git a/docs/api/client/plugins/disco.rst b/docs/api/plugins/disco.rst similarity index 100% rename from docs/api/client/plugins/disco.rst rename to docs/api/plugins/disco.rst diff --git a/docs/api/client/plugins/standalone.rst b/docs/api/plugins/standalone.rst similarity index 100% rename from docs/api/client/plugins/standalone.rst rename to docs/api/plugins/standalone.rst diff --git a/docs/index.rst b/docs/index.rst index 2edf74636..6bd5c06c9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,8 @@ Welcome to the Let's Encrypt client documentation! contributing plugins acme + letsencrypt_apache + letsencrypt_nginx .. toctree:: :maxdepth: 1 diff --git a/docs/letsencrypt_apache.rst b/docs/letsencrypt_apache.rst new file mode 100644 index 000000000..44966cca6 --- /dev/null +++ b/docs/letsencrypt_apache.rst @@ -0,0 +1,29 @@ +:mod:`letsencrypt_apache` +------------------------- + +.. automodule:: letsencrypt_apache + :members: + +:mod:`letsencrypt_apache.configurator` +====================================== + +.. automodule:: letsencrypt_apache.configurator + :members: + +:mod:`letsencrypt_apache.dvsni` +=============================== + +.. automodule:: letsencrypt_apache.dvsni + :members: + +:mod:`letsencrypt_apache.obj` +============================= + +.. automodule:: letsencrypt_apache.obj + :members: + +:mod:`letsencrypt_apache.parser` +================================ + +.. automodule:: letsencrypt_apache.parser + :members: diff --git a/docs/letsencrypt_nginx.rst b/docs/letsencrypt_nginx.rst new file mode 100644 index 000000000..03114b685 --- /dev/null +++ b/docs/letsencrypt_nginx.rst @@ -0,0 +1,35 @@ +:mod:`letsencrypt_nginx` +------------------------ + +.. automodule:: letsencrypt_nginx + :members: + +:mod:`letsencrypt_nginx.configurator` +===================================== + +.. automodule:: letsencrypt_nginx.configurator + :members: + +:mod:`letsencrypt_nginx.dvsni` +============================== + +.. automodule:: letsencrypt_nginx.dvsni + :members: + +:mod:`letsencrypt_nginx.obj` +============================ + +.. automodule:: letsencrypt_nginx.obj + :members: + +:mod:`letsencrypt_nginx.parser` +=============================== + +.. automodule:: letsencrypt_nginx.parser + :members: + +:mod:`letsencrypt_nginx.nginxparser` +==================================== + +.. automodule:: letsencrypt_nginx.nginxparser + :members: diff --git a/letsencrypt/client/plugins/apache/__init__.py b/letsencrypt/client/plugins/apache/__init__.py deleted file mode 100644 index 70172b06d..000000000 --- a/letsencrypt/client/plugins/apache/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt client.plugins.apache.""" diff --git a/letsencrypt/client/plugins/nginx/__init__.py b/letsencrypt/client/plugins/nginx/__init__.py deleted file mode 100644 index 63728924f..000000000 --- a/letsencrypt/client/plugins/nginx/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt client.plugins.nginx.""" diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index 33530a083..52fc1be94 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -56,8 +56,7 @@ class DetermineAccountTest(unittest.TestCase): class RollbackTest(unittest.TestCase): """Test the rollback function.""" def setUp(self): - from letsencrypt.client.plugins.apache.configurator import ( - ApacheConfigurator) + from letsencrypt_apache.configurator import ApacheConfigurator self.m_install = mock.MagicMock(spec=ApacheConfigurator) @classmethod diff --git a/letsencrypt/client/tests/revoker_test.py b/letsencrypt/client/tests/revoker_test.py index 75510cc75..6810a1115 100644 --- a/letsencrypt/client/tests/revoker_test.py +++ b/letsencrypt/client/tests/revoker_test.py @@ -10,9 +10,10 @@ import mock from letsencrypt.client import errors from letsencrypt.client import le_util -from letsencrypt.client.plugins.apache import configurator from letsencrypt.client.display import util as display_util +from letsencrypt_apache import configurator + class RevokerBase(unittest.TestCase): # pylint: disable=too-few-public-methods """Base Class for Revoker Tests.""" diff --git a/letsencrypt_apache/__init__.py b/letsencrypt_apache/__init__.py new file mode 100644 index 000000000..c0d1e0d52 --- /dev/null +++ b/letsencrypt_apache/__init__.py @@ -0,0 +1 @@ +"""Let's Encrypt Apache plugin.""" diff --git a/letsencrypt/client/plugins/apache/configurator.py b/letsencrypt_apache/configurator.py similarity index 95% rename from letsencrypt/client/plugins/apache/configurator.py rename to letsencrypt_apache/configurator.py index 7006e7d2f..a7c7b68dc 100644 --- a/letsencrypt/client/plugins/apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -18,10 +18,10 @@ from letsencrypt.client import errors from letsencrypt.client import interfaces from letsencrypt.client import le_util -from letsencrypt.client.plugins.apache import constants -from letsencrypt.client.plugins.apache import dvsni -from letsencrypt.client.plugins.apache import obj -from letsencrypt.client.plugins.apache import parser +from letsencrypt_apache import constants +from letsencrypt_apache import dvsni +from letsencrypt_apache import obj +from letsencrypt_apache import parser # TODO: Augeas sections ie. , beginning and closing @@ -69,12 +69,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :type config: :class:`~letsencrypt.client.interfaces.IConfig` :ivar parser: Handles low level parsing - :type parser: :class:`~letsencrypt.client.plugins.apache.parser` + :type parser: :class:`~letsencrypt_apache.parser` :ivar tup version: version of Apache :ivar list vhosts: All vhosts found in the configuration - (:class:`list` of - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`) + (:class:`list` of :class:`~letsencrypt_apache.obj.VirtualHost`) :ivar dict assoc: Mapping between domains and vhosts @@ -222,7 +221,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str target_name: domain name :returns: ssl vhost associated with name - :rtype: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :rtype: :class:`~letsencrypt_apache.obj.VirtualHost` """ # Allows for domain names to be associated with a virtual host @@ -263,7 +262,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str domain: domain name to associate :param vhost: virtual host to associate with domain - :type vhost: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type vhost: :class:`~letsencrypt_apache.obj.VirtualHost` """ self.assoc[domain] = vhost @@ -300,7 +299,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Helper function for get_virtual_hosts(). :param host: In progress vhost whose names will be added - :type host: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type host: :class:`~letsencrypt_apache.obj.VirtualHost` """ name_match = self.aug.match(("%s//*[self::directive=~regexp('%s')] | " @@ -321,7 +320,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str path: Augeas path to virtual host :returns: newly created vhost - :rtype: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :rtype: :class:`~letsencrypt_apache.obj.VirtualHost` """ addrs = set() @@ -344,9 +343,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def get_virtual_hosts(self): """Returns list of virtual hosts found in the Apache configuration. - :returns: List of - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` objects - found in configuration + :returns: List of :class:`~letsencrypt_apache.obj.VirtualHost` + objects found in configuration :rtype: list """ @@ -423,7 +421,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Checks to see if the server is ready for SNI challenges. :param vhost: VirtualHost to check SNI compatibility - :type vhost: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :param str default_addr: TODO - investigate function further @@ -455,11 +453,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param nonssl_vhost: Valid VH that doesn't have SSLEngine on - :type nonssl_vhost: - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type nonssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: SSL vhost - :rtype: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :rtype: :class:`~letsencrypt_apache.obj.VirtualHost` """ avail_fp = nonssl_vhost.filep @@ -579,15 +576,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :param unused_options: Not currently used :type unused_options: Not Available :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`) + :rtype: (bool, :class:`~letsencrypt_apache.obj.VirtualHost`) """ if not mod_loaded("rewrite_module", self.conf('ctl')): @@ -638,7 +633,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): -1 is also returned in case of no redirection/rewrite directives :param vhost: vhost to check - :type vhost: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: Success, code value... see documentation :rtype: bool, int @@ -670,12 +665,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Creates an http_vhost specifically to redirect for the ssl_vhost. :param ssl_vhost: ssl vhost - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: tuple of the form - (`success`, - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost`) + (`success`, :class:`~letsencrypt_apache.obj.VirtualHost`) :rtype: tuple """ @@ -758,8 +751,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if not conflict: returns space separated list of new host addrs :param ssl_vhost: SSL Vhost to check for possible port 80 redirection - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: TODO :rtype: TODO @@ -792,12 +784,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): Consider changing this into a dict check :param ssl_vhost: ssl vhost to check - :type ssl_vhost: - :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: HTTP vhost or None if unsuccessful - :rtype: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` - or None + :rtype: :class:`~letsencrypt_apache.obj.VirtualHost` or ``None`` """ # _default_:443 check @@ -887,7 +877,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. todo:: Make sure link is not broken... :param vhost: vhost to enable - :type vhost: :class:`~letsencrypt.client.plugins.apache.obj.VirtualHost` + :type vhost: :class:`~letsencrypt_apache.obj.VirtualHost` :returns: Success :rtype: bool diff --git a/letsencrypt/client/plugins/apache/constants.py b/letsencrypt_apache/constants.py similarity index 90% rename from letsencrypt/client/plugins/apache/constants.py rename to letsencrypt_apache/constants.py index d9f2a0b9d..b40e2ac65 100644 --- a/letsencrypt/client/plugins/apache/constants.py +++ b/letsencrypt_apache/constants.py @@ -13,7 +13,7 @@ CLI_DEFAULTS = dict( MOD_SSL_CONF = pkg_resources.resource_filename( - "letsencrypt.client.plugins.apache", "options-ssl.conf") + "letsencrypt_apache", "options-ssl.conf") """Path to the Apache mod_ssl config file found in the Let's Encrypt distribution.""" diff --git a/letsencrypt/client/plugins/apache/dvsni.py b/letsencrypt_apache/dvsni.py similarity index 99% rename from letsencrypt/client/plugins/apache/dvsni.py rename to letsencrypt_apache/dvsni.py index 7755658e7..790c4e025 100644 --- a/letsencrypt/client/plugins/apache/dvsni.py +++ b/letsencrypt_apache/dvsni.py @@ -2,7 +2,7 @@ import logging import os -from letsencrypt.client.plugins.apache import parser +from letsencrypt_apache import parser class ApacheDvsni(object): diff --git a/letsencrypt/client/plugins/apache/obj.py b/letsencrypt_apache/obj.py similarity index 100% rename from letsencrypt/client/plugins/apache/obj.py rename to letsencrypt_apache/obj.py diff --git a/letsencrypt/client/plugins/apache/options-ssl.conf b/letsencrypt_apache/options-ssl.conf similarity index 100% rename from letsencrypt/client/plugins/apache/options-ssl.conf rename to letsencrypt_apache/options-ssl.conf diff --git a/letsencrypt/client/plugins/apache/parser.py b/letsencrypt_apache/parser.py similarity index 100% rename from letsencrypt/client/plugins/apache/parser.py rename to letsencrypt_apache/parser.py diff --git a/letsencrypt/client/plugins/apache/tests/__init__.py b/letsencrypt_apache/tests/__init__.py similarity index 100% rename from letsencrypt/client/plugins/apache/tests/__init__.py rename to letsencrypt_apache/tests/__init__.py diff --git a/letsencrypt/client/plugins/apache/tests/configurator_test.py b/letsencrypt_apache/tests/configurator_test.py similarity index 91% rename from letsencrypt/client/plugins/apache/tests/configurator_test.py rename to letsencrypt_apache/tests/configurator_test.py index b4881d63b..d7e6b95cf 100644 --- a/letsencrypt/client/plugins/apache/tests/configurator_test.py +++ b/letsencrypt_apache/tests/configurator_test.py @@ -1,4 +1,4 @@ -"""Test for letsencrypt.client.plugins.apache.configurator.""" +"""Test for letsencrypt_apache.configurator.""" import os import re import shutil @@ -12,14 +12,14 @@ from letsencrypt.client import achallenges from letsencrypt.client import errors from letsencrypt.client import le_util -from letsencrypt.client.plugins.apache import configurator -from letsencrypt.client.plugins.apache import obj -from letsencrypt.client.plugins.apache import parser - -from letsencrypt.client.plugins.apache.tests import util - from letsencrypt.client.tests import acme_util +from letsencrypt_apache import configurator +from letsencrypt_apache import obj +from letsencrypt_apache import parser + +from letsencrypt_apache.tests import util + class TwoVhost80Test(util.ApacheTest): """Test two standard well configured HTTP vhosts.""" @@ -27,7 +27,7 @@ class TwoVhost80Test(util.ApacheTest): def setUp(self): super(TwoVhost80Test, self).setUp() - with mock.patch("letsencrypt.client.plugins.apache.configurator." + with mock.patch("letsencrypt_apache.configurator." "mod_loaded") as mock_load: mock_load.return_value = True self.config = util.get_apache_configurator( @@ -150,10 +150,8 @@ class TwoVhost80Test(util.ApacheTest): self.assertEqual(len(self.config.vhosts), 5) - @mock.patch("letsencrypt.client.plugins.apache.configurator." - "dvsni.ApacheDvsni.perform") - @mock.patch("letsencrypt.client.plugins.apache.configurator." - "ApacheConfigurator.restart") + @mock.patch("letsencrypt_apache.configurator.dvsni.ApacheDvsni.perform") + @mock.patch("letsencrypt_apache.configurator.ApacheConfigurator.restart") def test_perform(self, mock_restart, mock_dvsni_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -186,8 +184,7 @@ class TwoVhost80Test(util.ApacheTest): self.assertEqual(mock_restart.call_count, 1) - @mock.patch("letsencrypt.client.plugins.apache.configurator." - "subprocess.Popen") + @mock.patch("letsencrypt_apache.configurator.subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( "Server Version: Apache/2.4.2 (Debian)", "") diff --git a/letsencrypt/client/plugins/apache/tests/dvsni_test.py b/letsencrypt_apache/tests/dvsni_test.py similarity index 91% rename from letsencrypt/client/plugins/apache/tests/dvsni_test.py rename to letsencrypt_apache/tests/dvsni_test.py index 9694ce69b..323bf8c31 100644 --- a/letsencrypt/client/plugins/apache/tests/dvsni_test.py +++ b/letsencrypt_apache/tests/dvsni_test.py @@ -1,4 +1,4 @@ -"""Test for letsencrypt.client.plugins.apache.dvsni.""" +"""Test for letsencrypt_apache.dvsni.""" import pkg_resources import unittest import shutil @@ -10,12 +10,11 @@ from acme import challenges from letsencrypt.client import achallenges from letsencrypt.client import le_util -from letsencrypt.client.plugins.apache.obj import Addr - -from letsencrypt.client.plugins.apache.tests import util - from letsencrypt.client.tests import acme_util +from letsencrypt_apache import obj +from letsencrypt_apache.tests import util + class DvsniPerformTest(util.ApacheTest): """Test the ApacheDVSNI challenge.""" @@ -23,14 +22,14 @@ class DvsniPerformTest(util.ApacheTest): def setUp(self): super(DvsniPerformTest, self).setUp() - with mock.patch("letsencrypt.client.plugins.apache.configurator." + with mock.patch("letsencrypt_apache.configurator." "mod_loaded") as mock_load: mock_load.return_value = True config = util.get_apache_configurator( self.config_path, self.config_dir, self.work_dir, self.ssl_options) - from letsencrypt.client.plugins.apache import dvsni + from letsencrypt_apache import dvsni self.sni = dvsni.ApacheDvsni(config) rsa256_file = pkg_resources.resource_filename( @@ -80,8 +79,7 @@ class DvsniPerformTest(util.ApacheTest): nonce_domain=self.achalls[0].nonce_domain) achall.gen_cert_and_response.return_value = ("pem", response) - with mock.patch("letsencrypt.client.plugins.apache.dvsni.open", - m_open, create=True): + with mock.patch("letsencrypt_apache.dvsni.open", m_open, create=True): # pylint: disable=protected-access self.assertEqual(response, self.sni._setup_challenge_cert( achall, "randomS1")) @@ -142,8 +140,8 @@ class DvsniPerformTest(util.ApacheTest): def test_mod_config(self): for achall in self.achalls: self.sni.add_chall(achall) - v_addr1 = [Addr(("1.2.3.4", "443")), Addr(("5.6.7.8", "443"))] - v_addr2 = [Addr(("127.0.0.1", "443"))] + v_addr1 = [obj.Addr(("1.2.3.4", "443")), obj.Addr(("5.6.7.8", "443"))] + v_addr2 = [obj.Addr(("127.0.0.1", "443"))] ll_addr = [] ll_addr.append(v_addr1) ll_addr.append(v_addr2) diff --git a/letsencrypt/client/plugins/apache/tests/obj_test.py b/letsencrypt_apache/tests/obj_test.py similarity index 82% rename from letsencrypt/client/plugins/apache/tests/obj_test.py rename to letsencrypt_apache/tests/obj_test.py index b0c65eadb..5ad683fae 100644 --- a/letsencrypt/client/plugins/apache/tests/obj_test.py +++ b/letsencrypt_apache/tests/obj_test.py @@ -1,11 +1,11 @@ -"""Test the helper objects in letsencrypt.client.plugins.apache.obj.""" +"""Test the helper objects in letsencrypt_apache.obj.""" import unittest class AddrTest(unittest.TestCase): """Test the Addr class.""" def setUp(self): - from letsencrypt.client.plugins.apache.obj import Addr + from letsencrypt_apache.obj import Addr self.addr1 = Addr.fromstring("192.168.1.1") self.addr2 = Addr.fromstring("192.168.1.1:*") self.addr3 = Addr.fromstring("192.168.1.1:80") @@ -34,7 +34,7 @@ class AddrTest(unittest.TestCase): self.assertFalse(self.addr1 == 3333) def test_set_inclusion(self): - from letsencrypt.client.plugins.apache.obj import Addr + from letsencrypt_apache.obj import Addr set_a = set([self.addr1, self.addr2]) addr1b = Addr.fromstring("192.168.1.1") addr2b = Addr.fromstring("192.168.1.1:*") @@ -46,15 +46,15 @@ class AddrTest(unittest.TestCase): class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from letsencrypt.client.plugins.apache.obj import VirtualHost - from letsencrypt.client.plugins.apache.obj import Addr + from letsencrypt_apache.obj import VirtualHost + from letsencrypt_apache.obj import Addr self.vhost1 = VirtualHost( "filep", "vh_path", set([Addr.fromstring("localhost")]), False, False) def test_eq(self): - from letsencrypt.client.plugins.apache.obj import Addr - from letsencrypt.client.plugins.apache.obj import VirtualHost + from letsencrypt_apache.obj import Addr + from letsencrypt_apache.obj import VirtualHost vhost1b = VirtualHost( "filep", "vh_path", set([Addr.fromstring("localhost")]), False, False) diff --git a/letsencrypt/client/plugins/apache/tests/parser_test.py b/letsencrypt_apache/tests/parser_test.py similarity index 83% rename from letsencrypt/client/plugins/apache/tests/parser_test.py rename to letsencrypt_apache/tests/parser_test.py index 1696841f8..b8fbb5dea 100644 --- a/letsencrypt/client/plugins/apache/tests/parser_test.py +++ b/letsencrypt_apache/tests/parser_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.plugins.apache.parser.""" +"""Tests for letsencrypt_apache.parser.""" import os import shutil import sys @@ -11,7 +11,7 @@ import zope.component from letsencrypt.client import errors from letsencrypt.client.display import util as display_util -from letsencrypt.client.plugins.apache.tests import util +from letsencrypt_apache.tests import util class ApacheParserTest(util.ApacheTest): @@ -22,7 +22,7 @@ class ApacheParserTest(util.ApacheTest): zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) - from letsencrypt.client.plugins.apache.parser import ApacheParser + from letsencrypt_apache.parser import ApacheParser self.aug = augeas.Augeas(flags=augeas.Augeas.NONE) self.parser = ApacheParser(self.aug, self.config_path, self.ssl_options) @@ -32,19 +32,19 @@ class ApacheParserTest(util.ApacheTest): shutil.rmtree(self.work_dir) def test_root_normalized(self): - from letsencrypt.client.plugins.apache.parser import ApacheParser + from letsencrypt_apache.parser import ApacheParser path = os.path.join(self.temp_dir, "debian_apache_2_4/////" "two_vhost_80/../two_vhost_80/apache2") parser = ApacheParser(self.aug, path, None) self.assertEqual(parser.root, self.config_path) def test_root_absolute(self): - from letsencrypt.client.plugins.apache.parser import ApacheParser + from letsencrypt_apache.parser import ApacheParser parser = ApacheParser(self.aug, os.path.relpath(self.config_path), None) self.assertEqual(parser.root, self.config_path) def test_root_no_trailing_slash(self): - from letsencrypt.client.plugins.apache.parser import ApacheParser + from letsencrypt_apache.parser import ApacheParser parser = ApacheParser(self.aug, self.config_path + os.path.sep, None) self.assertEqual(parser.root, self.config_path) @@ -67,7 +67,7 @@ class ApacheParserTest(util.ApacheTest): self.assertTrue(matches) def test_find_dir(self): - from letsencrypt.client.plugins.apache.parser import case_i + from letsencrypt_apache.parser import case_i test = self.parser.find_dir(case_i("Listen"), "443") # This will only look in enabled hosts test2 = self.parser.find_dir(case_i("documentroot")) @@ -92,7 +92,7 @@ class ApacheParserTest(util.ApacheTest): Path must be valid before attempting to add to augeas """ - from letsencrypt.client.plugins.apache.parser import get_aug_path + from letsencrypt_apache.parser import get_aug_path self.parser.add_dir_to_ifmodssl( get_aug_path(self.parser.loc["default"]), "FakeDirective", "123") @@ -103,12 +103,11 @@ class ApacheParserTest(util.ApacheTest): self.assertTrue("IfModule" in matches[0]) def test_get_aug_path(self): - from letsencrypt.client.plugins.apache.parser import get_aug_path + from letsencrypt_apache.parser import get_aug_path self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) def test_set_locations(self): - with mock.patch("letsencrypt.client.plugins.apache.parser." - "os.path") as mock_path: + with mock.patch("letsencrypt_apache.parser.os.path") as mock_path: mock_path.isfile.return_value = False diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/sites b/letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/sites similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/default_vhost/sites rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/default_vhost/sites diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/apache2.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/apache2.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/apache2.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/apache2.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/other-vhosts-access-log.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/other-vhosts-access-log.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/other-vhosts-access-log.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/other-vhosts-access-log.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/security.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/security.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/security.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/security.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/serve-cgi-bin.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/serve-cgi-bin.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/serve-cgi-bin.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-available/serve-cgi-bin.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/other-vhosts-access-log.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/other-vhosts-access-log.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/other-vhosts-access-log.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/other-vhosts-access-log.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/security.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/security.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/security.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/security.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/serve-cgi-bin.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/serve-cgi-bin.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/serve-cgi-bin.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/conf-enabled/serve-cgi-bin.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/envvars b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/envvars similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/envvars rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/envvars diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.load b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.load similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.load rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/mods-available/ssl.load diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/ports.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/ports.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/ports.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/ports.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/000-default.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/000-default.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/000-default.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/000-default.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/encryption-example.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/encryption-example.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/encryption-example.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/encryption-example.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/letsencrypt.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/letsencrypt.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/letsencrypt.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/letsencrypt.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/000-default.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/000-default.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/000-default.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/000-default.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/encryption-example.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/encryption-example.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/encryption-example.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/encryption-example.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/letsencrypt.conf b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/letsencrypt.conf similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/letsencrypt.conf rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-enabled/letsencrypt.conf diff --git a/letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/sites b/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/sites similarity index 100% rename from letsencrypt/client/plugins/apache/tests/testdata/debian_apache_2_4/two_vhost_80/sites rename to letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/sites diff --git a/letsencrypt/client/plugins/apache/tests/util.py b/letsencrypt_apache/tests/util.py similarity index 91% rename from letsencrypt/client/plugins/apache/tests/util.py rename to letsencrypt_apache/tests/util.py index 98c06af40..12e3a5140 100644 --- a/letsencrypt/client/plugins/apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -1,4 +1,4 @@ -"""Common utilities for letsencrypt.client.plugins.apache.""" +"""Common utilities for letsencrypt_apache.""" import os import pkg_resources import shutil @@ -7,9 +7,9 @@ import unittest import mock -from letsencrypt.client.plugins.apache import configurator -from letsencrypt.client.plugins.apache import constants -from letsencrypt.client.plugins.apache import obj +from letsencrypt_apache import configurator +from letsencrypt_apache import constants +from letsencrypt_apache import obj class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods @@ -38,7 +38,7 @@ def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"): work_dir = tempfile.mkdtemp("work") test_configs = pkg_resources.resource_filename( - "letsencrypt.client.plugins.apache.tests", "testdata/%s" % test_dir) + "letsencrypt_apache.tests", "testdata/%s" % test_dir) shutil.copytree( test_configs, os.path.join(temp_dir, test_dir), symlinks=True) @@ -59,7 +59,7 @@ def get_apache_configurator( backups = os.path.join(work_dir, "backups") - with mock.patch("letsencrypt.client.plugins.apache.configurator." + with mock.patch("letsencrypt_apache.configurator." "subprocess.Popen") as mock_popen: # This just states that the ssl module is already loaded mock_popen().communicate.return_value = ("ssl_module", "") diff --git a/letsencrypt_nginx/__init__.py b/letsencrypt_nginx/__init__.py new file mode 100644 index 000000000..34db9673d --- /dev/null +++ b/letsencrypt_nginx/__init__.py @@ -0,0 +1 @@ +"""Let's Encrypt nginx plugin.""" diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt_nginx/configurator.py similarity index 98% rename from letsencrypt/client/plugins/nginx/configurator.py rename to letsencrypt_nginx/configurator.py index f7e64ebdb..e1d3bd367 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt_nginx/configurator.py @@ -20,9 +20,9 @@ from letsencrypt.client import reverter from letsencrypt.client.plugins import common -from letsencrypt.client.plugins.nginx import constants -from letsencrypt.client.plugins.nginx import dvsni -from letsencrypt.client.plugins.nginx import parser +from letsencrypt_nginx import constants +from letsencrypt_nginx import dvsni +from letsencrypt_nginx import parser class NginxConfigurator(common.Plugin): @@ -38,7 +38,7 @@ class NginxConfigurator(common.Plugin): :type config: :class:`~letsencrypt.client.interfaces.IConfig` :ivar parser: Handles low level parsing - :type parser: :class:`~letsencrypt.client.plugins.nginx.parser` + :type parser: :class:`~letsencrypt_nginx.parser` :ivar str save_notes: Human-readable config change notes @@ -166,7 +166,7 @@ class NginxConfigurator(common.Plugin): :param str target_name: domain name :returns: ssl vhost associated with name - :rtype: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` + :rtype: :class:`~letsencrypt_nginx.obj.VirtualHost` """ vhost = None diff --git a/letsencrypt/client/plugins/nginx/constants.py b/letsencrypt_nginx/constants.py similarity index 84% rename from letsencrypt/client/plugins/nginx/constants.py rename to letsencrypt_nginx/constants.py index 17d05f438..6c15b1664 100644 --- a/letsencrypt/client/plugins/nginx/constants.py +++ b/letsencrypt_nginx/constants.py @@ -11,6 +11,6 @@ CLI_DEFAULTS = dict( MOD_SSL_CONF = pkg_resources.resource_filename( - "letsencrypt.client.plugins.nginx", "options-ssl.conf") + "letsencrypt_nginx", "options-ssl.conf") """Path to the Nginx mod_ssl config file found in the Let's Encrypt distribution.""" diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt_nginx/dvsni.py similarity index 97% rename from letsencrypt/client/plugins/nginx/dvsni.py rename to letsencrypt_nginx/dvsni.py index 7233d7c62..c845db916 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt_nginx/dvsni.py @@ -1,7 +1,7 @@ """NginxDVSNI""" import logging -from letsencrypt.client.plugins.apache.dvsni import ApacheDvsni +from letsencrypt_apache.dvsni import ApacheDvsni class NginxDvsni(ApacheDvsni): diff --git a/letsencrypt/client/plugins/nginx/nginxparser.py b/letsencrypt_nginx/nginxparser.py similarity index 100% rename from letsencrypt/client/plugins/nginx/nginxparser.py rename to letsencrypt_nginx/nginxparser.py diff --git a/letsencrypt/client/plugins/nginx/obj.py b/letsencrypt_nginx/obj.py similarity index 98% rename from letsencrypt/client/plugins/nginx/obj.py rename to letsencrypt_nginx/obj.py index acaacb3b0..3b3482beb 100644 --- a/letsencrypt/client/plugins/nginx/obj.py +++ b/letsencrypt_nginx/obj.py @@ -1,7 +1,7 @@ """Module contains classes used by the Nginx Configurator.""" import re -from letsencrypt.client.plugins.apache.obj import Addr as ApacheAddr +from letsencrypt_apache.obj import Addr as ApacheAddr class Addr(ApacheAddr): diff --git a/letsencrypt/client/plugins/nginx/options-ssl.conf b/letsencrypt_nginx/options-ssl.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/options-ssl.conf rename to letsencrypt_nginx/options-ssl.conf diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt_nginx/parser.py similarity index 97% rename from letsencrypt/client/plugins/nginx/parser.py rename to letsencrypt_nginx/parser.py index 55a0b01e8..7e11fe0c3 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt_nginx/parser.py @@ -6,8 +6,9 @@ import pyparsing import re from letsencrypt.client import errors -from letsencrypt.client.plugins.nginx import obj -from letsencrypt.client.plugins.nginx.nginxparser import dump, load + +from letsencrypt_nginx import obj +from letsencrypt_nginx import nginxparser class NginxParser(object): @@ -85,9 +86,8 @@ class NginxParser(object): Technically this is a misnomer because Nginx does not have virtual hosts, it has 'server blocks'. - :returns: List of - :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` objects - found in configuration + :returns: List of :class:`~letsencrypt_nginx.obj.VirtualHost` + objects found in configuration :rtype: list """ @@ -159,7 +159,7 @@ class NginxParser(object): continue try: with open(item) as _file: - parsed = load(_file) + parsed = nginxparser.load(_file) self.parsed[item] = parsed trees.append(parsed) except IOError: @@ -213,7 +213,7 @@ class NginxParser(object): filename = filename + os.path.extsep + ext try: with open(filename, 'w') as _file: - dump(tree, _file) + nginxparser.dump(tree, _file) except IOError: logging.error("Could not open file for writing: %s", filename) diff --git a/letsencrypt/client/plugins/nginx/tests/__init__.py b/letsencrypt_nginx/tests/__init__.py similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/__init__.py rename to letsencrypt_nginx/tests/__init__.py diff --git a/letsencrypt/client/plugins/nginx/tests/configurator_test.py b/letsencrypt_nginx/tests/configurator_test.py similarity index 94% rename from letsencrypt/client/plugins/nginx/tests/configurator_test.py rename to letsencrypt_nginx/tests/configurator_test.py index 7be3177e3..12101949f 100644 --- a/letsencrypt/client/plugins/nginx/tests/configurator_test.py +++ b/letsencrypt_nginx/tests/configurator_test.py @@ -1,4 +1,4 @@ -"""Test for letsencrypt.client.plugins.nginx.configurator.""" +"""Test for letsencrypt_nginx.configurator.""" import shutil import unittest @@ -11,7 +11,7 @@ from letsencrypt.client import achallenges from letsencrypt.client import errors from letsencrypt.client import le_util -from letsencrypt.client.plugins.nginx.tests import util +from letsencrypt_nginx.tests import util class NginxConfiguratorTest(util.NginxTest): @@ -158,10 +158,8 @@ class NginxConfiguratorTest(util.NginxTest): ('/etc/nginx/cert.pem', '/etc/nginx/key.pem', nginx_conf), ]), self.config.get_all_certs_keys()) - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "dvsni.NginxDvsni.perform") - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "NginxConfigurator.restart") + @mock.patch("letsencrypt_nginx.configurator.dvsni.NginxDvsni.perform") + @mock.patch("letsencrypt_nginx.configurator.NginxConfigurator.restart") def test_perform(self, mock_restart, mock_dvsni_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -195,8 +193,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual(responses, dvsni_ret_val) self.assertEqual(mock_restart.call_count, 1) - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "subprocess.Popen") + @mock.patch("letsencrypt_nginx.configurator.subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", @@ -251,16 +248,14 @@ class NginxConfiguratorTest(util.NginxTest): self.assertRaises( errors.LetsEncryptConfiguratorError, self.config.get_version) - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "subprocess.Popen") + @mock.patch("letsencrypt_nginx.configurator.subprocess.Popen") def test_nginx_restart(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 0 self.assertTrue(self.config.restart()) - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "subprocess.Popen") + @mock.patch("letsencrypt_nginx.configurator.subprocess.Popen") def test_config_test(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt_nginx/tests/dvsni_test.py similarity index 88% rename from letsencrypt/client/plugins/nginx/tests/dvsni_test.py rename to letsencrypt_nginx/tests/dvsni_test.py index 24e32bafe..94708e282 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt_nginx/tests/dvsni_test.py @@ -1,4 +1,4 @@ -"""Test for letsencrypt.client.plugins.nginx.dvsni.""" +"""Test for letsencrypt_nginx.dvsni.""" import pkg_resources import unittest import shutil @@ -11,7 +11,7 @@ from acme import messages2 from letsencrypt.client import achallenges from letsencrypt.client import le_util -from letsencrypt.client.plugins.nginx.tests import util +from letsencrypt_nginx.tests import util class DvsniPerformTest(util.NginxTest): @@ -31,7 +31,7 @@ class DvsniPerformTest(util.NginxTest): auth_key = le_util.Key(rsa256_file, rsa256_pem) - from letsencrypt.client.plugins.nginx import dvsni + from letsencrypt_nginx import dvsni self.sni = dvsni.NginxDvsni(config) self.achalls = [ @@ -67,8 +67,7 @@ class DvsniPerformTest(util.NginxTest): self.assertEqual(1, len(self.sni.achalls)) self.assertEqual([0], self.sni.indices) - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "NginxConfigurator.save") + @mock.patch("letsencrypt_nginx.configurator.NginxConfigurator.save") def test_perform0(self, mock_save): self.sni.add_chall(self.achalls[0]) responses = self.sni.perform() @@ -82,8 +81,7 @@ class DvsniPerformTest(util.NginxTest): # http://www.voidspace.org.uk/python/mock/helpers.html#mock.mock_open pass - @mock.patch("letsencrypt.client.plugins.nginx.configurator." - "NginxConfigurator.save") + @mock.patch("letsencrypt_nginx.configurator.NginxConfigurator.save") def test_perform1(self, mock_save): self.sni.add_chall(self.achalls[1]) responses = self.sni.perform() diff --git a/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py b/letsencrypt_nginx/tests/nginxparser_test.py similarity index 94% rename from letsencrypt/client/plugins/nginx/tests/nginxparser_test.py rename to letsencrypt_nginx/tests/nginxparser_test.py index 2e19e71d1..e60dc9468 100644 --- a/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py +++ b/letsencrypt_nginx/tests/nginxparser_test.py @@ -1,10 +1,10 @@ -"""Test for letsencrypt.client.plugins.nginx.nginxparser.""" +"""Test for letsencrypt_nginx.nginxparser.""" import operator import unittest -from letsencrypt.client.plugins.nginx.nginxparser import (RawNginxParser, - load, dumps, dump) -from letsencrypt.client.plugins.nginx.tests import util +from letsencrypt_nginx.nginxparser import ( + RawNginxParser, load, dumps, dump) +from letsencrypt_nginx.tests import util FIRST = operator.itemgetter(0) diff --git a/letsencrypt/client/plugins/nginx/tests/obj_test.py b/letsencrypt_nginx/tests/obj_test.py similarity index 87% rename from letsencrypt/client/plugins/nginx/tests/obj_test.py rename to letsencrypt_nginx/tests/obj_test.py index d5591c763..bad007973 100644 --- a/letsencrypt/client/plugins/nginx/tests/obj_test.py +++ b/letsencrypt_nginx/tests/obj_test.py @@ -1,11 +1,11 @@ -"""Test the helper objects in letsencrypt.client.plugins.nginx.obj.""" +"""Test the helper objects in letsencrypt_nginx.obj.""" import unittest class AddrTest(unittest.TestCase): """Test the Addr class.""" def setUp(self): - from letsencrypt.client.plugins.nginx.obj import Addr + from letsencrypt_nginx.obj import Addr self.addr1 = Addr.fromstring("192.168.1.1") self.addr2 = Addr.fromstring("192.168.1.1:* ssl") self.addr3 = Addr.fromstring("192.168.1.1:80") @@ -56,14 +56,14 @@ class AddrTest(unittest.TestCase): self.assertEqual(str(self.addr6), "80") def test_eq(self): - from letsencrypt.client.plugins.nginx.obj import Addr + from letsencrypt_nginx.obj import Addr new_addr1 = Addr.fromstring("192.168.1.1 spdy") self.assertEqual(self.addr1, new_addr1) self.assertNotEqual(self.addr1, self.addr2) self.assertFalse(self.addr1 == 3333) def test_set_inclusion(self): - from letsencrypt.client.plugins.nginx.obj import Addr + from letsencrypt_nginx.obj import Addr set_a = set([self.addr1, self.addr2]) addr1b = Addr.fromstring("192.168.1.1") addr2b = Addr.fromstring("192.168.1.1:* ssl") @@ -75,16 +75,16 @@ class AddrTest(unittest.TestCase): class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from letsencrypt.client.plugins.nginx.obj import VirtualHost - from letsencrypt.client.plugins.nginx.obj import Addr + from letsencrypt_nginx.obj import VirtualHost + from letsencrypt_nginx.obj import Addr self.vhost1 = VirtualHost( "filep", set([Addr.fromstring("localhost")]), False, False, set(['localhost']), []) def test_eq(self): - from letsencrypt.client.plugins.nginx.obj import Addr - from letsencrypt.client.plugins.nginx.obj import VirtualHost + from letsencrypt_nginx.obj import Addr + from letsencrypt_nginx.obj import VirtualHost vhost1b = VirtualHost( "filep", set([Addr.fromstring("localhost blah")]), False, False, diff --git a/letsencrypt/client/plugins/nginx/tests/parser_test.py b/letsencrypt_nginx/tests/parser_test.py similarity index 97% rename from letsencrypt/client/plugins/nginx/tests/parser_test.py rename to letsencrypt_nginx/tests/parser_test.py index 21e96aa26..47b44e671 100644 --- a/letsencrypt/client/plugins/nginx/tests/parser_test.py +++ b/letsencrypt_nginx/tests/parser_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.plugins.nginx.parser.""" +"""Tests for letsencrypt_nginx.parser.""" import glob import os import re @@ -6,10 +6,11 @@ import shutil import unittest from letsencrypt.client.errors import LetsEncryptMisconfigurationError -from letsencrypt.client.plugins.nginx import nginxparser -from letsencrypt.client.plugins.nginx import obj -from letsencrypt.client.plugins.nginx import parser -from letsencrypt.client.plugins.nginx.tests import util + +from letsencrypt_nginx import nginxparser +from letsencrypt_nginx import obj +from letsencrypt_nginx import parser +from letsencrypt_nginx.tests import util class NginxParserTest(util.NginxTest): diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/foo.conf b/letsencrypt_nginx/tests/testdata/foo.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/foo.conf rename to letsencrypt_nginx/tests/testdata/foo.conf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/mime.types b/letsencrypt_nginx/tests/testdata/mime.types similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/mime.types rename to letsencrypt_nginx/tests/testdata/mime.types diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/nginx.conf b/letsencrypt_nginx/tests/testdata/nginx.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/nginx.conf rename to letsencrypt_nginx/tests/testdata/nginx.conf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/nginx.new.conf b/letsencrypt_nginx/tests/testdata/nginx.new.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/nginx.new.conf rename to letsencrypt_nginx/tests/testdata/nginx.new.conf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/server.conf b/letsencrypt_nginx/tests/testdata/server.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/server.conf rename to letsencrypt_nginx/tests/testdata/server.conf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/default b/letsencrypt_nginx/tests/testdata/sites-enabled/default similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/default rename to letsencrypt_nginx/tests/testdata/sites-enabled/default diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/example.com b/letsencrypt_nginx/tests/testdata/sites-enabled/example.com similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/example.com rename to letsencrypt_nginx/tests/testdata/sites-enabled/example.com diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params diff --git a/letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf b/letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf similarity index 100% rename from letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf rename to letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf diff --git a/letsencrypt/client/plugins/nginx/tests/util.py b/letsencrypt_nginx/tests/util.py similarity index 90% rename from letsencrypt/client/plugins/nginx/tests/util.py rename to letsencrypt_nginx/tests/util.py index 47577daa1..57d11c188 100644 --- a/letsencrypt/client/plugins/nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -7,8 +7,8 @@ import unittest import mock -from letsencrypt.client.plugins.nginx import constants -from letsencrypt.client.plugins.nginx import configurator +from letsencrypt_nginx import constants +from letsencrypt_nginx import configurator class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods @@ -33,7 +33,7 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods def get_data_filename(filename): """Gets the filename of a test data file.""" return pkg_resources.resource_filename( - "letsencrypt.client.plugins.nginx.tests", "testdata/%s" % filename) + "letsencrypt_nginx.tests", "testdata/%s" % filename) def dir_setup(test_dir="debian_nginx/two_vhost_80"): @@ -43,7 +43,7 @@ def dir_setup(test_dir="debian_nginx/two_vhost_80"): work_dir = tempfile.mkdtemp("work") test_configs = pkg_resources.resource_filename( - "letsencrypt.client.plugins.nginx.tests", test_dir) + "letsencrypt_nginx.tests", test_dir) shutil.copytree( test_configs, os.path.join(temp_dir, test_dir), symlinks=True) diff --git a/setup.cfg b/setup.cfg index a9fa682f2..cd572471c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,5 +6,5 @@ dev = develop easy_install letsencrypt[dev,docs,testing] [nosetests] nocapture=1 -cover-package=letsencrypt,acme +cover-package=letsencrypt,acme,letsencrypt_apache,letsencrypt_nginx cover-erase=1 diff --git a/setup.py b/setup.py index 9256f48ff..1c65ff880 100644 --- a/setup.py +++ b/setup.py @@ -115,12 +115,12 @@ setup( 'jws = letsencrypt.acme.jose.jws:CLI.run', ], 'letsencrypt.plugins': [ - 'apache = letsencrypt.client.plugins.apache.configurator' - ':ApacheConfigurator', - 'nginx = letsencrypt.client.plugins.nginx.configurator' - ':NginxConfigurator', 'standalone = letsencrypt.client.plugins.standalone.authenticator' ':StandaloneAuthenticator', + + # to be moved to separate pypi packages + 'apache = letsencrypt_apache.configurator:ApacheConfigurator', + 'nginx = letsencrypt_nginx.configurator:NginxConfigurator', ], }, diff --git a/tox.ini b/tox.ini index 5c869d289..add0a15e7 100644 --- a/tox.ini +++ b/tox.ini @@ -27,4 +27,4 @@ commands = basepython = python2.7 commands = pip install -e .[dev] - pylint --rcfile=.pylintrc letsencrypt acme + pylint --rcfile=.pylintrc letsencrypt acme letsencrypt_apache letsencrypt_nginx From 41e86df252109c59023e65179e7c73a727a18d9a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 12:25:29 +0000 Subject: [PATCH 64/76] Move letsencrypt.client to letsencrypt --- acme/challenges_test.py | 2 +- acme/jose/json_util_test.py | 4 +- acme/jose/jws_test.py | 2 +- acme/jose/testdata/README | 4 +- acme/messages_test.py | 4 +- docs/api/account.rst | 5 + docs/api/achallenges.rst | 5 + docs/api/augeas_configurator.rst | 5 + docs/api/auth_handler.rst | 5 + docs/api/{client/index.rst => client.rst} | 0 docs/api/client/account.rst | 5 - docs/api/client/achallenges.rst | 5 - docs/api/client/augeas_configurator.rst | 5 - docs/api/client/auth_handler.rst | 5 - docs/api/client/client.rst | 5 - docs/api/client/configuration.rst | 5 - docs/api/client/constants.rst | 5 - docs/api/client/continuity_auth.rst | 5 - docs/api/client/crypto_util.rst | 5 - docs/api/client/display.rst | 29 ---- docs/api/client/errors.rst | 5 - docs/api/client/interfaces.rst | 5 - docs/api/client/le_util.rst | 5 - docs/api/client/log.rst | 5 - docs/api/client/network.rst | 5 - docs/api/client/network2.rst | 5 - docs/api/client/recovery_token.rst | 5 - docs/api/client/reverter.rst | 5 - docs/api/client/revoker.rst | 5 - docs/api/configuration.rst | 5 + docs/api/constants.rst | 5 + docs/api/continuity_auth.rst | 5 + docs/api/crypto_util.rst | 5 + docs/api/display.rst | 29 ++++ docs/api/errors.rst | 5 + docs/api/index.rst | 5 + docs/api/interfaces.rst | 5 + docs/api/le_util.rst | 5 + docs/api/log.rst | 5 + docs/api/network.rst | 5 + docs/api/network2.rst | 5 + docs/api/plugins/common.rst | 6 +- docs/api/plugins/disco.rst | 6 +- docs/api/plugins/standalone.rst | 12 +- docs/api/recovery_token.rst | 5 + docs/api/reverter.rst | 5 + docs/api/revoker.rst | 5 + docs/contributing.rst | 16 +-- docs/plugins.rst | 4 +- .../plugins/letsencrypt_example_plugins.py | 6 +- examples/restified.py | 4 +- letsencrypt/{client => }/.gitignore | 0 letsencrypt/__init__.py | 3 +- letsencrypt/{client => }/account.py | 24 ++-- letsencrypt/{client => }/achallenges.py | 4 +- .../{client => }/augeas_configurator.py | 8 +- letsencrypt/{client => }/auth_handler.py | 30 ++--- letsencrypt/{client => }/cli.py | 22 ++-- letsencrypt/{client => }/client.py | 64 ++++----- letsencrypt/client/__init__.py | 1 - letsencrypt/client/display/__init__.py | 1 - .../client/plugins/standalone/__init__.py | 1 - letsencrypt/{client => }/configuration.py | 10 +- letsencrypt/{client => }/constants.py | 2 +- letsencrypt/{client => }/continuity_auth.py | 12 +- letsencrypt/{client => }/crypto_util.py | 8 +- letsencrypt/display/__init__.py | 1 + .../{client => }/display/enhancements.py | 10 +- letsencrypt/{client => }/display/ops.py | 12 +- .../{client => }/display/revocation.py | 8 +- letsencrypt/{client => }/display/util.py | 2 +- letsencrypt/{client => }/errors.py | 0 letsencrypt/{client => }/interfaces.py | 14 +- letsencrypt/{client => }/le_util.py | 2 +- letsencrypt/{client => }/log.py | 2 +- letsencrypt/{client => }/network.py | 2 +- letsencrypt/{client => }/network2.py | 12 +- letsencrypt/{client => }/plugins/__init__.py | 0 letsencrypt/{client => }/plugins/common.py | 2 +- .../{client => }/plugins/common_test.py | 12 +- letsencrypt/{client => }/plugins/disco.py | 6 +- .../{client => }/plugins/disco_test.py | 27 ++-- letsencrypt/plugins/standalone/__init__.py | 1 + .../plugins/standalone/authenticator.py | 10 +- .../plugins/standalone/tests/__init__.py | 0 .../standalone/tests/authenticator_test.py | 124 ++++++++---------- letsencrypt/{client => }/recovery_token.py | 8 +- letsencrypt/{client => }/reverter.py | 28 ++-- letsencrypt/{client => }/revoker.py | 30 ++--- letsencrypt/{client => }/tests/__init__.py | 0 .../{client => }/tests/account_test.py | 52 ++++---- .../{client => }/tests/achallenges_test.py | 10 +- letsencrypt/{client => }/tests/acme_util.py | 0 .../{client => }/tests/auth_handler_test.py | 44 +++---- letsencrypt/{client => }/tests/cli_test.py | 8 +- letsencrypt/{client => }/tests/client_test.py | 38 +++--- .../{client => }/tests/configuration_test.py | 8 +- .../tests/continuity_auth_test.py | 8 +- .../{client => }/tests/crypto_util_test.py | 38 +++--- .../{client => }/tests/display/__init__.py | 0 .../tests/display/enhancements_test.py | 16 +-- .../{client => }/tests/display/ops_test.py | 70 +++++----- .../tests/display/revocation_test.py | 34 ++--- .../{client => }/tests/display/util_test.py | 34 ++--- .../{client => }/tests/le_util_test.py | 12 +- letsencrypt/{client => }/tests/log_test.py | 4 +- .../{client => }/tests/network2_test.py | 28 ++-- .../{client => }/tests/recovery_token_test.py | 6 +- .../{client => }/tests/reverter_test.py | 36 +++-- .../{client => }/tests/revoker_test.py | 76 +++++------ .../{client => }/tests/testdata/cert-san.pem | 0 .../{client => }/tests/testdata/cert.b64jose | 0 .../{client => }/tests/testdata/cert.pem | 0 .../{client => }/tests/testdata/csr-san.der | Bin .../{client => }/tests/testdata/csr-san.pem | 0 .../{client => }/tests/testdata/csr.der | Bin .../{client => }/tests/testdata/csr.pem | 0 .../tests/testdata/rsa512_key.pem | 0 letsencrypt_apache/configurator.py | 18 +-- letsencrypt_apache/dvsni.py | 10 +- letsencrypt_apache/parser.py | 2 +- letsencrypt_apache/tests/configurator_test.py | 8 +- letsencrypt_apache/tests/dvsni_test.py | 6 +- letsencrypt_apache/tests/parser_test.py | 4 +- letsencrypt_nginx/configurator.py | 22 ++-- letsencrypt_nginx/dvsni.py | 2 +- letsencrypt_nginx/parser.py | 2 +- letsencrypt_nginx/tests/configurator_test.py | 6 +- letsencrypt_nginx/tests/dvsni_test.py | 4 +- letsencrypt_nginx/tests/parser_test.py | 2 +- letsencrypt_nginx/tests/util.py | 2 +- setup.py | 4 +- 132 files changed, 697 insertions(+), 728 deletions(-) create mode 100644 docs/api/account.rst create mode 100644 docs/api/achallenges.rst create mode 100644 docs/api/augeas_configurator.rst create mode 100644 docs/api/auth_handler.rst rename docs/api/{client/index.rst => client.rst} (100%) delete mode 100644 docs/api/client/account.rst delete mode 100644 docs/api/client/achallenges.rst delete mode 100644 docs/api/client/augeas_configurator.rst delete mode 100644 docs/api/client/auth_handler.rst delete mode 100644 docs/api/client/client.rst delete mode 100644 docs/api/client/configuration.rst delete mode 100644 docs/api/client/constants.rst delete mode 100644 docs/api/client/continuity_auth.rst delete mode 100644 docs/api/client/crypto_util.rst delete mode 100644 docs/api/client/display.rst delete mode 100644 docs/api/client/errors.rst delete mode 100644 docs/api/client/interfaces.rst delete mode 100644 docs/api/client/le_util.rst delete mode 100644 docs/api/client/log.rst delete mode 100644 docs/api/client/network.rst delete mode 100644 docs/api/client/network2.rst delete mode 100644 docs/api/client/recovery_token.rst delete mode 100644 docs/api/client/reverter.rst delete mode 100644 docs/api/client/revoker.rst create mode 100644 docs/api/configuration.rst create mode 100644 docs/api/constants.rst create mode 100644 docs/api/continuity_auth.rst create mode 100644 docs/api/crypto_util.rst create mode 100644 docs/api/display.rst create mode 100644 docs/api/errors.rst create mode 100644 docs/api/index.rst create mode 100644 docs/api/interfaces.rst create mode 100644 docs/api/le_util.rst create mode 100644 docs/api/log.rst create mode 100644 docs/api/network.rst create mode 100644 docs/api/network2.rst create mode 100644 docs/api/recovery_token.rst create mode 100644 docs/api/reverter.rst create mode 100644 docs/api/revoker.rst rename letsencrypt/{client => }/.gitignore (100%) rename letsencrypt/{client => }/account.py (90%) rename letsencrypt/{client => }/achallenges.py (96%) rename letsencrypt/{client => }/augeas_configurator.py (96%) rename letsencrypt/{client => }/auth_handler.py (94%) rename letsencrypt/{client => }/cli.py (96%) rename letsencrypt/{client => }/client.py (86%) delete mode 100644 letsencrypt/client/__init__.py delete mode 100644 letsencrypt/client/display/__init__.py delete mode 100644 letsencrypt/client/plugins/standalone/__init__.py rename letsencrypt/{client => }/configuration.py (89%) rename letsencrypt/{client => }/constants.py (96%) rename letsencrypt/{client => }/continuity_auth.py (82%) rename letsencrypt/{client => }/crypto_util.py (96%) create mode 100644 letsencrypt/display/__init__.py rename letsencrypt/{client => }/display/enhancements.py (83%) rename letsencrypt/{client => }/display/ops.py (94%) rename letsencrypt/{client => }/display/revocation.py (89%) rename letsencrypt/{client => }/display/util.py (99%) rename letsencrypt/{client => }/errors.py (100%) rename letsencrypt/{client => }/interfaces.py (95%) rename letsencrypt/{client => }/le_util.py (98%) rename letsencrypt/{client => }/log.py (97%) rename letsencrypt/{client => }/network.py (99%) rename letsencrypt/{client => }/network2.py (98%) rename letsencrypt/{client => }/plugins/__init__.py (100%) rename letsencrypt/{client => }/plugins/common.py (98%) rename letsencrypt/{client => }/plugins/common_test.py (81%) rename letsencrypt/{client => }/plugins/disco.py (98%) rename letsencrypt/{client => }/plugins/disco_test.py (91%) create mode 100644 letsencrypt/plugins/standalone/__init__.py rename letsencrypt/{client => }/plugins/standalone/authenticator.py (98%) rename letsencrypt/{client => }/plugins/standalone/tests/__init__.py (100%) rename letsencrypt/{client => }/plugins/standalone/tests/authenticator_test.py (84%) rename letsencrypt/{client => }/recovery_token.py (89%) rename letsencrypt/{client => }/reverter.py (95%) rename letsencrypt/{client => }/revoker.py (95%) rename letsencrypt/{client => }/tests/__init__.py (100%) rename letsencrypt/{client => }/tests/account_test.py (80%) rename letsencrypt/{client => }/tests/achallenges_test.py (83%) rename letsencrypt/{client => }/tests/acme_util.py (100%) rename letsencrypt/{client => }/tests/auth_handler_test.py (92%) rename letsencrypt/{client => }/tests/cli_test.py (77%) rename letsencrypt/{client => }/tests/client_test.py (66%) rename letsencrypt/{client => }/tests/configuration_test.py (86%) rename letsencrypt/{client => }/tests/continuity_auth_test.py (91%) rename letsencrypt/{client => }/tests/crypto_util_test.py (75%) rename letsencrypt/{client => }/tests/display/__init__.py (100%) rename letsencrypt/{client => }/tests/display/enhancements_test.py (71%) rename letsencrypt/{client => }/tests/display/ops_test.py (81%) rename letsencrypt/{client => }/tests/display/revocation_test.py (68%) rename letsencrypt/{client => }/tests/display/util_test.py (91%) rename letsencrypt/{client => }/tests/le_util_test.py (90%) rename letsencrypt/{client => }/tests/log_test.py (94%) rename letsencrypt/{client => }/tests/network2_test.py (96%) rename letsencrypt/{client => }/tests/recovery_token_test.py (93%) rename letsencrypt/{client => }/tests/reverter_test.py (93%) rename letsencrypt/{client => }/tests/revoker_test.py (84%) rename letsencrypt/{client => }/tests/testdata/cert-san.pem (100%) rename letsencrypt/{client => }/tests/testdata/cert.b64jose (100%) rename letsencrypt/{client => }/tests/testdata/cert.pem (100%) rename letsencrypt/{client => }/tests/testdata/csr-san.der (100%) rename letsencrypt/{client => }/tests/testdata/csr-san.pem (100%) rename letsencrypt/{client => }/tests/testdata/csr.der (100%) rename letsencrypt/{client => }/tests/testdata/csr.pem (100%) rename letsencrypt/{client => }/tests/testdata/rsa512_key.pem (100%) diff --git a/acme/challenges_test.py b/acme/challenges_test.py index c2c8bdfea..ab5ddd9aa 100644 --- a/acme/challenges_test.py +++ b/acme/challenges_test.py @@ -12,7 +12,7 @@ from acme import other CERT = jose.ComparableX509(M2Crypto.X509.load_cert( pkg_resources.resource_filename( - 'letsencrypt.client.tests', os.path.join('testdata', 'cert.pem')))) + 'letsencrypt.tests', os.path.join('testdata', 'cert.pem')))) KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( pkg_resources.resource_string( 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) diff --git a/acme/jose/json_util_test.py b/acme/jose/json_util_test.py index 42113279e..eed72d57f 100644 --- a/acme/jose/json_util_test.py +++ b/acme/jose/json_util_test.py @@ -12,9 +12,9 @@ from acme.jose import util CERT = M2Crypto.X509.load_cert(pkg_resources.resource_filename( - 'letsencrypt.client.tests', os.path.join('testdata', 'cert.pem'))) + 'letsencrypt.tests', os.path.join('testdata', 'cert.pem'))) CSR = M2Crypto.X509.load_request(pkg_resources.resource_filename( - 'letsencrypt.client.tests', os.path.join('testdata', 'csr.pem'))) + 'letsencrypt.tests', os.path.join('testdata', 'csr.pem'))) class FieldTest(unittest.TestCase): diff --git a/acme/jose/jws_test.py b/acme/jose/jws_test.py index 736391e4c..d15d113b7 100644 --- a/acme/jose/jws_test.py +++ b/acme/jose/jws_test.py @@ -17,7 +17,7 @@ from acme.jose import util CERT = util.ComparableX509(M2Crypto.X509.load_cert( pkg_resources.resource_filename( - 'letsencrypt.client.tests', 'testdata/cert.pem'))) + 'letsencrypt.tests', 'testdata/cert.pem'))) RSA512_KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( __name__, os.path.join('testdata', 'rsa512_key.pem'))) diff --git a/acme/jose/testdata/README b/acme/jose/testdata/README index 4b37ae921..72ec557e0 100644 --- a/acme/jose/testdata/README +++ b/acme/jose/testdata/README @@ -4,7 +4,7 @@ The following command has been used to generate test keys: and for the CSR: - python -c from 'letsencrypt.client.crypto_util import make_csr; + python -c from 'letsencrypt.crypto_util import make_csr; import pkg_resources; open("csr2.pem", - "w").write(make_csr(pkg_resources.resource_string("letsencrypt.client.tests", + "w").write(make_csr(pkg_resources.resource_string("letsencrypt.tests", "testdata/rsa512_key.pem"), ["example2.com"])[0])' diff --git a/acme/messages_test.py b/acme/messages_test.py index 1acb0e838..662de3f1c 100644 --- a/acme/messages_test.py +++ b/acme/messages_test.py @@ -17,10 +17,10 @@ KEY = jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( 'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))) CERT = jose.ComparableX509(M2Crypto.X509.load_cert( pkg_resources.resource_filename( - 'letsencrypt.client.tests', os.path.join('testdata', 'cert.pem')))) + 'letsencrypt.tests', os.path.join('testdata', 'cert.pem')))) CSR = jose.ComparableX509(M2Crypto.X509.load_request( pkg_resources.resource_filename( - 'letsencrypt.client.tests', os.path.join('testdata', 'csr.pem')))) + 'letsencrypt.tests', os.path.join('testdata', 'csr.pem')))) CSR2 = jose.ComparableX509(M2Crypto.X509.load_request( pkg_resources.resource_filename( 'acme.jose', os.path.join('testdata', 'csr2.pem')))) diff --git a/docs/api/account.rst b/docs/api/account.rst new file mode 100644 index 000000000..16c2061a8 --- /dev/null +++ b/docs/api/account.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.account` +-------------------------- + +.. automodule:: letsencrypt.account + :members: diff --git a/docs/api/achallenges.rst b/docs/api/achallenges.rst new file mode 100644 index 000000000..09cec1702 --- /dev/null +++ b/docs/api/achallenges.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.achallenges` +------------------------------ + +.. automodule:: letsencrypt.achallenges + :members: diff --git a/docs/api/augeas_configurator.rst b/docs/api/augeas_configurator.rst new file mode 100644 index 000000000..402eee797 --- /dev/null +++ b/docs/api/augeas_configurator.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.augeas_configurator` +-------------------------------------- + +.. automodule:: letsencrypt.augeas_configurator + :members: diff --git a/docs/api/auth_handler.rst b/docs/api/auth_handler.rst new file mode 100644 index 000000000..3b168faf8 --- /dev/null +++ b/docs/api/auth_handler.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.auth_handler` +------------------------------- + +.. automodule:: letsencrypt.auth_handler + :members: diff --git a/docs/api/client/index.rst b/docs/api/client.rst similarity index 100% rename from docs/api/client/index.rst rename to docs/api/client.rst diff --git a/docs/api/client/account.rst b/docs/api/client/account.rst deleted file mode 100644 index 6fad87556..000000000 --- a/docs/api/client/account.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.account` ---------------------------------- - -.. automodule:: letsencrypt.client.account - :members: diff --git a/docs/api/client/achallenges.rst b/docs/api/client/achallenges.rst deleted file mode 100644 index 46a13ee8b..000000000 --- a/docs/api/client/achallenges.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.achallenges` -------------------------------------- - -.. automodule:: letsencrypt.client.achallenges - :members: diff --git a/docs/api/client/augeas_configurator.rst b/docs/api/client/augeas_configurator.rst deleted file mode 100644 index 6b8ca4d83..000000000 --- a/docs/api/client/augeas_configurator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.augeas_configurator` ---------------------------------------------- - -.. automodule:: letsencrypt.client.augeas_configurator - :members: diff --git a/docs/api/client/auth_handler.rst b/docs/api/client/auth_handler.rst deleted file mode 100644 index b52006993..000000000 --- a/docs/api/client/auth_handler.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.auth_handler` --------------------------------------- - -.. automodule:: letsencrypt.client.auth_handler - :members: diff --git a/docs/api/client/client.rst b/docs/api/client/client.rst deleted file mode 100644 index ac68c2764..000000000 --- a/docs/api/client/client.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.client` --------------------------------- - -.. automodule:: letsencrypt.client.client - :members: diff --git a/docs/api/client/configuration.rst b/docs/api/client/configuration.rst deleted file mode 100644 index 0bec61480..000000000 --- a/docs/api/client/configuration.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.configuration` ---------------------------------------- - -.. automodule:: letsencrypt.client.configuration - :members: diff --git a/docs/api/client/constants.rst b/docs/api/client/constants.rst deleted file mode 100644 index c901e13c2..000000000 --- a/docs/api/client/constants.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.constants` ------------------------------------ - -.. automodule:: letsencrypt.client.constants - :members: diff --git a/docs/api/client/continuity_auth.rst b/docs/api/client/continuity_auth.rst deleted file mode 100644 index 29f6a3ffb..000000000 --- a/docs/api/client/continuity_auth.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.continuity_auth` ------------------------------------------ - -.. automodule:: letsencrypt.client.continuity_auth - :members: diff --git a/docs/api/client/crypto_util.rst b/docs/api/client/crypto_util.rst deleted file mode 100644 index 1b47cd151..000000000 --- a/docs/api/client/crypto_util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.crypto_util` -------------------------------------- - -.. automodule:: letsencrypt.client.crypto_util - :members: diff --git a/docs/api/client/display.rst b/docs/api/client/display.rst deleted file mode 100644 index 59f97d18e..000000000 --- a/docs/api/client/display.rst +++ /dev/null @@ -1,29 +0,0 @@ -:mod:`letsencrypt.client.display` ---------------------------------- - -.. automodule:: letsencrypt.client.display - :members: - -:mod:`letsencrypt.client.display.util` -====================================== - -.. automodule:: letsencrypt.client.display.util - :members: - -:mod:`letsencrypt.client.display.ops` -===================================== - -.. automodule:: letsencrypt.client.display.ops - :members: - -:mod:`letsencrypt.client.display.enhancements` -============================================== - -.. automodule:: letsencrypt.client.display.enhancements - :members: - -:mod:`letsencrypt.client.display.revocation` -============================================ - -.. automodule:: letsencrypt.client.display.revocation - :members: diff --git a/docs/api/client/errors.rst b/docs/api/client/errors.rst deleted file mode 100644 index 40f0ae6c7..000000000 --- a/docs/api/client/errors.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.errors` --------------------------------- - -.. automodule:: letsencrypt.client.errors - :members: diff --git a/docs/api/client/interfaces.rst b/docs/api/client/interfaces.rst deleted file mode 100644 index e14daed7f..000000000 --- a/docs/api/client/interfaces.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.interfaces` ------------------------------------- - -.. automodule:: letsencrypt.client.interfaces - :members: diff --git a/docs/api/client/le_util.rst b/docs/api/client/le_util.rst deleted file mode 100644 index 537e90546..000000000 --- a/docs/api/client/le_util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.le_util` ---------------------------------- - -.. automodule:: letsencrypt.client.le_util - :members: diff --git a/docs/api/client/log.rst b/docs/api/client/log.rst deleted file mode 100644 index 2ccad738b..000000000 --- a/docs/api/client/log.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.log` ------------------------------ - -.. automodule:: letsencrypt.client.log - :members: diff --git a/docs/api/client/network.rst b/docs/api/client/network.rst deleted file mode 100644 index 7b4ec633a..000000000 --- a/docs/api/client/network.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.network` ---------------------------------- - -.. automodule:: letsencrypt.client.network - :members: diff --git a/docs/api/client/network2.rst b/docs/api/client/network2.rst deleted file mode 100644 index b05017551..000000000 --- a/docs/api/client/network2.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.network2` ----------------------------------- - -.. automodule:: letsencrypt.client.network2 - :members: diff --git a/docs/api/client/recovery_token.rst b/docs/api/client/recovery_token.rst deleted file mode 100644 index cc37e036d..000000000 --- a/docs/api/client/recovery_token.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.recovery_token` --------------------------------------------------- - -.. automodule:: letsencrypt.client.recovery_token - :members: diff --git a/docs/api/client/reverter.rst b/docs/api/client/reverter.rst deleted file mode 100644 index ad2e41437..000000000 --- a/docs/api/client/reverter.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.reverter` ----------------------------------- - -.. automodule:: letsencrypt.client.reverter - :members: diff --git a/docs/api/client/revoker.rst b/docs/api/client/revoker.rst deleted file mode 100644 index e0a7db533..000000000 --- a/docs/api/client/revoker.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`letsencrypt.client.revoker` ---------------------------------- - -.. automodule:: letsencrypt.client.revoker - :members: diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst new file mode 100644 index 000000000..e92392b99 --- /dev/null +++ b/docs/api/configuration.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.configuration` +-------------------------------- + +.. automodule:: letsencrypt.configuration + :members: diff --git a/docs/api/constants.rst b/docs/api/constants.rst new file mode 100644 index 000000000..3a2815b5e --- /dev/null +++ b/docs/api/constants.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.constants` +----------------------------------- + +.. automodule:: letsencrypt.constants + :members: diff --git a/docs/api/continuity_auth.rst b/docs/api/continuity_auth.rst new file mode 100644 index 000000000..82869e6f4 --- /dev/null +++ b/docs/api/continuity_auth.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.continuity_auth` +---------------------------------- + +.. automodule:: letsencrypt.continuity_auth + :members: diff --git a/docs/api/crypto_util.rst b/docs/api/crypto_util.rst new file mode 100644 index 000000000..5d4c77538 --- /dev/null +++ b/docs/api/crypto_util.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.crypto_util` +------------------------------ + +.. automodule:: letsencrypt.crypto_util + :members: diff --git a/docs/api/display.rst b/docs/api/display.rst new file mode 100644 index 000000000..b79ef25d7 --- /dev/null +++ b/docs/api/display.rst @@ -0,0 +1,29 @@ +:mod:`letsencrypt.display` +-------------------------- + +.. automodule:: letsencrypt.display + :members: + +:mod:`letsencrypt.display.util` +=============================== + +.. automodule:: letsencrypt.display.util + :members: + +:mod:`letsencrypt.display.ops` +============================== + +.. automodule:: letsencrypt.display.ops + :members: + +:mod:`letsencrypt.display.enhancements` +======================================= + +.. automodule:: letsencrypt.display.enhancements + :members: + +:mod:`letsencrypt.display.revocation` +===================================== + +.. automodule:: letsencrypt.display.revocation + :members: diff --git a/docs/api/errors.rst b/docs/api/errors.rst new file mode 100644 index 000000000..1ad13235c --- /dev/null +++ b/docs/api/errors.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.errors` +------------------------- + +.. automodule:: letsencrypt.errors + :members: diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 000000000..a2475eeae --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt` +------------------ + +.. automodule:: letsencrypt + :members: diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst new file mode 100644 index 000000000..00b0a1e50 --- /dev/null +++ b/docs/api/interfaces.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.interfaces` +----------------------------- + +.. automodule:: letsencrypt.interfaces + :members: diff --git a/docs/api/le_util.rst b/docs/api/le_util.rst new file mode 100644 index 000000000..8c6b717cf --- /dev/null +++ b/docs/api/le_util.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.le_util` +-------------------------- + +.. automodule:: letsencrypt.le_util + :members: diff --git a/docs/api/log.rst b/docs/api/log.rst new file mode 100644 index 000000000..f41c6c4b1 --- /dev/null +++ b/docs/api/log.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.log` +---------------------- + +.. automodule:: letsencrypt.log + :members: diff --git a/docs/api/network.rst b/docs/api/network.rst new file mode 100644 index 000000000..0359379dd --- /dev/null +++ b/docs/api/network.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.network` +-------------------------- + +.. automodule:: letsencrypt.network + :members: diff --git a/docs/api/network2.rst b/docs/api/network2.rst new file mode 100644 index 000000000..a73308e1b --- /dev/null +++ b/docs/api/network2.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.network2` +--------------------------- + +.. automodule:: letsencrypt.network2 + :members: diff --git a/docs/api/plugins/common.rst b/docs/api/plugins/common.rst index 9ee3e6b3e..ca55ba8fb 100644 --- a/docs/api/plugins/common.rst +++ b/docs/api/plugins/common.rst @@ -1,5 +1,5 @@ -:mod:`letsencrypt.client.plugins.common` ----------------------------------------- +:mod:`letsencrypt.plugins.common` +--------------------------------- -.. automodule:: letsencrypt.client.plugins.common +.. automodule:: letsencrypt.plugins.common :members: diff --git a/docs/api/plugins/disco.rst b/docs/api/plugins/disco.rst index 2b877b654..7bf2b76b4 100644 --- a/docs/api/plugins/disco.rst +++ b/docs/api/plugins/disco.rst @@ -1,5 +1,5 @@ -:mod:`letsencrypt.client.plugins.disco` ---------------------------------------- +:mod:`letsencrypt.plugins.disco` +-------------------------------- -.. automodule:: letsencrypt.client.plugins.disco +.. automodule:: letsencrypt.plugins.disco :members: diff --git a/docs/api/plugins/standalone.rst b/docs/api/plugins/standalone.rst index 44cf4b8ca..99e5ea2f8 100644 --- a/docs/api/plugins/standalone.rst +++ b/docs/api/plugins/standalone.rst @@ -1,11 +1,11 @@ -:mod:`letsencrypt.client.plugins.standalone` --------------------------------------------- +:mod:`letsencrypt.plugins.standalone` +------------------------------------- -.. automodule:: letsencrypt.client.plugins.standalone +.. automodule:: letsencrypt.plugins.standalone :members: -:mod:`letsencrypt.client.plugins.standalone.authenticator` -========================================================== +:mod:`letsencrypt.plugins.standalone.authenticator` +=================================================== -.. automodule:: letsencrypt.client.plugins.standalone.authenticator +.. automodule:: letsencrypt.plugins.standalone.authenticator :members: diff --git a/docs/api/recovery_token.rst b/docs/api/recovery_token.rst new file mode 100644 index 000000000..774aa4b3c --- /dev/null +++ b/docs/api/recovery_token.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.recovery_token` +-------------------------------------------------- + +.. automodule:: letsencrypt.recovery_token + :members: diff --git a/docs/api/reverter.rst b/docs/api/reverter.rst new file mode 100644 index 000000000..4c220124f --- /dev/null +++ b/docs/api/reverter.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.reverter` +--------------------------- + +.. automodule:: letsencrypt.reverter + :members: diff --git a/docs/api/revoker.rst b/docs/api/revoker.rst new file mode 100644 index 000000000..a482a138e --- /dev/null +++ b/docs/api/revoker.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.revoker` +-------------------------- + +.. automodule:: letsencrypt.revoker + :members: diff --git a/docs/contributing.rst b/docs/contributing.rst index 4a61f7388..da28686a2 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -69,10 +69,8 @@ Code components and layout acme contains all protocol specific code -letsencrypt/client +letsencrypt all client code -letsencrypt/scripts - just the starting point of the code, main.py Plugin-architecture @@ -84,14 +82,14 @@ The interfaces available for plugins to implement are defined in `interfaces.py`_. The most common kind of plugin is a "Configurator", which is likely to -implement the `~letsencrypt.client.interfaces.IAuthenticator` and -`~letsencrypt.client.interfaces.IInstaller` interfaces (though some +implement the `~letsencrypt.interfaces.IAuthenticator` and +`~letsencrypt.interfaces.IInstaller` interfaces (though some Configurators may implement just one of those). -There are also `~letsencrypt.client.interfaces.IDisplay` plugins, +There are also `~letsencrypt.interfaces.IDisplay` plugins, which implement bindings to alternative UI libraries. -.. _interfaces.py: https://github.com/letsencrypt/lets-encrypt-preview/blob/master/letsencrypt/client/interfaces.py +.. _interfaces.py: https://github.com/letsencrypt/lets-encrypt-preview/blob/master/letsencrypt/interfaces.py Authenticators @@ -138,7 +136,7 @@ Installer Development --------------------- There are a few existing classes that may be beneficial while -developing a new `~letsencrypt.client.interfaces.IInstaller`. +developing a new `~letsencrypt.interfaces.IInstaller`. Installers aimed to reconfigure UNIX servers may use Augeas for configuration parsing and can inherit from `~.AugeasConfigurator` class to handle much of the interface. Installers that are unable to use @@ -150,7 +148,7 @@ Display ~~~~~~~ We currently offer a pythondialog and "text" mode for displays. Display -plugins implement the `~letsencrypt.client.interfaces.IDisplay` +plugins implement the `~letsencrypt.interfaces.IDisplay` interface. diff --git a/docs/plugins.rst b/docs/plugins.rst index 0451bfe3f..c4df1180a 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -5,8 +5,8 @@ Plugins Let's Encrypt client supports dynamic discovery of plugins through the `setuptools entry points`_. This way you can, for example, create a custom implementation of -`~letsencrypt.client.interfaces.IAuthenticator` or the -'~letsencrypt.client.interfaces.IInstaller' without having to +`~letsencrypt.interfaces.IAuthenticator` or the +'~letsencrypt.interfaces.IInstaller' without having to merge it with the core upstream source code. An example is provided in ``examples/plugins/`` directory. diff --git a/examples/plugins/letsencrypt_example_plugins.py b/examples/plugins/letsencrypt_example_plugins.py index 7c6d1311c..a364ae905 100644 --- a/examples/plugins/letsencrypt_example_plugins.py +++ b/examples/plugins/letsencrypt_example_plugins.py @@ -1,12 +1,12 @@ """Example Let's Encrypt plugins. -For full examples, see `letsencrypt.client.plugins`. +For full examples, see `letsencrypt.plugins`. """ import zope.interface -from letsencrypt.client import interfaces -from letsencrypt.client.plugins import common +from letsencrypt import interfaces +from letsencrypt.plugins import common class Authenticator(common.Plugin): diff --git a/examples/restified.py b/examples/restified.py index 651ecccd1..b7b0257e2 100644 --- a/examples/restified.py +++ b/examples/restified.py @@ -7,7 +7,7 @@ import M2Crypto from letsencrypt.acme import messages2 from letsencrypt.acme import jose -from letsencrypt.client import network2 +from letsencrypt import network2 logger = logging.getLogger() @@ -35,7 +35,7 @@ logging.debug(authzr) authzr, authzr_response = net.poll(authzr) csr = M2Crypto.X509.load_request_string(pkg_resources.resource_string( - 'letsencrypt.client.tests', os.path.join('testdata', 'csr.pem'))) + 'letsencrypt.tests', os.path.join('testdata', 'csr.pem'))) try: net.request_issuance(csr, (authzr,)) except messages2.Error as error: diff --git a/letsencrypt/client/.gitignore b/letsencrypt/.gitignore similarity index 100% rename from letsencrypt/client/.gitignore rename to letsencrypt/.gitignore diff --git a/letsencrypt/__init__.py b/letsencrypt/__init__.py index b36747b5f..560191bf1 100644 --- a/letsencrypt/__init__.py +++ b/letsencrypt/__init__.py @@ -1,3 +1,4 @@ -"""Let's Encrypt.""" +"""Let's Encrypt client.""" + # version number like 1.2.3a0, must have at least 2 parts, like 1.2 __version__ = "0.1" diff --git a/letsencrypt/client/account.py b/letsencrypt/account.py similarity index 90% rename from letsencrypt/client/account.py rename to letsencrypt/account.py index 053f73a87..3f8e3d012 100644 --- a/letsencrypt/client/account.py +++ b/letsencrypt/account.py @@ -8,21 +8,21 @@ import zope.component from acme import messages2 -from letsencrypt.client import crypto_util -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util +from letsencrypt import crypto_util +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class Account(object): """ACME protocol registration. :ivar config: Client configuration object - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` :ivar key: Account/Authorized Key - :type key: :class:`~letsencrypt.client.le_util.Key` + :type key: :class:`~letsencrypt.le_util.Key` :ivar str email: Client's email address :ivar str phone: Client's phone number @@ -156,7 +156,7 @@ class Account(object): """Return all current accounts. :param config: Configuration - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ try: @@ -178,10 +178,10 @@ class Account(object): """Generate an account from prompted user input. :param config: Configuration - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` :returns: Account or None - :rtype: :class:`letsencrypt.client.account.Account` + :rtype: :class:`letsencrypt.account.Account` """ while True: @@ -201,11 +201,11 @@ class Account(object): """Generate a new account from an email address. :param config: Configuration - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` :param str email: Email address - :raises letsencrypt.client.errors.LetsEncryptClientError: If invalid + :raises letsencrypt.errors.LetsEncryptClientError: If invalid email address is given. """ diff --git a/letsencrypt/client/achallenges.py b/letsencrypt/achallenges.py similarity index 96% rename from letsencrypt/client/achallenges.py rename to letsencrypt/achallenges.py index a88b0dddb..77e362f22 100644 --- a/letsencrypt/client/achallenges.py +++ b/letsencrypt/achallenges.py @@ -6,7 +6,7 @@ and :class:`.ChallengeBody` (denoted by ``challb``):: from acme import challenges from acme import messages2 - from letsencrypt.client import achallenges + from letsencrypt import achallenges chall = challenges.DNS(token='foo') challb = messages2.ChallengeBody(chall=chall) @@ -20,7 +20,7 @@ Note, that all annotated challenges act as a proxy objects:: from acme import challenges from acme.jose import util as jose_util -from letsencrypt.client import crypto_util +from letsencrypt import crypto_util # pylint: disable=too-few-public-methods diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/augeas_configurator.py similarity index 96% rename from letsencrypt/client/augeas_configurator.py rename to letsencrypt/augeas_configurator.py index 713d49291..c59d755c2 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/augeas_configurator.py @@ -3,22 +3,22 @@ import logging import augeas -from letsencrypt.client import reverter -from letsencrypt.client.plugins import common +from letsencrypt import reverter +from letsencrypt.plugins import common class AugeasConfigurator(common.Plugin): """Base Augeas Configurator class. :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` :ivar aug: Augeas object :type aug: :class:`augeas.Augeas` :ivar str save_notes: Human-readable configuration change notes :ivar reverter: saves and reverts checkpoints - :type reverter: :class:`letsencrypt.client.reverter.Reverter` + :type reverter: :class:`letsencrypt.reverter.Reverter` """ diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/auth_handler.py similarity index 94% rename from letsencrypt/client/auth_handler.py rename to letsencrypt/auth_handler.py index 52e2df0c6..e83cdd717 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -6,9 +6,9 @@ import time from acme import challenges from acme import messages2 -from letsencrypt.client import achallenges -from letsencrypt.client import constants -from letsencrypt.client import errors +from letsencrypt import achallenges +from letsencrypt import constants +from letsencrypt import errors class AuthHandler(object): @@ -16,25 +16,25 @@ class AuthHandler(object): :ivar dv_auth: Authenticator capable of solving :class:`~acme.challenges.DVChallenge` types - :type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` + :type dv_auth: :class:`letsencrypt.interfaces.IAuthenticator` :ivar cont_auth: Authenticator capable of solving :class:`~acme.challenges.ContinuityChallenge` types - :type cont_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` + :type cont_auth: :class:`letsencrypt.interfaces.IAuthenticator` :ivar network: Network object for sending and receiving authorization messages - :type network: :class:`letsencrypt.client.network2.Network` + :type network: :class:`letsencrypt.network2.Network` :ivar account: Client's Account - :type account: :class:`letsencrypt.client.account.Account` + :type account: :class:`letsencrypt.account.Account` :ivar dict authzr: ACME Authorization Resource dict where keys are domains and values are :class:`acme.messages2.AuthorizationResource` :ivar list dv_c: DV challenges in the form of - :class:`letsencrypt.client.achallenges.AnnotatedChallenge` + :class:`letsencrypt.achallenges.AnnotatedChallenge` :ivar list cont_c: Continuity challenges in the - form of :class:`letsencrypt.client.achallenges.AnnotatedChallenge` + form of :class:`letsencrypt.achallenges.AnnotatedChallenge` """ def __init__(self, dv_auth, cont_auth, network, account): @@ -222,7 +222,7 @@ class AuthHandler(object): :type authzr: :class:`acme.messages2.AuthorizationResource` :param achall: Annotated challenge for which to get status - :type achall: :class:`letsencrypt.client.achallenges.AnnotatedChallenge` + :type achall: :class:`letsencrypt.achallenges.AnnotatedChallenge` """ for authzr_challb in authzr.body.challenges: @@ -289,9 +289,9 @@ class AuthHandler(object): :param list path: List of indices from `challenges`. :returns: dv_chall, list of DVChallenge type - :class:`letsencrypt.client.achallenges.Indexed` + :class:`letsencrypt.achallenges.Indexed` cont_chall, list of ContinuityChallenge type - :class:`letsencrypt.client.achallenges.Indexed` + :class:`letsencrypt.achallenges.Indexed` :rtype: tuple :raises errors.LetsEncryptClientError: If Challenge type is not @@ -322,12 +322,12 @@ def challb_to_achall(challb, key, domain): :type challb: :class:`acme.messages2.ChallengeBody` :param key: Key - :type key: :class:`letsencrypt.client.le_util.Key` + :type key: :class:`letsencrypt.le_util.Key` :param str domain: Domain of the challb :returns: Appropriate AnnotatedChallenge - :rtype: :class:`letsencrypt.client.achallenges.AnnotatedChallenge` + :rtype: :class:`letsencrypt.achallenges.AnnotatedChallenge` """ chall = challb.chall @@ -383,7 +383,7 @@ def gen_challenge_path(challbs, preferences, combinations): :returns: tuple of indices from ``challenges``. :rtype: tuple - :raises letsencrypt.client.errors.AuthorizationError: If a + :raises letsencrypt.errors.AuthorizationError: If a path cannot be created that satisfies the CA given the preferences and combinations. diff --git a/letsencrypt/client/cli.py b/letsencrypt/cli.py similarity index 96% rename from letsencrypt/client/cli.py rename to letsencrypt/cli.py index a83c0f767..be933d891 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/cli.py @@ -12,19 +12,19 @@ import zope.interface.verify import letsencrypt -from letsencrypt.client import account -from letsencrypt.client import configuration -from letsencrypt.client import constants -from letsencrypt.client import client -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util -from letsencrypt.client import log +from letsencrypt import account +from letsencrypt import configuration +from letsencrypt import constants +from letsencrypt import client +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util +from letsencrypt import log -from letsencrypt.client.display import util as display_util -from letsencrypt.client.display import ops as display_ops +from letsencrypt.display import util as display_util +from letsencrypt.display import ops as display_ops -from letsencrypt.client.plugins import disco as plugins_disco +from letsencrypt.plugins import disco as plugins_disco def _account_init(args, config): diff --git a/letsencrypt/client/client.py b/letsencrypt/client.py similarity index 86% rename from letsencrypt/client/client.py rename to letsencrypt/client.py index b658df90e..b28a7fa74 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client.py @@ -9,40 +9,40 @@ import zope.component from acme import jose from acme.jose import jwk -from letsencrypt.client import account -from letsencrypt.client import auth_handler -from letsencrypt.client import continuity_auth -from letsencrypt.client import crypto_util -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util -from letsencrypt.client import network2 -from letsencrypt.client import reverter -from letsencrypt.client import revoker +from letsencrypt import account +from letsencrypt import auth_handler +from letsencrypt import continuity_auth +from letsencrypt import crypto_util +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util +from letsencrypt import network2 +from letsencrypt import reverter +from letsencrypt import revoker -from letsencrypt.client.display import ops as display_ops -from letsencrypt.client.display import enhancements +from letsencrypt.display import ops as display_ops +from letsencrypt.display import enhancements class Client(object): """ACME protocol client. :ivar network: Network object for sending and receiving messages - :type network: :class:`letsencrypt.client.network2.Network` + :type network: :class:`letsencrypt.network2.Network` :ivar account: Account object used for registration - :type account: :class:`letsencrypt.client.account.Account` + :type account: :class:`letsencrypt.account.Account` :ivar auth_handler: Object that supports the IAuthenticator interface. auth_handler contains both a dv_authenticator and a continuity_authenticator - :type auth_handler: :class:`letsencrypt.client.auth_handler.AuthHandler` + :type auth_handler: :class:`letsencrypt.auth_handler.AuthHandler` :ivar installer: Object supporting the IInstaller interface. - :type installer: :class:`letsencrypt.client.interfaces.IInstaller` + :type installer: :class:`letsencrypt.interfaces.IInstaller` :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` """ @@ -50,10 +50,10 @@ class Client(object): """Initialize a client. :param dv_auth: IAuthenticator that can solve the - :const:`letsencrypt.client.constants.DV_CHALLENGES`. - The :meth:`~letsencrypt.client.interfaces.IAuthenticator.prepare` + :const:`letsencrypt.constants.DV_CHALLENGES`. + The :meth:`~letsencrypt.interfaces.IAuthenticator.prepare` must have already been run. - :type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` + :type dv_auth: :class:`letsencrypt.interfaces.IAuthenticator` """ self.account = account_ @@ -107,7 +107,7 @@ class Client(object): :type csr: :class:`CSR` :returns: cert_key, cert_path, chain_path - :rtype: `tuple` of (:class:`letsencrypt.client.le_util.Key`, str, str) + :rtype: `tuple` of (:class:`letsencrypt.le_util.Key`, str, str) """ if self.auth_handler is None: @@ -196,7 +196,7 @@ class Client(object): :param list domains: list of domains to install the certificate :param privkey: private key for certificate - :type privkey: :class:`letsencrypt.client.le_util.Key` + :type privkey: :class:`letsencrypt.le_util.Key` :param str cert_path: certificate file path :param str chain_path: chain file path @@ -232,7 +232,7 @@ class Client(object): :param redirect: If traffic should be forwarded from HTTP to HTTPS. :type redirect: bool or None - :raises letsencrypt.client.errors.LetsEncryptClientError: if + :raises letsencrypt.errors.LetsEncryptClientError: if no installer is specified in the client. """ @@ -251,7 +251,7 @@ class Client(object): """Redirect all traffic from HTTP to HTTPS :param vhost: list of ssl_vhosts - :type vhost: :class:`letsencrypt.client.interfaces.IInstaller` + :type vhost: :class:`letsencrypt.interfaces.IInstaller` """ for dom in domains: @@ -274,12 +274,12 @@ def validate_key_csr(privkey, csr=None): If csr is left as None, only the key will be validated. :param privkey: Key associated with CSR - :type privkey: :class:`letsencrypt.client.le_util.Key` + :type privkey: :class:`letsencrypt.le_util.Key` :param csr: CSR - :type csr: :class:`letsencrypt.client.le_util.CSR` + :type csr: :class:`letsencrypt.le_util.CSR` - :raises letsencrypt.client.errors.LetsEncryptClientError: when + :raises letsencrypt.errors.LetsEncryptClientError: when validation fails """ @@ -317,10 +317,10 @@ def determine_account(config): Will create an account if necessary. :param config: Configuration object - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` :returns: Account - :rtype: :class:`letsencrypt.client.account.Account` + :rtype: :class:`letsencrypt.account.Account` """ accounts = account.Account.get_accounts(config) @@ -339,7 +339,7 @@ def rollback(default_installer, checkpoints, config, plugins): :param int checkpoints: Number of checkpoints to revert. :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ # Misconfigurations are only a slight problems... allow the user to rollback @@ -359,7 +359,7 @@ def revoke(default_installer, config, plugins, no_confirm, cert, authkey): """Revoke certificates. :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ installer = display_ops.pick_installer( @@ -382,7 +382,7 @@ def view_config_changes(config): .. note:: This assumes that the installation is using a Reverter object. :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ rev = reverter.Reverter(config) diff --git a/letsencrypt/client/__init__.py b/letsencrypt/client/__init__.py deleted file mode 100644 index d6c102964..000000000 --- a/letsencrypt/client/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt client.""" diff --git a/letsencrypt/client/display/__init__.py b/letsencrypt/client/display/__init__.py deleted file mode 100644 index b652c58a9..000000000 --- a/letsencrypt/client/display/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt client.display""" diff --git a/letsencrypt/client/plugins/standalone/__init__.py b/letsencrypt/client/plugins/standalone/__init__.py deleted file mode 100644 index 41de6eaf7..000000000 --- a/letsencrypt/client/plugins/standalone/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Let's Encrypt client.plugins.standalone.""" diff --git a/letsencrypt/client/configuration.py b/letsencrypt/configuration.py similarity index 89% rename from letsencrypt/client/configuration.py rename to letsencrypt/configuration.py index 14c7b23cd..a83fb9978 100644 --- a/letsencrypt/client/configuration.py +++ b/letsencrypt/configuration.py @@ -2,18 +2,18 @@ import os import zope.interface -from letsencrypt.client import constants -from letsencrypt.client import interfaces +from letsencrypt import constants +from letsencrypt import interfaces class NamespaceConfig(object): """Configuration wrapper around :class:`argparse.Namespace`. For more documentation, including available attributes, please see - :class:`letsencrypt.client.interfaces.IConfig`. However, note that + :class:`letsencrypt.interfaces.IConfig`. However, note that the following attributes are dynamically resolved using - :attr:`~letsencrypt.client.interfaces.IConfig.work_dir` and relative - paths defined in :py:mod:`letsencrypt.client.constants`: + :attr:`~letsencrypt.interfaces.IConfig.work_dir` and relative + paths defined in :py:mod:`letsencrypt.constants`: - ``temp_checkpoint_dir`` - ``in_progress_dir`` diff --git a/letsencrypt/client/constants.py b/letsencrypt/constants.py similarity index 96% rename from letsencrypt/client/constants.py rename to letsencrypt/constants.py index 513b76829..4eba69f20 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/constants.py @@ -31,7 +31,7 @@ EXCLUSIVE_CHALLENGES = frozenset([frozenset([ ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"] -"""List of possible :class:`letsencrypt.client.interfaces.IInstaller` +"""List of possible :class:`letsencrypt.interfaces.IInstaller` enhancements. List of expected options parameters: diff --git a/letsencrypt/client/continuity_auth.py b/letsencrypt/continuity_auth.py similarity index 82% rename from letsencrypt/client/continuity_auth.py rename to letsencrypt/continuity_auth.py index 4d75b43cf..e4bbbe13d 100644 --- a/letsencrypt/client/continuity_auth.py +++ b/letsencrypt/continuity_auth.py @@ -3,10 +3,10 @@ import zope.interface from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import recovery_token +from letsencrypt import achallenges +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import recovery_token class ContinuityAuthenticator(object): @@ -14,7 +14,7 @@ class ContinuityAuthenticator(object): :const:`~acme.challenges.ContinuityChallenge` class challenges. :ivar rec_token: Performs "recoveryToken" challenges - :type rec_token: :class:`letsencrypt.client.recovery_token.RecoveryToken` + :type rec_token: :class:`letsencrypt.recovery_token.RecoveryToken` """ zope.interface.implements(interfaces.IAuthenticator) @@ -24,7 +24,7 @@ class ContinuityAuthenticator(object): """Initialize Client Authenticator. :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ self.rec_token = recovery_token.RecoveryToken( diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/crypto_util.py similarity index 96% rename from letsencrypt/client/crypto_util.py rename to letsencrypt/crypto_util.py index c2b761d59..f89f281cb 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/crypto_util.py @@ -14,7 +14,7 @@ import Crypto.Signature.PKCS1_v1_5 import M2Crypto -from letsencrypt.client import le_util +from letsencrypt import le_util # High level functions @@ -31,7 +31,7 @@ def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): :param str keyname: Filename of key :returns: Key - :rtype: :class:`letsencrypt.client.le_util.Key` + :rtype: :class:`letsencrypt.le_util.Key` :raises ValueError: If unable to generate the key given key_size. @@ -58,14 +58,14 @@ def init_save_csr(privkey, names, cert_dir, csrname="csr-letsencrypt.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR - :type privkey: :class:`letsencrypt.client.le_util.Key` + :type privkey: :class:`letsencrypt.le_util.Key` :param set names: `str` names to include in the CSR :param str cert_dir: Certificate save directory. :returns: CSR - :rtype: :class:`letsencrypt.client.le_util.CSR` + :rtype: :class:`letsencrypt.le_util.CSR` """ csr_pem, csr_der = make_csr(privkey.pem, names) diff --git a/letsencrypt/display/__init__.py b/letsencrypt/display/__init__.py new file mode 100644 index 000000000..01e3ca11f --- /dev/null +++ b/letsencrypt/display/__init__.py @@ -0,0 +1 @@ +"""Let's Encrypt display utilities.""" diff --git a/letsencrypt/client/display/enhancements.py b/letsencrypt/display/enhancements.py similarity index 83% rename from letsencrypt/client/display/enhancements.py rename to letsencrypt/display/enhancements.py index d7ea3a66a..48f168441 100644 --- a/letsencrypt/client/display/enhancements.py +++ b/letsencrypt/display/enhancements.py @@ -3,9 +3,9 @@ import logging import zope.component -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client.display import util as display_util +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt.display import util as display_util # Define a helper function to avoid verbose code @@ -16,12 +16,12 @@ def ask(enhancement): """Display the enhancement to the user. :param str enhancement: One of the - :class:`letsencrypt.client.CONFIG.ENHANCEMENTS` enhancements + :class:`letsencrypt.CONFIG.ENHANCEMENTS` enhancements :returns: True if feature is desired, False otherwise :rtype: bool - :raises letsencrypt.client.errors.LetsEncryptClientError: If + :raises letsencrypt.errors.LetsEncryptClientError: If the enhancement provided is not supported. """ diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/display/ops.py similarity index 94% rename from letsencrypt/client/display/ops.py rename to letsencrypt/display/ops.py index dc6992c8c..e05a19b14 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/display/ops.py @@ -4,8 +4,8 @@ import os import zope.component -from letsencrypt.client import interfaces -from letsencrypt.client.display import util as display_util +from letsencrypt import interfaces +from letsencrypt.display import util as display_util # Define a helper function to avoid verbose code @@ -46,9 +46,9 @@ def choose_plugin(prepared, question): def pick_plugin(config, default, plugins, question, ifaces): """Pick plugin. - :param letsencrypt.client.interfaces.IConfig: Configuration + :param letsencrypt.interfaces.IConfig: Configuration :param str default: Plugin name supplied by user or ``None``. - :param letsencrypt.client.plugins.disco.PluginsRegistry plugins: + :param letsencrypt.plugins.disco.PluginsRegistry plugins: All plugins registered as entry points. :param str question: Question to be presented to the user in case multiple candidates are found. @@ -114,7 +114,7 @@ def choose_account(accounts): """Choose an account. :param list accounts: Containing at least one - :class:`~letsencrypt.client.account.Account` + :class:`~letsencrypt.account.Account` """ # Note this will get more complicated once we start recording authorizations @@ -136,7 +136,7 @@ def choose_names(installer): """Display screen to select domains to validate. :param installer: An installer object - :type installer: :class:`letsencrypt.client.interfaces.IInstaller` + :type installer: :class:`letsencrypt.interfaces.IInstaller` :returns: List of selected names :rtype: `list` of `str` diff --git a/letsencrypt/client/display/revocation.py b/letsencrypt/display/revocation.py similarity index 89% rename from letsencrypt/client/display/revocation.py rename to letsencrypt/display/revocation.py index 65dbd9f63..02a253676 100644 --- a/letsencrypt/client/display/revocation.py +++ b/letsencrypt/display/revocation.py @@ -3,8 +3,8 @@ import os import zope.component -from letsencrypt.client import interfaces -from letsencrypt.client.display import util as display_util +from letsencrypt import interfaces +from letsencrypt.display import util as display_util # Define a helper function to avoid verbose code util = zope.component.getUtility # pylint: disable=invalid-name @@ -13,7 +13,7 @@ util = zope.component.getUtility # pylint: disable=invalid-name def display_certs(certs): """Display the certificates in a menu for revocation. - :param list certs: each is a :class:`letsencrypt.client.revoker.Cert` + :param list certs: each is a :class:`letsencrypt.revoker.Cert` :returns: tuple of the form (code, selection) where code is a display exit code @@ -69,7 +69,7 @@ def success_revocation(cert): """Display a success message. :param cert: cert that was revoked - :type cert: :class:`letsencrypt.client.revoker.Cert` + :type cert: :class:`letsencrypt.revoker.Cert` """ util(interfaces.IDisplay).notification( diff --git a/letsencrypt/client/display/util.py b/letsencrypt/display/util.py similarity index 99% rename from letsencrypt/client/display/util.py rename to letsencrypt/display/util.py index d34c6b46b..bd509dd28 100644 --- a/letsencrypt/client/display/util.py +++ b/letsencrypt/display/util.py @@ -5,7 +5,7 @@ import textwrap import dialog import zope.interface -from letsencrypt.client import interfaces +from letsencrypt import interfaces WIDTH = 72 diff --git a/letsencrypt/client/errors.py b/letsencrypt/errors.py similarity index 100% rename from letsencrypt/client/errors.py rename to letsencrypt/errors.py diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/interfaces.py similarity index 95% rename from letsencrypt/client/interfaces.py rename to letsencrypt/interfaces.py index e28264759..e6c0588c5 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/interfaces.py @@ -68,10 +68,10 @@ class IPlugin(zope.interface.Interface): Finish up any additional initialization. - :raises letsencrypt.client.errors.LetsEncryptMisconfigurationError: + :raises letsencrypt.errors.LetsEncryptMisconfigurationError: when full initialization cannot be completed. Plugin will be displayed on a list of available plugins. - :raises letsencrypt.client.errors.LetsEncryptNoInstallationError: + :raises letsencrypt.errors.LetsEncryptNoInstallationError: when the necessary programs/files cannot be located. Plugin will NOT be displayed on a list of available plugins. @@ -111,7 +111,7 @@ class IAuthenticator(IPlugin): """Perform the given challenge. :param list achalls: Non-empty (guaranteed) list of - :class:`~letsencrypt.client.achallenges.AnnotatedChallenge` + :class:`~letsencrypt.achallenges.AnnotatedChallenge` instances, such that it contains types found within :func:`get_chall_pref` only. @@ -134,7 +134,7 @@ class IAuthenticator(IPlugin): """Revert changes and shutdown after challenges complete. :param list achalls: Non-empty (guaranteed) list of - :class:`~letsencrypt.client.achallenges.AnnotatedChallenge` + :class:`~letsencrypt.achallenges.AnnotatedChallenge` instances, a subset of those previously passed to :func:`perform`. """ @@ -203,10 +203,10 @@ class IInstaller(IPlugin): :param str domain: domain for which to provide enhancement :param str enhancement: An enhancement as defined in - :const:`~letsencrypt.client.constants.ENHANCEMENTS` + :const:`~letsencrypt.constants.ENHANCEMENTS` :param options: Flexible options parameter for enhancement. Check documentation of - :const:`~letsencrypt.client.constants.ENHANCEMENTS` + :const:`~letsencrypt.constants.ENHANCEMENTS` for expected options for each enhancement. """ @@ -215,7 +215,7 @@ class IInstaller(IPlugin): """Returns a list of supported enhancements. :returns: supported enhancements which should be a subset of - :const:`~letsencrypt.client.constants.ENHANCEMENTS` + :const:`~letsencrypt.constants.ENHANCEMENTS` :rtype: :class:`list` of :class:`str` """ diff --git a/letsencrypt/client/le_util.py b/letsencrypt/le_util.py similarity index 98% rename from letsencrypt/client/le_util.py rename to letsencrypt/le_util.py index 1615fc29d..27d795749 100644 --- a/letsencrypt/client/le_util.py +++ b/letsencrypt/le_util.py @@ -4,7 +4,7 @@ import errno import os import stat -from letsencrypt.client import errors +from letsencrypt import errors Key = collections.namedtuple("Key", "file pem") diff --git a/letsencrypt/client/log.py b/letsencrypt/log.py similarity index 97% rename from letsencrypt/client/log.py rename to letsencrypt/log.py index 57c642ce2..a31bad8e3 100644 --- a/letsencrypt/client/log.py +++ b/letsencrypt/log.py @@ -3,7 +3,7 @@ import logging import dialog -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods diff --git a/letsencrypt/client/network.py b/letsencrypt/network.py similarity index 99% rename from letsencrypt/client/network.py rename to letsencrypt/network.py index 81a3fccdc..f59794971 100644 --- a/letsencrypt/client/network.py +++ b/letsencrypt/network.py @@ -8,7 +8,7 @@ import requests from acme import jose from acme import messages -from letsencrypt.client import errors +from letsencrypt import errors # https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning diff --git a/letsencrypt/client/network2.py b/letsencrypt/network2.py similarity index 98% rename from letsencrypt/client/network2.py rename to letsencrypt/network2.py index 2b85ebd3c..e66afb3a6 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/network2.py @@ -12,7 +12,7 @@ import werkzeug from acme import jose from acme import messages2 -from letsencrypt.client import errors +from letsencrypt import errors # https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning @@ -109,7 +109,7 @@ class Network(object): def _get(self, uri, content_type=JSON_CONTENT_TYPE, **kwargs): """Send GET request. - :raises letsencrypt.client.errors.NetworkError: + :raises letsencrypt.errors.NetworkError: :returns: HTTP Response :rtype: `requests.Response` @@ -172,7 +172,7 @@ class Network(object): :returns: Registration Resource. :rtype: `.RegistrationResource` - :raises letsencrypt.client.errors.UnexpectedUpdate: + :raises letsencrypt.errors.UnexpectedUpdate: """ new_reg = messages2.Registration(contact=contact) @@ -190,10 +190,10 @@ class Network(object): """Register with server. :param account: Account - :type account: :class:`letsencrypt.client.account.Account` + :type account: :class:`letsencrypt.account.Account` :returns: Updated account - :rtype: :class:`letsencrypt.client.account.Account` + :rtype: :class:`letsencrypt.account.Account` """ details = ( @@ -535,7 +535,7 @@ class Network(object): :param when: When should the revocation take place? Takes the same values as `.messages2.Revocation.revoke`. - :raises letsencrypt.client.errors.NetworkError: If revocation is + :raises letsencrypt.errors.NetworkError: If revocation is unsuccessful. """ diff --git a/letsencrypt/client/plugins/__init__.py b/letsencrypt/plugins/__init__.py similarity index 100% rename from letsencrypt/client/plugins/__init__.py rename to letsencrypt/plugins/__init__.py diff --git a/letsencrypt/client/plugins/common.py b/letsencrypt/plugins/common.py similarity index 98% rename from letsencrypt/client/plugins/common.py rename to letsencrypt/plugins/common.py index 08e9cf658..32bee2b49 100644 --- a/letsencrypt/client/plugins/common.py +++ b/letsencrypt/plugins/common.py @@ -3,7 +3,7 @@ import zope.interface from acme.jose import util as jose_util -from letsencrypt.client import interfaces +from letsencrypt import interfaces def option_namespace(name): diff --git a/letsencrypt/client/plugins/common_test.py b/letsencrypt/plugins/common_test.py similarity index 81% rename from letsencrypt/client/plugins/common_test.py rename to letsencrypt/plugins/common_test.py index bdb5f7f3c..348ed9379 100644 --- a/letsencrypt/client/plugins/common_test.py +++ b/letsencrypt/plugins/common_test.py @@ -1,26 +1,26 @@ -"""Tests for letsencrypt.client.plugins.common.""" +"""Tests for letsencrypt.plugins.common.""" import unittest import mock class NamespaceFunctionsTest(unittest.TestCase): - """Tests for letsencrypt.client.plugins.common.*_namespace functions.""" + """Tests for letsencrypt.plugins.common.*_namespace functions.""" def test_option_namespace(self): - from letsencrypt.client.plugins.common import option_namespace + from letsencrypt.plugins.common import option_namespace self.assertEqual("foo-", option_namespace("foo")) def test_dest_namespace(self): - from letsencrypt.client.plugins.common import dest_namespace + from letsencrypt.plugins.common import dest_namespace self.assertEqual("foo_", dest_namespace("foo")) class PluginTest(unittest.TestCase): - """Test for letsencrypt.client.plugins.common.Plugin.""" + """Test for letsencrypt.plugins.common.Plugin.""" def setUp(self): - from letsencrypt.client.plugins.common import Plugin + from letsencrypt.plugins.common import Plugin class MockPlugin(Plugin): # pylint: disable=missing-docstring @classmethod diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/plugins/disco.py similarity index 98% rename from letsencrypt/client/plugins/disco.py rename to letsencrypt/plugins/disco.py index 50e0bce50..072c3ef42 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/plugins/disco.py @@ -5,9 +5,9 @@ import pkg_resources import zope.interface -from letsencrypt.client import constants -from letsencrypt.client import errors -from letsencrypt.client import interfaces +from letsencrypt import constants +from letsencrypt import errors +from letsencrypt import interfaces class PluginEntryPoint(object): diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/plugins/disco_test.py similarity index 91% rename from letsencrypt/client/plugins/disco_test.py rename to letsencrypt/plugins/disco_test.py index f5ea9e6ee..c90db7b7e 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/plugins/disco_test.py @@ -1,23 +1,23 @@ -"""Tests for letsencrypt.client.plugins.disco.""" +"""Tests for letsencrypt.plugins.disco.""" import pkg_resources import unittest import mock import zope.interface -from letsencrypt.client import errors -from letsencrypt.client import interfaces +from letsencrypt import errors +from letsencrypt import interfaces -from letsencrypt.client.plugins.standalone import authenticator +from letsencrypt.plugins.standalone import authenticator EP_SA = pkg_resources.EntryPoint( - "sa", "letsencrypt.client.plugins.standalone.authenticator", + "sa", "letsencrypt.plugins.standalone.authenticator", attrs=("StandaloneAuthenticator",), dist=mock.MagicMock(key="letsencrypt")) class PluginEntryPointTest(unittest.TestCase): - """Tests for letsencrypt.client.plugins.disco.PluginEntryPoint.""" + """Tests for letsencrypt.plugins.disco.PluginEntryPoint.""" def setUp(self): self.ep1 = pkg_resources.EntryPoint( @@ -31,11 +31,11 @@ class PluginEntryPointTest(unittest.TestCase): self.ep3 = pkg_resources.EntryPoint( "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - from letsencrypt.client.plugins.disco import PluginEntryPoint + from letsencrypt.plugins.disco import PluginEntryPoint self.plugin_ep = PluginEntryPoint(EP_SA) def test_entry_point_to_plugin_name(self): - from letsencrypt.client.plugins.disco import PluginEntryPoint + from letsencrypt.plugins.disco import PluginEntryPoint names = { self.ep1: "p1:ep1", @@ -97,7 +97,7 @@ class PluginEntryPointTest(unittest.TestCase): self.plugin_ep._initialized = plugin = mock.MagicMock() exceptions = zope.interface.exceptions - with mock.patch("letsencrypt.client.plugins." + with mock.patch("letsencrypt.plugins." "disco.zope.interface") as mock_zope: mock_zope.exceptions = exceptions def verify_object(iface, obj): # pylint: disable=missing-docstring @@ -148,18 +148,17 @@ class PluginEntryPointTest(unittest.TestCase): class PluginsRegistryTest(unittest.TestCase): - """Tests for letsencrypt.client.plugins.disco.PluginsRegistry.""" + """Tests for letsencrypt.plugins.disco.PluginsRegistry.""" def setUp(self): - from letsencrypt.client.plugins.disco import PluginsRegistry + from letsencrypt.plugins.disco import PluginsRegistry self.plugin_ep = mock.MagicMock(name="mock") self.plugins = {"mock": self.plugin_ep} self.reg = PluginsRegistry(self.plugins) def test_find_all(self): - from letsencrypt.client.plugins.disco import PluginsRegistry - with mock.patch("letsencrypt.client.plugins.disco" - ".pkg_resources") as mock_pkg: + from letsencrypt.plugins.disco import PluginsRegistry + with mock.patch("letsencrypt.plugins.disco.pkg_resources") as mock_pkg: mock_pkg.iter_entry_points.return_value = iter([EP_SA]) plugins = PluginsRegistry.find_all() self.assertTrue(plugins["sa"].plugin_cls diff --git a/letsencrypt/plugins/standalone/__init__.py b/letsencrypt/plugins/standalone/__init__.py new file mode 100644 index 000000000..972c484ed --- /dev/null +++ b/letsencrypt/plugins/standalone/__init__.py @@ -0,0 +1 @@ +"""Let's Encrypt Standalone Authenticator plugin.""" diff --git a/letsencrypt/client/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py similarity index 98% rename from letsencrypt/client/plugins/standalone/authenticator.py rename to letsencrypt/plugins/standalone/authenticator.py index dc8b7eac7..746cd505a 100644 --- a/letsencrypt/client/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -14,10 +14,10 @@ import zope.interface from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import interfaces +from letsencrypt import achallenges +from letsencrypt import interfaces -from letsencrypt.client.plugins import common +from letsencrypt.plugins import common class StandaloneAuthenticator(common.Plugin): @@ -196,7 +196,7 @@ class StandaloneAuthenticator(common.Plugin): :param int port: Which TCP port to bind. :param key: The private key to use to respond to DVSNI challenge requests. - :type key: `letsencrypt.client.le_util.Key` + :type key: `letsencrypt.le_util.Key` """ signal.signal(signal.SIGINT, self.subproc_signal_handler) @@ -252,7 +252,7 @@ class StandaloneAuthenticator(common.Plugin): :param int port: The TCP port to bind. :param key: The private key to use to respond to DVSNI challenge requests. - :type key: :class:`letsencrypt.client.le_util.Key` + :type key: :class:`letsencrypt.le_util.Key` :returns: ``True`` or ``False`` to indicate success or failure creating the subprocess. diff --git a/letsencrypt/client/plugins/standalone/tests/__init__.py b/letsencrypt/plugins/standalone/tests/__init__.py similarity index 100% rename from letsencrypt/client/plugins/standalone/tests/__init__.py rename to letsencrypt/plugins/standalone/tests/__init__.py diff --git a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py b/letsencrypt/plugins/standalone/tests/authenticator_test.py similarity index 84% rename from letsencrypt/client/plugins/standalone/tests/authenticator_test.py rename to letsencrypt/plugins/standalone/tests/authenticator_test.py index 230756e1d..5cfae33cc 100644 --- a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/plugins/standalone/tests/authenticator_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.plugins.standalone.authenticator.""" +"""Tests for letsencrypt.plugins.standalone.authenticator.""" import os import pkg_resources import psutil @@ -12,10 +12,10 @@ import OpenSSL.SSL from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import le_util -from letsencrypt.client.tests import acme_util +from letsencrypt.tests import acme_util KEY = le_util.Key("foo", pkg_resources.resource_string( @@ -57,7 +57,7 @@ class CallableExhausted(Exception): class ChallPrefTest(unittest.TestCase): """Tests for chall_pref() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) @@ -69,7 +69,7 @@ class ChallPrefTest(unittest.TestCase): class SNICallbackTest(unittest.TestCase): """Tests for sni_callback() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) self.cert = achallenges.DVSNI( @@ -107,7 +107,7 @@ class SNICallbackTest(unittest.TestCase): class ClientSignalHandlerTest(unittest.TestCase): """Tests for client_signal_handler() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} @@ -136,15 +136,15 @@ class ClientSignalHandlerTest(unittest.TestCase): class SubprocSignalHandlerTest(unittest.TestCase): """Tests for subproc_signal_handler() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} self.authenticator.child_pid = 12345 self.authenticator.parent_pid = 23456 - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.sys.exit") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.sys.exit") def test_subproc_signal_handler(self, mock_exit, mock_kill): self.authenticator.ssl_conn = mock.MagicMock() self.authenticator.connection = mock.MagicMock() @@ -158,8 +158,8 @@ class SubprocSignalHandlerTest(unittest.TestCase): self.authenticator.parent_pid, signal.SIGUSR1) mock_exit.assert_called_once_with(0) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.sys.exit") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.sys.exit") def test_subproc_signal_handler_trouble(self, mock_exit, mock_kill): """Test attempting to shut down a non-existent connection. @@ -188,15 +188,14 @@ class SubprocSignalHandlerTest(unittest.TestCase): class AlreadyListeningTest(unittest.TestCase): """Tests for already_listening() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil." "net_connections") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "psutil.Process") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_race_condition(self, mock_get_utility, mock_process, mock_net): # This tests a race condition, or permission problem, or OS @@ -220,11 +219,10 @@ class AlreadyListeningTest(unittest.TestCase): self.assertEqual(mock_get_utility.generic_notification.call_count, 0) mock_process.assert_called_once_with(4416) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil." "net_connections") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "psutil.Process") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_not_listening(self, mock_get_utility, mock_process, mock_net): from psutil._common import sconn @@ -241,11 +239,10 @@ class AlreadyListeningTest(unittest.TestCase): self.assertEqual(mock_get_utility.generic_notification.call_count, 0) self.assertEqual(mock_process.call_count, 0) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil." "net_connections") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "psutil.Process") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net): from psutil._common import sconn @@ -265,11 +262,10 @@ class AlreadyListeningTest(unittest.TestCase): self.assertEqual(mock_get_utility.call_count, 1) mock_process.assert_called_once_with(4416) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.psutil." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil." "net_connections") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "psutil.Process") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net): from psutil._common import sconn @@ -295,7 +291,7 @@ class AlreadyListeningTest(unittest.TestCase): class PerformTest(unittest.TestCase): """Tests for perform() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) @@ -369,13 +365,13 @@ class PerformTest(unittest.TestCase): class StartListenerTest(unittest.TestCase): """Tests for start_listener() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator." "Crypto.Random.atfork") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.fork") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.fork") def test_start_listener_fork_parent(self, mock_fork, mock_atfork): self.authenticator.do_parent_process = mock.Mock() self.authenticator.do_parent_process.return_value = True @@ -388,9 +384,9 @@ class StartListenerTest(unittest.TestCase): self.authenticator.do_parent_process.assert_called_once_with(1717) mock_atfork.assert_called_once_with() - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator." "Crypto.Random.atfork") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.fork") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.fork") def test_start_listener_fork_child(self, mock_fork, mock_atfork): self.authenticator.do_parent_process = mock.Mock() self.authenticator.do_child_process = mock.Mock() @@ -404,13 +400,12 @@ class StartListenerTest(unittest.TestCase): class DoParentProcessTest(unittest.TestCase): """Tests for do_parent_process() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "signal.signal") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_do_parent_process_ok(self, mock_get_utility, mock_signal): self.authenticator.subproc_state = "ready" @@ -419,9 +414,8 @@ class DoParentProcessTest(unittest.TestCase): self.assertEqual(mock_get_utility.call_count, 1) self.assertEqual(mock_signal.call_count, 3) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "signal.signal") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_do_parent_process_inuse(self, mock_get_utility, mock_signal): self.authenticator.subproc_state = "inuse" @@ -430,9 +424,8 @@ class DoParentProcessTest(unittest.TestCase): self.assertEqual(mock_get_utility.call_count, 1) self.assertEqual(mock_signal.call_count, 3) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "signal.signal") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_do_parent_process_cantbind(self, mock_get_utility, mock_signal): self.authenticator.subproc_state = "cantbind" @@ -441,9 +434,8 @@ class DoParentProcessTest(unittest.TestCase): self.assertEqual(mock_get_utility.call_count, 1) self.assertEqual(mock_signal.call_count, 3) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "signal.signal") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator.signal.signal") + @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") def test_do_parent_process_timeout(self, mock_get_utility, mock_signal): # Normally times out in 5 seconds and returns False. We can @@ -458,7 +450,7 @@ class DoParentProcessTest(unittest.TestCase): class DoChildProcessTest(unittest.TestCase): """Tests for do_child_process() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) self.cert = achallenges.DVSNI( @@ -469,10 +461,9 @@ class DoChildProcessTest(unittest.TestCase): self.authenticator.tasks = {"abcdef.acme.invalid": self.cert} self.authenticator.parent_pid = 12345 - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "socket.socket") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.sys.exit") + @mock.patch("letsencrypt.plugins.standalone.authenticator.socket.socket") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.sys.exit") def test_do_child_process_cantbind1( self, mock_exit, mock_kill, mock_socket): mock_exit.side_effect = IndentationError("subprocess would exit here") @@ -491,10 +482,9 @@ class DoChildProcessTest(unittest.TestCase): mock_exit.assert_called_once_with(1) mock_kill.assert_called_once_with(12345, signal.SIGUSR2) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "socket.socket") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.sys.exit") + @mock.patch("letsencrypt.plugins.standalone.authenticator.socket.socket") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.sys.exit") def test_do_child_process_cantbind2(self, mock_exit, mock_kill, mock_socket): mock_exit.side_effect = IndentationError("subprocess would exit here") @@ -507,7 +497,7 @@ class DoChildProcessTest(unittest.TestCase): mock_exit.assert_called_once_with(1) mock_kill.assert_called_once_with(12345, signal.SIGUSR1) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator." "socket.socket") def test_do_child_process_cantbind3(self, mock_socket): """Test case where attempt to bind socket results in an unhandled @@ -521,11 +511,10 @@ class DoChildProcessTest(unittest.TestCase): self.assertRaises( socket.error, self.authenticator.do_child_process, 1717, KEY) - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." + @mock.patch("letsencrypt.plugins.standalone.authenticator." "OpenSSL.SSL.Connection") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "socket.socket") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.socket.socket") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") def test_do_child_process_success( self, mock_kill, mock_socket, mock_connection): sample_socket = mock.MagicMock() @@ -547,7 +536,7 @@ class DoChildProcessTest(unittest.TestCase): class CleanupTest(unittest.TestCase): """Tests for cleanup() method.""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import \ + from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator self.authenticator = StandaloneAuthenticator(config=None, name=None) self.achall = achallenges.DVSNI( @@ -557,9 +546,8 @@ class CleanupTest(unittest.TestCase): self.authenticator.tasks = {self.achall.nonce_domain: "stuff"} self.authenticator.child_pid = 12345 - @mock.patch("letsencrypt.client.plugins.standalone.authenticator.os.kill") - @mock.patch("letsencrypt.client.plugins.standalone.authenticator." - "time.sleep") + @mock.patch("letsencrypt.plugins.standalone.authenticator.os.kill") + @mock.patch("letsencrypt.plugins.standalone.authenticator.time.sleep") def test_cleanup(self, mock_sleep, mock_kill): mock_sleep.return_value = None mock_kill.return_value = None @@ -580,7 +568,7 @@ class CleanupTest(unittest.TestCase): class MoreInfoTest(unittest.TestCase): """Tests for more_info() method. (trivially)""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import ( + from letsencrypt.plugins.standalone.authenticator import ( StandaloneAuthenticator) self.authenticator = StandaloneAuthenticator(config=None, name=None) @@ -592,7 +580,7 @@ class MoreInfoTest(unittest.TestCase): class InitTest(unittest.TestCase): """Tests for more_info() method. (trivially)""" def setUp(self): - from letsencrypt.client.plugins.standalone.authenticator import ( + from letsencrypt.plugins.standalone.authenticator import ( StandaloneAuthenticator) self.authenticator = StandaloneAuthenticator(config=None, name=None) diff --git a/letsencrypt/client/recovery_token.py b/letsencrypt/recovery_token.py similarity index 89% rename from letsencrypt/client/recovery_token.py rename to letsencrypt/recovery_token.py index 3be0471ab..c5796d581 100644 --- a/letsencrypt/client/recovery_token.py +++ b/letsencrypt/recovery_token.py @@ -6,8 +6,8 @@ import zope.component from acme import challenges -from letsencrypt.client import le_util -from letsencrypt.client import interfaces +from letsencrypt import le_util +from letsencrypt import interfaces class RecoveryToken(object): @@ -23,7 +23,7 @@ class RecoveryToken(object): """Perform the Recovery Token Challenge. :param chall: Recovery Token Challenge - :type chall: :class:`letsencrypt.client.achallenges.RecoveryToken` + :type chall: :class:`letsencrypt.achallenges.RecoveryToken` :returns: response :rtype: dict @@ -46,7 +46,7 @@ class RecoveryToken(object): """Cleanup the saved recovery token if it exists. :param chall: Recovery Token Challenge - :type chall: :class:`letsencrypt.client.achallenges.RecoveryToken` + :type chall: :class:`letsencrypt.achallenges.RecoveryToken` """ try: diff --git a/letsencrypt/client/reverter.py b/letsencrypt/reverter.py similarity index 95% rename from letsencrypt/client/reverter.py rename to letsencrypt/reverter.py index 9d739f37e..604c3999a 100644 --- a/letsencrypt/client/reverter.py +++ b/letsencrypt/reverter.py @@ -6,19 +6,19 @@ import time import zope.component -from letsencrypt.client import constants -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util +from letsencrypt import constants +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class Reverter(object): """Reverter Class - save and revert configuration checkpoints. :param config: Configuration. - :type config: :class:`letsencrypt.client.interfaces.IConfig` + :type config: :class:`letsencrypt.interfaces.IConfig` """ def __init__(self, config): @@ -30,7 +30,7 @@ class Reverter(object): This function should reinstall the users original configuration files for all saves with temporary=True - :raises letsencrypt.client.errors.LetsEncryptReverterError: when + :raises letsencrypt.errors.LetsEncryptReverterError: when unable to revert config """ @@ -50,7 +50,7 @@ class Reverter(object): :param int rollback: Number of checkpoints to reverse. A str num will be cast to an integer. So "2" is also acceptable. - :raises letsencrypt.client.errors.LetsEncryptReverterError: If + :raises letsencrypt.errors.LetsEncryptReverterError: If there is a problem with the input or if the function is unable to correctly revert the configuration checkpoints. @@ -86,7 +86,7 @@ class Reverter(object): """Displays all saved checkpoints. All checkpoints are printed by - :meth:`letsencrypt.client.interfaces.IDisplay.notification`. + :meth:`letsencrypt.interfaces.IDisplay.notification`. .. todo:: Decide on a policy for error handling, OSError IOError... @@ -162,7 +162,7 @@ class Reverter(object): :param str save_notes: notes about changes made during the save :raises IOError: If unable to open cp_dir + FILEPATHS file - :raises letsencrypt.client.errors.LetsEncryptReverterError: If + :raises letsencrypt.errors.LetsEncryptReverterError: If unable to add checkpoint """ @@ -256,7 +256,7 @@ class Reverter(object): :param set save_files: Set of files about to be saved. - :raises letsencrypt.client.errors.LetsEncryptReverterError: + :raises letsencrypt.errors.LetsEncryptReverterError: when save is attempting to overwrite a temporary file. """ @@ -292,7 +292,7 @@ class Reverter(object): a temp or permanent save. :param \*files: file paths (str) to be registered - :raises letsencrypt.client.errors.LetsEncryptReverterError: If + :raises letsencrypt.errors.LetsEncryptReverterError: If call does not contain necessary parameters or if the file creation is unable to be registered. @@ -362,7 +362,7 @@ class Reverter(object): :returns: Success :rtype: bool - :raises letsencrypt.client.errors.LetsEncryptReverterError: If + :raises letsencrypt.errors.LetsEncryptReverterError: If all files within file_list cannot be removed """ @@ -400,7 +400,7 @@ class Reverter(object): :param str title: Title describing checkpoint - :raises letsencrypt.client.errors.LetsEncryptReverterError: when the + :raises letsencrypt.errors.LetsEncryptReverterError: when the checkpoint is not able to be finalized. """ diff --git a/letsencrypt/client/revoker.py b/letsencrypt/revoker.py similarity index 95% rename from letsencrypt/client/revoker.py rename to letsencrypt/revoker.py index 31f2d85ce..a1ea27e71 100644 --- a/letsencrypt/client/revoker.py +++ b/letsencrypt/revoker.py @@ -19,12 +19,12 @@ import M2Crypto from acme import messages from acme.jose import util as jose_util -from letsencrypt.client import errors -from letsencrypt.client import le_util -from letsencrypt.client import network +from letsencrypt import errors +from letsencrypt import le_util +from letsencrypt import network -from letsencrypt.client.display import util as display_util -from letsencrypt.client.display import revocation +from letsencrypt.display import util as display_util +from letsencrypt.display import revocation class Revoker(object): @@ -33,13 +33,13 @@ class Revoker(object): .. todo:: Add a method to specify your own certificate for revocation - CLI :ivar network: Network object - :type network: :class:`letsencrypt.client.network` + :type network: :class:`letsencrypt.network` :ivar installer: Installer object - :type installer: :class:`~letsencrypt.client.interfaces.IInstaller` + :type installer: :class:`~letsencrypt.interfaces.IInstaller` :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` :ivar bool no_confirm: Whether or not to ask for confirmation for revocation @@ -61,7 +61,7 @@ class Revoker(object): """Revoke all certificates under an authorized key. :param authkey: Authorized key used in previous transactions - :type authkey: :class:`letsencrypt.client.le_util.Key` + :type authkey: :class:`letsencrypt.le_util.Key` """ certs = [] @@ -205,10 +205,10 @@ class Revoker(object): """Confirm and revoke certificates. :param certs: certs intended to be revoked - :type certs: :class:`list` of :class:`letsencrypt.client.revoker.Cert` + :type certs: :class:`list` of :class:`letsencrypt.revoker.Cert` :returns: certs successfully revoked - :rtype: :class:`list` of :class:`letsencrypt.client.revoker.Cert` + :rtype: :class:`list` of :class:`letsencrypt.revoker.Cert` """ success_list = [] @@ -233,7 +233,7 @@ class Revoker(object): """Revoke the certificate with the ACME server. :param cert: certificate to revoke - :type cert: :class:`letsencrypt.client.revoker.Cert` + :type cert: :class:`letsencrypt.revoker.Cert` :returns: TODO @@ -259,7 +259,7 @@ class Revoker(object): """Remove certificate and key. :param list cert_list: Must contain certs, each is of type - :class:`letsencrypt.client.revoker.Cert` + :class:`letsencrypt.revoker.Cert` """ # This must occur first, LIST is the official key @@ -274,7 +274,7 @@ class Revoker(object): """Remove a certificate from the LIST file. :param list cert_list: Must contain valid certs, each is of type - :class:`letsencrypt.client.revoker.Cert` + :class:`letsencrypt.revoker.Cert` """ list_path2 = tempfile.mktemp(".tmp", "LIST") @@ -319,7 +319,7 @@ class Revoker(object): :param str key_path: Path to authorized key for certificate :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` """ list_path = os.path.join(config.cert_key_backup, "LIST") diff --git a/letsencrypt/client/tests/__init__.py b/letsencrypt/tests/__init__.py similarity index 100% rename from letsencrypt/client/tests/__init__.py rename to letsencrypt/tests/__init__.py diff --git a/letsencrypt/client/tests/account_test.py b/letsencrypt/tests/account_test.py similarity index 80% rename from letsencrypt/client/tests/account_test.py rename to letsencrypt/tests/account_test.py index c99edc8ee..f0153e18c 100644 --- a/letsencrypt/client/tests/account_test.py +++ b/letsencrypt/tests/account_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.account.""" +"""Tests for letsencrypt.account.""" import logging import mock import os @@ -9,18 +9,18 @@ import unittest from acme import messages2 -from letsencrypt.client import configuration -from letsencrypt.client import errors -from letsencrypt.client import le_util +from letsencrypt import configuration +from letsencrypt import errors +from letsencrypt import le_util -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class AccountTest(unittest.TestCase): - """Tests letsencrypt.client.account.Account.""" + """Tests letsencrypt.account.Account.""" def setUp(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account logging.disable(logging.CRITICAL) @@ -55,10 +55,10 @@ class AccountTest(unittest.TestCase): shutil.rmtree(self.accounts_dir) logging.disable(logging.NOTSET) - @mock.patch("letsencrypt.client.account.zope.component.getUtility") - @mock.patch("letsencrypt.client.account.crypto_util.init_save_key") + @mock.patch("letsencrypt.account.zope.component.getUtility") + @mock.patch("letsencrypt.account.crypto_util.init_save_key") def test_prompts(self, mock_key, mock_util): - from letsencrypt.client.account import Account + from letsencrypt.account import Account mock_util().input.return_value = (display_util.OK, self.email) mock_key.return_value = self.key @@ -68,10 +68,10 @@ class AccountTest(unittest.TestCase): self.assertEqual(acc.key, self.key) self.assertEqual(acc.config, self.config) - @mock.patch("letsencrypt.client.account.zope.component.getUtility") - @mock.patch("letsencrypt.client.account.Account.from_email") + @mock.patch("letsencrypt.account.zope.component.getUtility") + @mock.patch("letsencrypt.account.Account.from_email") def test_prompts_bad_email(self, mock_from_email, mock_util): - from letsencrypt.client.account import Account + from letsencrypt.account import Account mock_from_email.side_effect = (errors.LetsEncryptClientError, "acc") mock_util().input.return_value = (display_util.OK, self.email) @@ -79,10 +79,10 @@ class AccountTest(unittest.TestCase): self.assertEqual(Account.from_prompts(self.config), "acc") - @mock.patch("letsencrypt.client.account.zope.component.getUtility") - @mock.patch("letsencrypt.client.account.crypto_util.init_save_key") + @mock.patch("letsencrypt.account.zope.component.getUtility") + @mock.patch("letsencrypt.account.crypto_util.init_save_key") def test_prompts_empty_email(self, mock_key, mock_util): - from letsencrypt.client.account import Account + from letsencrypt.account import Account mock_util().input.return_value = (display_util.OK, "") acc = Account.from_prompts(self.config) @@ -91,22 +91,22 @@ class AccountTest(unittest.TestCase): mock_key.assert_called_once_with( mock.ANY, mock.ANY, acc._get_config_filename(None)) - @mock.patch("letsencrypt.client.account.zope.component.getUtility") + @mock.patch("letsencrypt.account.zope.component.getUtility") def test_prompts_cancel(self, mock_util): - from letsencrypt.client.account import Account + from letsencrypt.account import Account mock_util().input.return_value = (display_util.CANCEL, "") self.assertTrue(Account.from_prompts(self.config) is None) def test_from_email(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account self.assertRaises(errors.LetsEncryptClientError, Account.from_email, self.config, "not_valid...email") def test_save_from_existing_account(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account self.test_account.save() acc = Account.from_existing_account(self.config, self.email) @@ -123,7 +123,7 @@ class AccountTest(unittest.TestCase): self.assertEqual(self.test_account.recovery_token, "recovery_token") def test_partial_properties(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account partial = Account(self.config, self.key) @@ -133,7 +133,7 @@ class AccountTest(unittest.TestCase): self.assertTrue(partial.recovery_token is None) def test_partial_account_default(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account partial = Account(self.config, self.key) partial.save() @@ -146,7 +146,7 @@ class AccountTest(unittest.TestCase): self.assertEqual(partial.regr, acc.regr) def test_get_accounts(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account accs = Account.get_accounts(self.config) self.assertFalse(accs) @@ -162,13 +162,13 @@ class AccountTest(unittest.TestCase): self.assertEqual(len(accs), 2) def test_get_accounts_no_accounts(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account self.assertEqual(Account.get_accounts( mock.Mock(accounts_dir="non-existant")), []) def test_failed_existing_account(self): - from letsencrypt.client.account import Account + from letsencrypt.account import Account self.assertRaises( errors.LetsEncryptClientError, @@ -185,7 +185,7 @@ class SafeEmailTest(unittest.TestCase): @classmethod def _call(cls, addr): - from letsencrypt.client.account import Account + from letsencrypt.account import Account return Account.safe_email(addr) def test_valid_emails(self): diff --git a/letsencrypt/client/tests/achallenges_test.py b/letsencrypt/tests/achallenges_test.py similarity index 83% rename from letsencrypt/client/tests/achallenges_test.py rename to letsencrypt/tests/achallenges_test.py index 476ff7cab..c22ff6227 100644 --- a/letsencrypt/client/tests/achallenges_test.py +++ b/letsencrypt/tests/achallenges_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.achallenges.""" +"""Tests for letsencrypt.achallenges.""" import os import pkg_resources import re @@ -7,12 +7,12 @@ import unittest import M2Crypto from acme import challenges -from letsencrypt.client import le_util -from letsencrypt.client.tests import acme_util +from letsencrypt import le_util +from letsencrypt.tests import acme_util class DVSNITest(unittest.TestCase): - """Tests for letsencrypt.client.achallenges.DVSNI.""" + """Tests for letsencrypt.achallenges.DVSNI.""" def setUp(self): self.chall = acme_util.chall_to_challb( @@ -21,7 +21,7 @@ class DVSNITest(unittest.TestCase): key = le_util.Key("path", pkg_resources.resource_string( "acme.jose", os.path.join("testdata", "rsa512_key.pem"))) - from letsencrypt.client.achallenges import DVSNI + from letsencrypt.achallenges import DVSNI self.achall = DVSNI(challb=self.chall, domain="example.com", key=key) def test_proxy(self): diff --git a/letsencrypt/client/tests/acme_util.py b/letsencrypt/tests/acme_util.py similarity index 100% rename from letsencrypt/client/tests/acme_util.py rename to letsencrypt/tests/acme_util.py diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py similarity index 92% rename from letsencrypt/client/tests/auth_handler_test.py rename to letsencrypt/tests/auth_handler_test.py index b03f25735..effc09990 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.auth_handler.""" +"""Tests for letsencrypt.auth_handler.""" import functools import logging import unittest @@ -8,11 +8,11 @@ import mock from acme import challenges from acme import messages2 -from letsencrypt.client import errors -from letsencrypt.client import le_util -from letsencrypt.client import network2 +from letsencrypt import errors +from letsencrypt import le_util +from letsencrypt import network2 -from letsencrypt.client.tests import acme_util +from letsencrypt.tests import acme_util TRANSLATE = { @@ -29,7 +29,7 @@ class ChallengeFactoryTest(unittest.TestCase): # pylint: disable=protected-access def setUp(self): - from letsencrypt.client.auth_handler import AuthHandler + from letsencrypt.auth_handler import AuthHandler # Account is mocked... self.handler = AuthHandler( @@ -73,7 +73,7 @@ class GetAuthorizationsTest(unittest.TestCase): """ def setUp(self): - from letsencrypt.client.auth_handler import AuthHandler + from letsencrypt.auth_handler import AuthHandler self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator") self.mock_cont_auth = mock.MagicMock(name="ContinuityAuthenticator") @@ -97,7 +97,7 @@ class GetAuthorizationsTest(unittest.TestCase): def tearDown(self): logging.disable(logging.NOTSET) - @mock.patch("letsencrypt.client.auth_handler.AuthHandler._poll_challenges") + @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name1_dvsni1(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.DV_CHALLENGES) @@ -121,7 +121,7 @@ class GetAuthorizationsTest(unittest.TestCase): self.assertEqual(len(authzr), 1) - @mock.patch("letsencrypt.client.auth_handler.AuthHandler._poll_challenges") + @mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges") def test_name3_dvsni3_rectok_3(self, mock_poll): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.CHALLENGES) @@ -180,8 +180,8 @@ class PollChallengesTest(unittest.TestCase): """Test poll challenges.""" def setUp(self): - from letsencrypt.client.auth_handler import challb_to_achall - from letsencrypt.client.auth_handler import AuthHandler + from letsencrypt.auth_handler import challb_to_achall + from letsencrypt.auth_handler import AuthHandler # Account and network are mocked... self.mock_net = mock.MagicMock() @@ -207,7 +207,7 @@ class PollChallengesTest(unittest.TestCase): challb_to_achall(challb, "dummy_key", dom) for challb in self.handler.authzr[dom].body.challenges] - @mock.patch("letsencrypt.client.auth_handler.time") + @mock.patch("letsencrypt.auth_handler.time") def test_poll_challenges(self, unused_mock_time): self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid self.handler._poll_challenges(self.chall_update, False) @@ -215,7 +215,7 @@ class PollChallengesTest(unittest.TestCase): for authzr in self.handler.authzr.values(): self.assertEqual(authzr.body.status, messages2.STATUS_VALID) - @mock.patch("letsencrypt.client.auth_handler.time") + @mock.patch("letsencrypt.auth_handler.time") def test_poll_challenges_failure_best_effort(self, unused_mock_time): self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid self.handler._poll_challenges(self.chall_update, True) @@ -223,16 +223,16 @@ class PollChallengesTest(unittest.TestCase): for authzr in self.handler.authzr.values(): self.assertEqual(authzr.body.status, messages2.STATUS_PENDING) - @mock.patch("letsencrypt.client.auth_handler.time") + @mock.patch("letsencrypt.auth_handler.time") def test_poll_challenges_failure(self, unused_mock_time): self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid self.assertRaises(errors.AuthorizationError, self.handler._poll_challenges, self.chall_update, False) - @mock.patch("letsencrypt.client.auth_handler.time") + @mock.patch("letsencrypt.auth_handler.time") def test_unable_to_find_challenge_status(self, unused_mock_time): - from letsencrypt.client.auth_handler import challb_to_achall + from letsencrypt.auth_handler import challb_to_achall self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid self.chall_update[self.doms[0]].append( challb_to_achall(acme_util.RECOVERY_CONTACT_P, "key", self.doms[0])) @@ -292,7 +292,7 @@ class PollChallengesTest(unittest.TestCase): return (new_authzr, "response") class GenChallengePathTest(unittest.TestCase): - """Tests for letsencrypt.client.auth_handler.gen_challenge_path. + """Tests for letsencrypt.auth_handler.gen_challenge_path. .. todo:: Add more tests for dumb_path... depending on what we want to do. @@ -305,7 +305,7 @@ class GenChallengePathTest(unittest.TestCase): @classmethod def _call(cls, challbs, preferences, combinations): - from letsencrypt.client.auth_handler import gen_challenge_path + from letsencrypt.auth_handler import gen_challenge_path return gen_challenge_path(challbs, preferences, combinations) def test_common_case(self): @@ -363,7 +363,7 @@ class GenChallengePathTest(unittest.TestCase): class MutuallyExclusiveTest(unittest.TestCase): - """Tests for letsencrypt.client.auth_handler.mutually_exclusive.""" + """Tests for letsencrypt.auth_handler.mutually_exclusive.""" # pylint: disable=invalid-name,missing-docstring,too-few-public-methods class A(object): @@ -380,7 +380,7 @@ class MutuallyExclusiveTest(unittest.TestCase): @classmethod def _call(cls, chall1, chall2, different=False): - from letsencrypt.client.auth_handler import mutually_exclusive + from letsencrypt.auth_handler import mutually_exclusive return mutually_exclusive(chall1, chall2, groups=frozenset([ frozenset([cls.A, cls.B]), frozenset([cls.A, cls.C]), ]), different=different) @@ -407,11 +407,11 @@ class MutuallyExclusiveTest(unittest.TestCase): class IsPreferredTest(unittest.TestCase): - """Tests for letsencrypt.client.auth_handler.is_preferred.""" + """Tests for letsencrypt.auth_handler.is_preferred.""" @classmethod def _call(cls, chall, satisfied): - from letsencrypt.client.auth_handler import is_preferred + from letsencrypt.auth_handler import is_preferred return is_preferred(chall, satisfied, exclusive_groups=frozenset([ frozenset([challenges.DVSNI, challenges.SimpleHTTPS]), frozenset([challenges.DNS, challenges.SimpleHTTPS]), diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/tests/cli_test.py similarity index 77% rename from letsencrypt/client/tests/cli_test.py rename to letsencrypt/tests/cli_test.py index bd25a9792..fc6e493ea 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.cli.""" +"""Tests for letsencrypt.cli.""" import itertools import unittest @@ -10,10 +10,10 @@ class CLITest(unittest.TestCase): @classmethod def _call(cls, args): - from letsencrypt.client import cli + from letsencrypt import cli args = ['--text'] + args - with mock.patch("letsencrypt.client.cli.sys.stdout") as stdout: - with mock.patch("letsencrypt.client.cli.sys.stderr") as stderr: + with mock.patch("letsencrypt.cli.sys.stdout") as stdout: + with mock.patch("letsencrypt.cli.sys.stderr") as stderr: ret = cli.main(args) return ret, stdout, stderr diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/tests/client_test.py similarity index 66% rename from letsencrypt/client/tests/client_test.py rename to letsencrypt/tests/client_test.py index 52fc1be94..58e95e5cf 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/tests/client_test.py @@ -1,4 +1,4 @@ -"""letsencrypt.client.client.py tests.""" +"""Tests for letsencrypt.client.""" import os import unittest import shutil @@ -6,9 +6,9 @@ import tempfile import mock -from letsencrypt.client import account -from letsencrypt.client import configuration -from letsencrypt.client import le_util +from letsencrypt import account +from letsencrypt import configuration +from letsencrypt import le_util class DetermineAccountTest(unittest.TestCase): @@ -25,11 +25,11 @@ class DetermineAccountTest(unittest.TestCase): def tearDown(self): shutil.rmtree(self.accounts_dir) - @mock.patch("letsencrypt.client.client.account.Account.from_prompts") - @mock.patch("letsencrypt.client.client.display_ops.choose_account") + @mock.patch("letsencrypt.account.Account.from_prompts") + @mock.patch("letsencrypt.client.display_ops.choose_account") def determine_account(self, mock_op, mock_prompt): """Test determine account""" - from letsencrypt.client import client + from letsencrypt import client key = le_util.Key("file", "pem") test_acc = account.Account(self.config, key, "email1@gmail.com") @@ -60,24 +60,20 @@ class RollbackTest(unittest.TestCase): self.m_install = mock.MagicMock(spec=ApacheConfigurator) @classmethod - def _call(cls, checkpoints): - from letsencrypt.client.client import rollback - rollback(None, checkpoints, {}, mock.MagicMock()) - - @mock.patch("letsencrypt.client.client.display_ops.pick_installer") - def test_no_problems(self, mock_pick_installer): - mock_pick_installer.side_effect = self.m_install - - self._call(1) + def _call(cls, checkpoints, side_effect): + from letsencrypt.client import rollback + with mock.patch("letsencrypt.client" + ".display_ops.pick_installer") as mock_pick_installer: + mock_pick_installer.side_effect = side_effect + rollback(None, checkpoints, {}, mock.MagicMock()) + def test_no_problems(self): + self._call(1, self.m_install) self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1) self.assertEqual(self.m_install().restart.call_count, 1) - @mock.patch("letsencrypt.client.client.display_ops.pick_installer") - def test_no_installer(self, mock_pick_installer): - mock_pick_installer.return_value = None - self._call(1) - # Just make sure no exceptions are raised + def test_no_installer(self): + self._call(1, None) # Just make sure no exceptions are raised if __name__ == "__main__": diff --git a/letsencrypt/client/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py similarity index 86% rename from letsencrypt/client/tests/configuration_test.py rename to letsencrypt/tests/configuration_test.py index cbbcd57ba..ddfc9de23 100644 --- a/letsencrypt/client/tests/configuration_test.py +++ b/letsencrypt/tests/configuration_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.configuration.""" +"""Tests for letsencrypt.configuration.""" import os import unittest @@ -6,10 +6,10 @@ import mock class NamespaceConfigTest(unittest.TestCase): - """Tests for letsencrypt.client.configuration.NamespaceConfig.""" + """Tests for letsencrypt.configuration.NamespaceConfig.""" def setUp(self): - from letsencrypt.client.configuration import NamespaceConfig + from letsencrypt.configuration import NamespaceConfig namespace = mock.MagicMock( config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar', server='acme-server.org:443/new') @@ -27,7 +27,7 @@ class NamespaceConfigTest(unittest.TestCase): self.assertEqual( self.config.server_url, 'https://acme-server.org:443/new') - @mock.patch('letsencrypt.client.configuration.constants') + @mock.patch('letsencrypt.configuration.constants') def test_dynamic_dirs(self, constants): constants.TEMP_CHECKPOINT_DIR = 't' constants.IN_PROGRESS_DIR = '../p' diff --git a/letsencrypt/client/tests/continuity_auth_test.py b/letsencrypt/tests/continuity_auth_test.py similarity index 91% rename from letsencrypt/client/tests/continuity_auth_test.py rename to letsencrypt/tests/continuity_auth_test.py index b8b5e7402..715651f19 100644 --- a/letsencrypt/client/tests/continuity_auth_test.py +++ b/letsencrypt/tests/continuity_auth_test.py @@ -5,15 +5,15 @@ import mock from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import errors +from letsencrypt import achallenges +from letsencrypt import errors class PerformTest(unittest.TestCase): """Test client perform function.""" def setUp(self): - from letsencrypt.client.continuity_auth import ContinuityAuthenticator + from letsencrypt.continuity_auth import ContinuityAuthenticator self.auth = ContinuityAuthenticator( mock.MagicMock(server="demo_server.org")) @@ -50,7 +50,7 @@ class CleanupTest(unittest.TestCase): """Test the Authenticator cleanup function.""" def setUp(self): - from letsencrypt.client.continuity_auth import ContinuityAuthenticator + from letsencrypt.continuity_auth import ContinuityAuthenticator self.auth = ContinuityAuthenticator( mock.MagicMock(server="demo_server.org")) diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/tests/crypto_util_test.py similarity index 75% rename from letsencrypt/client/tests/crypto_util_test.py rename to letsencrypt/tests/crypto_util_test.py index f0c234598..91b3f1c85 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/tests/crypto_util_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.crypto_util.""" +"""Tests for letsencrypt.crypto_util.""" import logging import os import pkg_resources @@ -17,7 +17,7 @@ RSA512_KEY = pkg_resources.resource_string( class InitSaveKeyTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.init_save_key.""" + """Tests for letsencrypt.crypto_util.init_save_key.""" def setUp(self): logging.disable(logging.CRITICAL) self.key_dir = tempfile.mkdtemp('key_dir') @@ -28,24 +28,24 @@ class InitSaveKeyTest(unittest.TestCase): @classmethod def _call(cls, key_size, key_dir): - from letsencrypt.client.crypto_util import init_save_key + from letsencrypt.crypto_util import init_save_key return init_save_key(key_size, key_dir, 'key-letsencrypt.pem') - @mock.patch('letsencrypt.client.crypto_util.make_key') + @mock.patch('letsencrypt.crypto_util.make_key') def test_success(self, mock_make): mock_make.return_value = 'key_pem' key = self._call(1024, self.key_dir) self.assertEqual(key.pem, 'key_pem') self.assertTrue('key-letsencrypt.pem' in key.file) - @mock.patch('letsencrypt.client.crypto_util.make_key') + @mock.patch('letsencrypt.crypto_util.make_key') def test_key_failure(self, mock_make): mock_make.side_effect = ValueError self.assertRaises(ValueError, self._call, 431, self.key_dir) class InitSaveCSRTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.init_save_csr.""" + """Tests for letsencrypt.crypto_util.init_save_csr.""" def setUp(self): self.csr_dir = tempfile.mkdtemp('csr_dir') @@ -53,10 +53,10 @@ class InitSaveCSRTest(unittest.TestCase): def tearDown(self): shutil.rmtree(self.csr_dir) - @mock.patch('letsencrypt.client.crypto_util.make_csr') - @mock.patch('letsencrypt.client.crypto_util.le_util.make_or_verify_dir') + @mock.patch('letsencrypt.crypto_util.make_csr') + @mock.patch('letsencrypt.crypto_util.le_util.make_or_verify_dir') def test_it(self, unused_mock_verify, mock_csr): - from letsencrypt.client.crypto_util import init_save_csr + from letsencrypt.crypto_util import init_save_csr mock_csr.return_value = ('csr_pem', 'csr_der') @@ -68,11 +68,11 @@ class InitSaveCSRTest(unittest.TestCase): self.assertTrue('csr-letsencrypt.pem' in csr.file) class ValidCSRTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.valid_csr.""" + """Tests for letsencrypt.crypto_util.valid_csr.""" @classmethod def _call(cls, csr): - from letsencrypt.client.crypto_util import valid_csr + from letsencrypt.crypto_util import valid_csr return valid_csr(csr) def _call_testdata(self, name): @@ -99,11 +99,11 @@ class ValidCSRTest(unittest.TestCase): class CSRMatchesPubkeyTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.csr_matches_pubkey.""" + """Tests for letsencrypt.crypto_util.csr_matches_pubkey.""" @classmethod def _call_testdata(cls, name, privkey): - from letsencrypt.client.crypto_util import csr_matches_pubkey + from letsencrypt.crypto_util import csr_matches_pubkey return csr_matches_pubkey(pkg_resources.resource_string( __name__, os.path.join('testdata', name)), privkey) @@ -115,20 +115,20 @@ class CSRMatchesPubkeyTest(unittest.TestCase): class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods - """Tests for letsencrypt.client.crypto_util.make_key.""" + """Tests for letsencrypt.crypto_util.make_key.""" def test_it(self): # pylint: disable=no-self-use - from letsencrypt.client.crypto_util import make_key + from letsencrypt.crypto_util import make_key # Do not test larger keys as it takes too long. M2Crypto.RSA.load_key_string(make_key(1024)) class ValidPrivkeyTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.valid_privkey.""" + """Tests for letsencrypt.crypto_util.valid_privkey.""" @classmethod def _call(cls, privkey): - from letsencrypt.client.crypto_util import valid_privkey + from letsencrypt.crypto_util import valid_privkey return valid_privkey(privkey) def test_valid_true(self): @@ -143,10 +143,10 @@ class ValidPrivkeyTest(unittest.TestCase): class MakeSSCertTest(unittest.TestCase): # pylint: disable=too-few-public-methods - """Tests for letsencrypt.client.crypto_util.make_ss_cert.""" + """Tests for letsencrypt.crypto_util.make_ss_cert.""" def test_it(self): # pylint: disable=no-self-use - from letsencrypt.client.crypto_util import make_ss_cert + from letsencrypt.crypto_util import make_ss_cert make_ss_cert(RSA512_KEY, ['example.com', 'www.example.com']) diff --git a/letsencrypt/client/tests/display/__init__.py b/letsencrypt/tests/display/__init__.py similarity index 100% rename from letsencrypt/client/tests/display/__init__.py rename to letsencrypt/tests/display/__init__.py diff --git a/letsencrypt/client/tests/display/enhancements_test.py b/letsencrypt/tests/display/enhancements_test.py similarity index 71% rename from letsencrypt/client/tests/display/enhancements_test.py rename to letsencrypt/tests/display/enhancements_test.py index a7fb7f246..648eaecb1 100644 --- a/letsencrypt/client/tests/display/enhancements_test.py +++ b/letsencrypt/tests/display/enhancements_test.py @@ -4,8 +4,8 @@ import unittest import mock -from letsencrypt.client import errors -from letsencrypt.client.display import util as display_util +from letsencrypt import errors +from letsencrypt.display import util as display_util class AskTest(unittest.TestCase): @@ -18,10 +18,10 @@ class AskTest(unittest.TestCase): @classmethod def _call(cls, enhancement): - from letsencrypt.client.display.enhancements import ask + from letsencrypt.display.enhancements import ask return ask(enhancement) - @mock.patch("letsencrypt.client.display.enhancements.util") + @mock.patch("letsencrypt.display.enhancements.util") def test_redirect(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call("redirect")) @@ -35,20 +35,20 @@ class RedirectTest(unittest.TestCase): """Test the redirect_by_default method.""" @classmethod def _call(cls): - from letsencrypt.client.display.enhancements import redirect_by_default + from letsencrypt.display.enhancements import redirect_by_default return redirect_by_default() - @mock.patch("letsencrypt.client.display.enhancements.util") + @mock.patch("letsencrypt.display.enhancements.util") def test_secure(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call()) - @mock.patch("letsencrypt.client.display.enhancements.util") + @mock.patch("letsencrypt.display.enhancements.util") def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 1) self.assertFalse(self._call()) - @mock.patch("letsencrypt.client.display.enhancements.util") + @mock.patch("letsencrypt.display.enhancements.util") def test_easy(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) self.assertFalse(self._call()) diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/tests/display/ops_test.py similarity index 81% rename from letsencrypt/client/tests/display/ops_test.py rename to letsencrypt/tests/display/ops_test.py index 7c5c1f74f..d71237676 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/tests/display/ops_test.py @@ -1,4 +1,4 @@ -"""Test letsencrypt.client.display.ops.""" +"""Test letsencrypt.display.ops.""" import os import sys import tempfile @@ -7,15 +7,15 @@ import unittest import mock import zope.component -from letsencrypt.client import account -from letsencrypt.client import interfaces -from letsencrypt.client import le_util +from letsencrypt import account +from letsencrypt import interfaces +from letsencrypt import le_util -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class ChoosePluginTest(unittest.TestCase): - """Tests for letsencrypt.client.display.ops.choose_plugin.""" + """Tests for letsencrypt.display.ops.choose_plugin.""" def setUp(self): zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) @@ -30,15 +30,15 @@ class ChoosePluginTest(unittest.TestCase): ] def _call(self): - from letsencrypt.client.display.ops import choose_plugin + from letsencrypt.display.ops import choose_plugin return choose_plugin(self.plugins, "Question?") - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_successful_choice(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) self.assertEqual(self.mock_apache, self._call()) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_more_info(self, mock_util): mock_util().menu.side_effect = [ (display_util.HELP, 0), @@ -49,14 +49,14 @@ class ChoosePluginTest(unittest.TestCase): self.assertEqual(self.mock_stand, self._call()) self.assertEqual(mock_util().notification.call_count, 2) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_no_choice(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 0) self.assertTrue(self._call() is None) class PickPluginTest(unittest.TestCase): - """Tests for letsencrypt.client.display.ops.pick_plugin.""" + """Tests for letsencrypt.display.ops.pick_plugin.""" def setUp(self): self.config = mock.Mock() @@ -66,7 +66,7 @@ class PickPluginTest(unittest.TestCase): self.ifaces = [] def _call(self): - from letsencrypt.client.display.ops import pick_plugin + from letsencrypt.display.ops import pick_plugin return pick_plugin(self.config, self.default, self.reg, self.question, self.ifaces) @@ -95,8 +95,7 @@ class PickPluginTest(unittest.TestCase): "bar": plugin_ep, "baz": plugin_ep, } - with mock.patch("letsencrypt.client.display" - ".ops.choose_plugin") as mock_choose: + with mock.patch("letsencrypt.display.ops.choose_plugin") as mock_choose: mock_choose.return_value = plugin_ep self.assertEqual("foo", self._call()) mock_choose.assert_called_once_with( @@ -108,36 +107,35 @@ class PickPluginTest(unittest.TestCase): "baz": None, } - with mock.patch("letsencrypt.client.display" - ".ops.choose_plugin") as mock_choose: + with mock.patch("letsencrypt.display.ops.choose_plugin") as mock_choose: mock_choose.return_value = None self.assertTrue(self._call() is None) class ConveniencePickPluginTest(unittest.TestCase): - """Tests for letsencrypt.client.display.ops.pick_*.""" + """Tests for letsencrypt.display.ops.pick_*.""" def _test(self, fun, ifaces): config = mock.Mock() default = mock.Mock() plugins = mock.Mock() - with mock.patch("letsencrypt.client.display.ops.pick_plugin") as mock_p: + with mock.patch("letsencrypt.display.ops.pick_plugin") as mock_p: mock_p.return_value = "foo" self.assertEqual("foo", fun(config, default, plugins, "Question?")) mock_p.assert_called_once_with( config, default, plugins, "Question?", ifaces) def test_authenticator(self): - from letsencrypt.client.display.ops import pick_authenticator + from letsencrypt.display.ops import pick_authenticator self._test(pick_authenticator, (interfaces.IAuthenticator,)) def test_installer(self): - from letsencrypt.client.display.ops import pick_installer + from letsencrypt.display.ops import pick_installer self._test(pick_installer, (interfaces.IInstaller,)) def test_configurator(self): - from letsencrypt.client.display.ops import pick_configurator + from letsencrypt.display.ops import pick_configurator self._test(pick_configurator, ( interfaces.IAuthenticator, interfaces.IInstaller)) @@ -165,20 +163,20 @@ class ChooseAccountTest(unittest.TestCase): @classmethod def _call(cls, accounts): - from letsencrypt.client.display import ops + from letsencrypt.display import ops return ops.choose_account(accounts) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_one(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) self.assertEqual(self._call([self.acc1]), self.acc1) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_two(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertEqual(self._call([self.acc1, self.acc2]), self.acc2) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 1) self.assertTrue(self._call([self.acc1, self.acc2]) is None) @@ -191,7 +189,7 @@ class GenHttpsNamesTest(unittest.TestCase): @classmethod def _call(cls, domains): - from letsencrypt.client.display.ops import _gen_https_names + from letsencrypt.display.ops import _gen_https_names return _gen_https_names(domains) def test_zero(self): @@ -239,20 +237,20 @@ class ChooseNamesTest(unittest.TestCase): @classmethod def _call(cls, installer): - from letsencrypt.client.display.ops import choose_names + from letsencrypt.display.ops import choose_names return choose_names(installer) - @mock.patch("letsencrypt.client.display.ops._choose_names_manually") + @mock.patch("letsencrypt.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") + @mock.patch("letsencrypt.display.ops.util") def test_no_installer_cancel(self, mock_util): mock_util().input.return_value = (display_util.CANCEL, []) self.assertEqual(self._call(None), []) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.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 @@ -263,14 +261,14 @@ class ChooseNamesTest(unittest.TestCase): self.assertEqual(mock_util().input.call_count, 1) self.assertEqual(actual_doms, [domain]) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.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.assertEqual(self._call(self.mock_install), []) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.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"]) @@ -279,14 +277,14 @@ class ChooseNamesTest(unittest.TestCase): self.assertEqual(names, ["example.com"]) self.assertEqual(mock_util().checklist.call_count, 1) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.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.assertEqual(self._call(self.mock_install), []) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.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 = ( @@ -300,10 +298,10 @@ class SuccessInstallationTest(unittest.TestCase): """Test the success installation message.""" @classmethod def _call(cls, names): - from letsencrypt.client.display.ops import success_installation + from letsencrypt.display.ops import success_installation success_installation(names) - @mock.patch("letsencrypt.client.display.ops.util") + @mock.patch("letsencrypt.display.ops.util") def test_success_installation(self, mock_util): mock_util().notification.return_value = None names = ["example.com", "abc.com"] diff --git a/letsencrypt/client/tests/display/revocation_test.py b/letsencrypt/tests/display/revocation_test.py similarity index 68% rename from letsencrypt/client/tests/display/revocation_test.py rename to letsencrypt/tests/display/revocation_test.py index 557648d9d..f07c2cbfd 100644 --- a/letsencrypt/client/tests/display/revocation_test.py +++ b/letsencrypt/tests/display/revocation_test.py @@ -1,4 +1,4 @@ -"""Test :mod:`letsencrypt.client.display.revocation`.""" +"""Test :mod:`letsencrypt.display.revocation`.""" import os import pkg_resources import sys @@ -7,13 +7,13 @@ import unittest import mock import zope.component -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class DisplayCertsTest(unittest.TestCase): def setUp(self): - from letsencrypt.client.revoker import Cert - base_package = "letsencrypt.client.tests" + from letsencrypt.revoker import Cert + base_package = "letsencrypt.tests" self.cert0 = Cert(pkg_resources.resource_filename( base_package, os.path.join("testdata", "cert.pem"))) self.cert1 = Cert(pkg_resources.resource_filename( @@ -25,10 +25,10 @@ class DisplayCertsTest(unittest.TestCase): @classmethod def _call(cls, certs): - from letsencrypt.client.display.revocation import display_certs + from letsencrypt.display.revocation import display_certs return display_certs(certs) - @mock.patch("letsencrypt.client.display.revocation.util") + @mock.patch("letsencrypt.display.revocation.util") def test_revocation(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) @@ -37,7 +37,7 @@ class DisplayCertsTest(unittest.TestCase): self.assertEqual(display_util.OK, code) self.assertEqual(self.certs[choice], self.cert0) - @mock.patch("letsencrypt.client.display.revocation.util") + @mock.patch("letsencrypt.display.revocation.util") def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, -1) @@ -49,10 +49,10 @@ class MoreInfoCertTest(unittest.TestCase): # pylint: disable=too-few-public-methods @classmethod def _call(cls, cert): - from letsencrypt.client.display.revocation import more_info_cert + from letsencrypt.display.revocation import more_info_cert more_info_cert(cert) - @mock.patch("letsencrypt.client.display.revocation.util") + @mock.patch("letsencrypt.display.revocation.util") def test_more_info(self, mock_util): self._call(mock.MagicMock()) @@ -61,18 +61,18 @@ class MoreInfoCertTest(unittest.TestCase): class SuccessRevocationTest(unittest.TestCase): def setUp(self): - from letsencrypt.client.revoker import Cert - base_package = "letsencrypt.client.tests" + from letsencrypt.revoker import Cert + base_package = "letsencrypt.tests" self.cert = Cert(pkg_resources.resource_filename( base_package, os.path.join("testdata", "cert.pem"))) @classmethod def _call(cls, cert): - from letsencrypt.client.display.revocation import success_revocation + from letsencrypt.display.revocation import success_revocation success_revocation(cert) # Pretty trivial test... something is displayed... - @mock.patch("letsencrypt.client.display.revocation.util") + @mock.patch("letsencrypt.display.revocation.util") def test_success_revocation(self, mock_util): self._call(self.cert) @@ -81,16 +81,16 @@ class SuccessRevocationTest(unittest.TestCase): class ConfirmRevocationTest(unittest.TestCase): def setUp(self): - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert self.cert = Cert(pkg_resources.resource_filename( - "letsencrypt.client.tests", os.path.join("testdata", "cert.pem"))) + "letsencrypt.tests", os.path.join("testdata", "cert.pem"))) @classmethod def _call(cls, cert): - from letsencrypt.client.display.revocation import confirm_revocation + from letsencrypt.display.revocation import confirm_revocation return confirm_revocation(cert) - @mock.patch("letsencrypt.client.display.revocation.util") + @mock.patch("letsencrypt.display.revocation.util") def test_confirm_revocation(self, mock_util): mock_util().yesno.return_value = True self.assertTrue(self._call(self.cert)) diff --git a/letsencrypt/client/tests/display/util_test.py b/letsencrypt/tests/display/util_test.py similarity index 91% rename from letsencrypt/client/tests/display/util_test.py rename to letsencrypt/tests/display/util_test.py index 42c948c79..6cebf88ba 100644 --- a/letsencrypt/client/tests/display/util_test.py +++ b/letsencrypt/tests/display/util_test.py @@ -1,10 +1,10 @@ -"""Test :mod:`letsencrypt.client.display.util`.""" +"""Test :mod:`letsencrypt.display.util`.""" import os import unittest import mock -from letsencrypt.client.display import util as display_util +from letsencrypt.display import util as display_util class DisplayT(unittest.TestCase): @@ -53,13 +53,13 @@ class NcursesDisplayTest(DisplayT): "menu_height": display_util.HEIGHT-6, } - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.msgbox") + @mock.patch("letsencrypt.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.util.dialog.Dialog.menu") + @mock.patch("letsencrypt.display.util.dialog.Dialog.menu") def test_menu_tag_and_desc(self, mock_menu): mock_menu.return_value = (display_util.OK, "First") @@ -68,7 +68,7 @@ class NcursesDisplayTest(DisplayT): self.assertEqual(ret, (display_util.OK, 0)) - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu") + @mock.patch("letsencrypt.display.util.dialog.Dialog.menu") def test_menu_tag_and_desc_cancel(self, mock_menu): mock_menu.return_value = (display_util.CANCEL, "") @@ -78,7 +78,7 @@ class NcursesDisplayTest(DisplayT): self.assertEqual(ret, (display_util.CANCEL, -1)) - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu") + @mock.patch("letsencrypt.display.util.dialog.Dialog.menu") def test_menu_desc_only(self, mock_menu): mock_menu.return_value = (display_util.OK, "1") @@ -90,7 +90,7 @@ class NcursesDisplayTest(DisplayT): self.assertEqual(ret, (display_util.OK, 0)) - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu") + @mock.patch("letsencrypt.display.util.dialog.Dialog.menu") def test_menu_desc_only_help(self, mock_menu): mock_menu.return_value = (display_util.HELP, "2") @@ -98,7 +98,7 @@ class NcursesDisplayTest(DisplayT): self.assertEqual(ret, (display_util.HELP, 1)) - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.menu") + @mock.patch("letsencrypt.display.util.dialog.Dialog.menu") def test_menu_desc_only_cancel(self, mock_menu): mock_menu.return_value = (display_util.CANCEL, "") @@ -106,13 +106,13 @@ class NcursesDisplayTest(DisplayT): self.assertEqual(ret, (display_util.CANCEL, -1)) - @mock.patch("letsencrypt.client.display.util." + @mock.patch("letsencrypt.display.util." "dialog.Dialog.inputbox") def test_input(self, mock_input): self.displayer.input("message") self.assertEqual(mock_input.call_count, 1) - @mock.patch("letsencrypt.client.display.util.dialog.Dialog.yesno") + @mock.patch("letsencrypt.display.util.dialog.Dialog.yesno") def test_yesno(self, mock_yesno): mock_yesno.return_value = display_util.OK @@ -122,7 +122,7 @@ class NcursesDisplayTest(DisplayT): "message", display_util.HEIGHT, display_util.WIDTH, yes_label="Yes", no_label="No") - @mock.patch("letsencrypt.client.display.util." + @mock.patch("letsencrypt.display.util." "dialog.Dialog.checklist") def test_checklist(self, mock_checklist): self.displayer.checklist("message", self.tags) @@ -164,7 +164,7 @@ class FileOutputDisplayTest(DisplayT): self.assertTrue("message" in self.mock_stdout.write.call_args[0][0]) - @mock.patch("letsencrypt.client.display.util." + @mock.patch("letsencrypt.display.util." "FileDisplay._get_valid_int_ans") def test_menu(self, mock_ans): mock_ans.return_value = (display_util.OK, 1) @@ -199,14 +199,14 @@ class FileOutputDisplayTest(DisplayT): with mock.patch("__builtin__.raw_input", return_value="a"): self.assertTrue(self.displayer.yesno("msg", yes_label="Agree")) - @mock.patch("letsencrypt.client.display.util.FileDisplay.input") + @mock.patch("letsencrypt.display.util.FileDisplay.input") def test_checklist_valid(self, mock_input): mock_input.return_value = (display_util.OK, "2 1") code, tag_list = self.displayer.checklist("msg", self.tags) self.assertEqual( (code, set(tag_list)), (display_util.OK, set(["tag1", "tag2"]))) - @mock.patch("letsencrypt.client.display.util.FileDisplay.input") + @mock.patch("letsencrypt.display.util.FileDisplay.input") def test_checklist_miss_valid(self, mock_input): mock_input.side_effect = [ (display_util.OK, "10"), @@ -217,7 +217,7 @@ class FileOutputDisplayTest(DisplayT): ret = self.displayer.checklist("msg", self.tags) self.assertEqual(ret, (display_util.OK, ["tag1"])) - @mock.patch("letsencrypt.client.display.util.FileDisplay.input") + @mock.patch("letsencrypt.display.util.FileDisplay.input") def test_checklist_miss_quit(self, mock_input): mock_input.side_effect = [ (display_util.OK, "10"), @@ -308,7 +308,7 @@ class SeparateListInputTest(unittest.TestCase): @classmethod def _call(cls, input_): - from letsencrypt.client.display.util import separate_list_input + from letsencrypt.display.util import separate_list_input return separate_list_input(input_) def test_commas(self): @@ -334,7 +334,7 @@ class SeparateListInputTest(unittest.TestCase): class PlaceParensTest(unittest.TestCase): @classmethod def _call(cls, label): # pylint: disable=protected-access - from letsencrypt.client.display.util import _parens_around_char + from letsencrypt.display.util import _parens_around_char return _parens_around_char(label) def test_single_letter(self): diff --git a/letsencrypt/client/tests/le_util_test.py b/letsencrypt/tests/le_util_test.py similarity index 90% rename from letsencrypt/client/tests/le_util_test.py rename to letsencrypt/tests/le_util_test.py index 39926a9b5..0d37a780b 100644 --- a/letsencrypt/client/tests/le_util_test.py +++ b/letsencrypt/tests/le_util_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.le_util.""" +"""Tests for letsencrypt.le_util.""" import os import shutil import stat @@ -9,7 +9,7 @@ import mock class MakeOrVerifyDirTest(unittest.TestCase): - """Tests for letsencrypt.client.le_util.make_or_verify_dir. + """Tests for letsencrypt.le_util.make_or_verify_dir. Note that it is not possible to test for a wrong directory owner, as this testing script would have to be run as root. @@ -27,7 +27,7 @@ class MakeOrVerifyDirTest(unittest.TestCase): shutil.rmtree(self.root_path, ignore_errors=True) def _call(self, directory, mode): - from letsencrypt.client.le_util import make_or_verify_dir + from letsencrypt.le_util import make_or_verify_dir return make_or_verify_dir(directory, mode, self.uid) def test_creates_dir_when_missing(self): @@ -50,7 +50,7 @@ class MakeOrVerifyDirTest(unittest.TestCase): class CheckPermissionsTest(unittest.TestCase): - """Tests for letsencrypt.client.le_util.check_permissions. + """Tests for letsencrypt.le_util.check_permissions. Note that it is not possible to test for a wrong file owner, as this testing script would have to be run as root. @@ -65,7 +65,7 @@ class CheckPermissionsTest(unittest.TestCase): os.remove(self.path) def _call(self, mode): - from letsencrypt.client.le_util import check_permissions + from letsencrypt.le_util import check_permissions return check_permissions(self.path, mode, self.uid) def test_ok_mode(self): @@ -88,7 +88,7 @@ class UniqueFileTest(unittest.TestCase): shutil.rmtree(self.root_path, ignore_errors=True) def _call(self, mode=0o600): - from letsencrypt.client.le_util import unique_file + from letsencrypt.le_util import unique_file return unique_file(self.default_name, mode) def test_returns_fd_for_writing(self): diff --git a/letsencrypt/client/tests/log_test.py b/letsencrypt/tests/log_test.py similarity index 94% rename from letsencrypt/client/tests/log_test.py rename to letsencrypt/tests/log_test.py index 49fbdc7c2..e5760184d 100644 --- a/letsencrypt/client/tests/log_test.py +++ b/letsencrypt/tests/log_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.log.""" +"""Tests for letsencrypt.log.""" import logging import unittest @@ -10,7 +10,7 @@ class DialogHandlerTest(unittest.TestCase): def setUp(self): self.d = mock.MagicMock() # pylint: disable=invalid-name - from letsencrypt.client.log import DialogHandler + from letsencrypt.log import DialogHandler self.handler = DialogHandler(height=2, width=6, d=self.d) self.handler.PADDING_HEIGHT = 2 self.handler.PADDING_WIDTH = 4 diff --git a/letsencrypt/client/tests/network2_test.py b/letsencrypt/tests/network2_test.py similarity index 96% rename from letsencrypt/client/tests/network2_test.py rename to letsencrypt/tests/network2_test.py index 1a5f7fd27..434fafad5 100644 --- a/letsencrypt/client/tests/network2_test.py +++ b/letsencrypt/tests/network2_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt.client.network2.""" +"""Tests for letsencrypt.network2.""" import datetime import httplib import os @@ -13,8 +13,8 @@ from acme import challenges from acme import jose from acme import messages2 -from letsencrypt.client import account -from letsencrypt.client import errors +from letsencrypt import account +from letsencrypt import errors CERT = jose.ComparableX509(M2Crypto.X509.load_cert_string( @@ -33,12 +33,12 @@ KEY2 = jose.JWKRSA.load(pkg_resources.resource_string( class NetworkTest(unittest.TestCase): - """Tests for letsencrypt.client.network2.Network.""" + """Tests for letsencrypt.network2.Network.""" # pylint: disable=too-many-instance-attributes,too-many-public-methods def setUp(self): - from letsencrypt.client.network2 import Network + from letsencrypt.network2 import Network self.net = Network( new_reg_uri='https://www.letsencrypt-demo.org/acme/new-reg', key=KEY, alg=jose.RS256) @@ -142,14 +142,14 @@ class NetworkTest(unittest.TestCase): # pylint: disable=protected-access self.net._check_response(self.response) - @mock.patch('letsencrypt.client.network2.requests') + @mock.patch('letsencrypt.network2.requests') def test_get_requests_error_passthrough(self, requests_mock): requests_mock.exceptions = requests.exceptions requests_mock.get.side_effect = requests.exceptions.RequestException # pylint: disable=protected-access self.assertRaises(errors.NetworkError, self.net._get, 'uri') - @mock.patch('letsencrypt.client.network2.requests') + @mock.patch('letsencrypt.network2.requests') def test_get(self, requests_mock): # pylint: disable=protected-access self.net._check_response = mock.MagicMock() @@ -157,14 +157,14 @@ class NetworkTest(unittest.TestCase): self.net._check_response.assert_called_once_with( requests_mock.get('uri'), content_type='ct') - @mock.patch('letsencrypt.client.network2.requests') + @mock.patch('letsencrypt.network2.requests') def test_post_requests_error_passthrough(self, requests_mock): requests_mock.exceptions = requests.exceptions requests_mock.post.side_effect = requests.exceptions.RequestException # pylint: disable=protected-access self.assertRaises(errors.NetworkError, self.net._post, 'uri', 'data') - @mock.patch('letsencrypt.client.network2.requests') + @mock.patch('letsencrypt.network2.requests') def test_post(self, requests_mock): # pylint: disable=protected-access self.net._check_response = mock.MagicMock() @@ -295,7 +295,7 @@ class NetworkTest(unittest.TestCase): datetime.datetime(1999, 12, 31, 23, 59, 59), self.net.retry_after(response=self.response, default=10)) - @mock.patch('letsencrypt.client.network2.datetime') + @mock.patch('letsencrypt.network2.datetime') def test_retry_after_invalid(self, dt_mock): dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) dt_mock.timedelta = datetime.timedelta @@ -305,7 +305,7 @@ class NetworkTest(unittest.TestCase): datetime.datetime(2015, 3, 27, 0, 0, 10), self.net.retry_after(response=self.response, default=10)) - @mock.patch('letsencrypt.client.network2.datetime') + @mock.patch('letsencrypt.network2.datetime') def test_retry_after_seconds(self, dt_mock): dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) dt_mock.timedelta = datetime.timedelta @@ -315,7 +315,7 @@ class NetworkTest(unittest.TestCase): datetime.datetime(2015, 3, 27, 0, 0, 50), self.net.retry_after(response=self.response, default=10)) - @mock.patch('letsencrypt.client.network2.datetime') + @mock.patch('letsencrypt.network2.datetime') def test_retry_after_missing(self, dt_mock): dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) dt_mock.timedelta = datetime.timedelta @@ -353,8 +353,8 @@ class NetworkTest(unittest.TestCase): errors.NetworkError, self.net.request_issuance, CSR, (self.authzr,)) - @mock.patch('letsencrypt.client.network2.datetime') - @mock.patch('letsencrypt.client.network2.time') + @mock.patch('letsencrypt.network2.datetime') + @mock.patch('letsencrypt.network2.time') def test_poll_and_request_issuance(self, time_mock, dt_mock): # clock.dt | pylint: disable=no-member clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27)) diff --git a/letsencrypt/client/tests/recovery_token_test.py b/letsencrypt/tests/recovery_token_test.py similarity index 93% rename from letsencrypt/client/tests/recovery_token_test.py rename to letsencrypt/tests/recovery_token_test.py index c0d692c8c..238aa07a3 100644 --- a/letsencrypt/client/tests/recovery_token_test.py +++ b/letsencrypt/tests/recovery_token_test.py @@ -8,12 +8,12 @@ import mock from acme import challenges -from letsencrypt.client import achallenges +from letsencrypt import achallenges class RecoveryTokenTest(unittest.TestCase): def setUp(self): - from letsencrypt.client.recovery_token import RecoveryToken + from letsencrypt.recovery_token import RecoveryToken server = "demo_server" self.base_dir = tempfile.mkdtemp("tokens") self.token_dir = os.path.join(self.base_dir, server) @@ -61,7 +61,7 @@ class RecoveryTokenTest(unittest.TestCase): self.assertEqual( response, challenges.RecoveryTokenResponse(token="444")) - @mock.patch("letsencrypt.client.recovery_token.zope.component.getUtility") + @mock.patch("letsencrypt.recovery_token.zope.component.getUtility") def test_perform_not_stored(self, mock_input): mock_input().input.side_effect = [(0, "555"), (1, "000")] response = self.rec_token.perform( diff --git a/letsencrypt/client/tests/reverter_test.py b/letsencrypt/tests/reverter_test.py similarity index 93% rename from letsencrypt/client/tests/reverter_test.py rename to letsencrypt/tests/reverter_test.py index 25da75611..402866a37 100644 --- a/letsencrypt/client/tests/reverter_test.py +++ b/letsencrypt/tests/reverter_test.py @@ -1,4 +1,4 @@ -"""Test letsencrypt.client.reverter.""" +"""Test letsencrypt.reverter.""" import logging import os import shutil @@ -7,14 +7,14 @@ import unittest import mock -from letsencrypt.client import errors +from letsencrypt import errors class ReverterCheckpointLocalTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes """Test the Reverter Class.""" def setUp(self): - from letsencrypt.client.reverter import Reverter + from letsencrypt.reverter import Reverter # Disable spurious errors... we are trying to test for them logging.disable(logging.CRITICAL) @@ -48,8 +48,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase): "{0}\n{1}\n".format(self.config1, self.config2)) def test_add_to_checkpoint_copy_failure(self): - with mock.patch("letsencrypt.client.reverter." - "shutil.copy2") as mock_copy2: + with mock.patch("letsencrypt.reverter.shutil.copy2") as mock_copy2: mock_copy2.side_effect = IOError("bad copy") self.assertRaises(errors.LetsEncryptReverterError, self.reverter.add_to_checkpoint, @@ -119,8 +118,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase): def test_register_file_creation_write_error(self): m_open = mock.mock_open() - with mock.patch("letsencrypt.client.reverter.open", - m_open, create=True): + with mock.patch("letsencrypt.reverter.open", m_open, create=True): m_open.side_effect = OSError("bad open") self.assertRaises(errors.LetsEncryptReverterError, self.reverter.register_file_creation, @@ -169,8 +167,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase): def test_recover_checkpoint_copy_failure(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") - with mock.patch("letsencrypt.client.reverter.shutil." - "copy2") as mock_copy2: + with mock.patch("letsencrypt.reverter.shutil.copy2") as mock_copy2: mock_copy2.side_effect = OSError("bad copy") self.assertRaises(errors.LetsEncryptReverterError, self.reverter.revert_temporary_config) @@ -178,20 +175,19 @@ class ReverterCheckpointLocalTest(unittest.TestCase): def test_recover_checkpoint_rm_failure(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "temp save") - with mock.patch("letsencrypt.client.reverter.shutil." - "rmtree") as mock_rmtree: + with mock.patch("letsencrypt.reverter.shutil.rmtree") as mock_rmtree: mock_rmtree.side_effect = OSError("Cannot remove tree") self.assertRaises(errors.LetsEncryptReverterError, self.reverter.revert_temporary_config) - @mock.patch("letsencrypt.client.reverter.logging.warning") + @mock.patch("letsencrypt.reverter.logging.warning") def test_recover_checkpoint_missing_new_files(self, mock_warn): self.reverter.register_file_creation( True, os.path.join(self.dir1, "missing_file.txt")) self.reverter.revert_temporary_config() self.assertEqual(mock_warn.call_count, 1) - @mock.patch("letsencrypt.client.reverter.os.remove") + @mock.patch("letsencrypt.reverter.os.remove") def test_recover_checkpoint_remove_failure(self, mock_remove): self.reverter.register_file_creation(True, self.config1) mock_remove.side_effect = OSError("Can't remove") @@ -236,7 +232,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): # pylint: disable=too-many-instance-attributes """Tests functions having to deal with full checkpoints.""" def setUp(self): - from letsencrypt.client.reverter import Reverter + from letsencrypt.reverter import Reverter # Disable spurious errors... logging.disable(logging.CRITICAL) @@ -298,7 +294,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): # No need to warn for this... just make sure there are no errors. self.reverter.finalize_checkpoint("No checkpoint...") - @mock.patch("letsencrypt.client.reverter.shutil.move") + @mock.patch("letsencrypt.reverter.shutil.move") def test_finalize_checkpoint_cannot_title(self, mock_move): self.reverter.add_to_checkpoint(self.sets[0], "perm save") mock_move.side_effect = OSError("cannot move") @@ -307,7 +303,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): self.reverter.finalize_checkpoint, "Title") - @mock.patch("letsencrypt.client.reverter.os.rename") + @mock.patch("letsencrypt.reverter.os.rename") def test_finalize_checkpoint_no_rename_directory(self, mock_rename): # pylint: disable=invalid-name self.reverter.add_to_checkpoint(self.sets[0], "perm save") @@ -317,7 +313,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): self.reverter.finalize_checkpoint, "Title") - @mock.patch("letsencrypt.client.reverter.logging") + @mock.patch("letsencrypt.reverter.logging") def test_rollback_too_many(self, mock_logging): self.reverter.rollback_checkpoints(1) self.assertEqual(mock_logging.warning.call_count, 1) @@ -330,7 +326,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): self.assertEqual(read_in(self.config2), "directive-dir2") self.assertFalse(os.path.isfile(config3)) - @mock.patch("letsencrypt.client.reverter.zope.component.getUtility") + @mock.patch("letsencrypt.reverter.zope.component.getUtility") def test_view_config_changes(self, mock_output): """This is not strict as this is subject to change.""" self._setup_three_checkpoints() @@ -341,7 +337,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): # Make sure notification is output self.assertEqual(mock_output().notification.call_count, 1) - @mock.patch("letsencrypt.client.reverter.logging") + @mock.patch("letsencrypt.reverter.logging") def test_view_config_changes_no_backups(self, mock_logging): self.reverter.view_config_changes() self.assertTrue(mock_logging.info.call_count > 0) @@ -386,7 +382,7 @@ class TestFullCheckpointsReverter(unittest.TestCase): def setup_work_direc(): """Setup directories. - :returns: Mocked :class:`letsencrypt.client.interfaces.IConfig` + :returns: Mocked :class:`letsencrypt.interfaces.IConfig` """ work_dir = tempfile.mkdtemp("work") diff --git a/letsencrypt/client/tests/revoker_test.py b/letsencrypt/tests/revoker_test.py similarity index 84% rename from letsencrypt/client/tests/revoker_test.py rename to letsencrypt/tests/revoker_test.py index 6810a1115..798f28c50 100644 --- a/letsencrypt/client/tests/revoker_test.py +++ b/letsencrypt/tests/revoker_test.py @@ -1,4 +1,4 @@ -"""Test letsencrypt.client.revoker.""" +"""Test letsencrypt.revoker.""" import csv import os import pkg_resources @@ -8,9 +8,9 @@ import unittest import mock -from letsencrypt.client import errors -from letsencrypt.client import le_util -from letsencrypt.client.display import util as display_util +from letsencrypt import errors +from letsencrypt import le_util +from letsencrypt.display import util as display_util from letsencrypt_apache import configurator @@ -27,7 +27,7 @@ class RevokerBase(unittest.TestCase): # pylint: disable=too-few-public-methods def _store_certs(self): # pylint: disable=protected-access - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker Revoker.store_cert_key(self.paths[0], self.key_path, self.mock_config) Revoker.store_cert_key(self.paths[1], self.key_path, self.mock_config) @@ -51,7 +51,7 @@ class RevokerBase(unittest.TestCase): # pylint: disable=too-few-public-methods class RevokerTest(RevokerBase): def setUp(self): - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker super(RevokerTest, self).setUp() with open(self.key_path) as key_file: @@ -66,9 +66,8 @@ class RevokerTest(RevokerBase): def tearDown(self): shutil.rmtree(self.backup_dir) - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_key_all(self, mock_display, mock_net): mock_display().confirm_revocation.return_value = True @@ -81,7 +80,7 @@ class RevokerTest(RevokerBase): self.assertEqual(mock_net.call_count, 2) - @mock.patch("letsencrypt.client.revoker.Crypto.PublicKey.RSA.importKey") + @mock.patch("letsencrypt.revoker.Crypto.PublicKey.RSA.importKey") def test_revoke_by_invalid_keys(self, mock_import): mock_import.side_effect = ValueError self.assertRaises(errors.LetsEncryptRevokerError, @@ -93,9 +92,8 @@ class RevokerTest(RevokerBase): self.revoker.revoke_from_key, self.key) - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_wrong_key(self, mock_display, mock_net): mock_display().confirm_revocation.return_value = True @@ -110,9 +108,8 @@ class RevokerTest(RevokerBase): # No revocation went through self.assertEqual(mock_net.call_count, 0) - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_cert(self, mock_display, mock_net): mock_display().confirm_revocation.return_value = True @@ -128,9 +125,8 @@ class RevokerTest(RevokerBase): self.assertEqual(mock_net.call_count, 1) - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_cert_not_found(self, mock_display, mock_net): mock_display().confirm_revocation.return_value = True @@ -148,9 +144,8 @@ class RevokerTest(RevokerBase): self.assertEqual(mock_net.call_count, 1) - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_menu(self, mock_display, mock_net): mock_display().confirm_revocation.return_value = True mock_display.display_certs.side_effect = [ @@ -172,10 +167,9 @@ class RevokerTest(RevokerBase): self.assertEqual(mock_net.call_count, 1) self.assertEqual(mock_display.more_info_cert.call_count, 1) - @mock.patch("letsencrypt.client.revoker.logging") - @mock.patch("letsencrypt.client.revoker.network." - "Network.send_and_receive_expected") - @mock.patch("letsencrypt.client.revoker.revocation") + @mock.patch("letsencrypt.revoker.logging") + @mock.patch("letsencrypt.revoker.network.Network.send_and_receive_expected") + @mock.patch("letsencrypt.revoker.revocation") def test_revoke_by_menu_delete_all(self, mock_display, mock_net, mock_log): mock_display().confirm_revocation.return_value = True mock_display.display_certs.return_value = (display_util.OK, 0) @@ -192,9 +186,9 @@ class RevokerTest(RevokerBase): # Info is called when there aren't any certs left... self.assertTrue(mock_log.info.called) - @mock.patch("letsencrypt.client.revoker.revocation") - @mock.patch("letsencrypt.client.revoker.Revoker._acme_revoke") - @mock.patch("letsencrypt.client.revoker.logging") + @mock.patch("letsencrypt.revoker.revocation") + @mock.patch("letsencrypt.revoker.Revoker._acme_revoke") + @mock.patch("letsencrypt.revoker.logging") def test_safe_revoke_acme_fail(self, mock_log, mock_revoke, mock_display): # pylint: disable=protected-access mock_revoke.side_effect = errors.LetsEncryptClientError @@ -203,7 +197,7 @@ class RevokerTest(RevokerBase): self.revoker._safe_revoke(self.certs) self.assertTrue(mock_log.error.called) - @mock.patch("letsencrypt.client.revoker.Crypto.PublicKey.RSA.importKey") + @mock.patch("letsencrypt.revoker.Crypto.PublicKey.RSA.importKey") def test_acme_revoke_failure(self, mock_crypto): # pylint: disable=protected-access mock_crypto.side_effect = ValueError @@ -213,7 +207,7 @@ class RevokerTest(RevokerBase): def test_remove_certs_from_list_bad_certs(self): # pylint: disable=protected-access - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert new_cert = Cert(self.paths[0]) @@ -252,7 +246,7 @@ class RevokerInstallerTest(RevokerBase): self._store_certs() def _get_revoker(self, installer): - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker return Revoker(installer, self.mock_config) def test_no_installer_get_installed_locations(self): @@ -274,7 +268,7 @@ class RevokerInstallerTest(RevokerBase): self.assertEqual( sha_vh[cert.get_fingerprint()], self.installs[i]) - @mock.patch("letsencrypt.client.revoker.M2Crypto.X509.load_cert") + @mock.patch("letsencrypt.revoker.M2Crypto.X509.load_cert") def test_get_installed_load_failure(self, mock_m2): mock_installer = mock.MagicMock() mock_installer.get_all_certs_keys.return_value = self.certs_keys @@ -296,11 +290,11 @@ class RevokerClassMethodsTest(RevokerBase): shutil.rmtree(self.backup_dir) def _call(self, cert_path, key_path): - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker Revoker.store_cert_key(cert_path, key_path, self.mock_config) def test_store_two(self): - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker self._call(self.paths[0], self.key_path) self._call(self.paths[1], self.key_path) @@ -318,7 +312,7 @@ class RevokerClassMethodsTest(RevokerBase): self.assertEqual(len(rows), 2) def test_store_one_mixed(self): - from letsencrypt.client.revoker import Revoker + from letsencrypt.revoker import Revoker self._write_rows( [["5", "blank", "blank"], ["18", "dc", "dc"], ["21", "b", "b"]]) self._call(self.paths[0], self.key_path) @@ -338,14 +332,14 @@ class CertTest(unittest.TestCase): self.paths, self.certs, self.key_path = create_revoker_certs() def test_failed_load(self): - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert self.assertRaises(errors.LetsEncryptRevokerError, Cert, self.key_path) def test_no_row(self): self.assertEqual(self.certs[0].get_row(), None) def test_meta_moved_files(self): - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert fake_path = "/not/a/real/path/r72d3t6" self.certs[0].add_meta( 0, fake_path, fake_path, self.paths[0], self.key_path) @@ -354,7 +348,7 @@ class CertTest(unittest.TestCase): self.assertEqual(self.certs[0].orig_key.status, Cert.DELETED_MSG) def test_meta_changed_files(self): - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert self.certs[0].add_meta( 0, self.paths[1], self.paths[1], self.paths[0], self.key_path) @@ -385,9 +379,9 @@ class CertTest(unittest.TestCase): def create_revoker_certs(): """Create a few revoker.Cert objects.""" - from letsencrypt.client.revoker import Cert + from letsencrypt.revoker import Cert - base_package = "letsencrypt.client.tests" + base_package = "letsencrypt.tests" cert0_path = pkg_resources.resource_filename( base_package, os.path.join("testdata", "cert.pem")) diff --git a/letsencrypt/client/tests/testdata/cert-san.pem b/letsencrypt/tests/testdata/cert-san.pem similarity index 100% rename from letsencrypt/client/tests/testdata/cert-san.pem rename to letsencrypt/tests/testdata/cert-san.pem diff --git a/letsencrypt/client/tests/testdata/cert.b64jose b/letsencrypt/tests/testdata/cert.b64jose similarity index 100% rename from letsencrypt/client/tests/testdata/cert.b64jose rename to letsencrypt/tests/testdata/cert.b64jose diff --git a/letsencrypt/client/tests/testdata/cert.pem b/letsencrypt/tests/testdata/cert.pem similarity index 100% rename from letsencrypt/client/tests/testdata/cert.pem rename to letsencrypt/tests/testdata/cert.pem diff --git a/letsencrypt/client/tests/testdata/csr-san.der b/letsencrypt/tests/testdata/csr-san.der similarity index 100% rename from letsencrypt/client/tests/testdata/csr-san.der rename to letsencrypt/tests/testdata/csr-san.der diff --git a/letsencrypt/client/tests/testdata/csr-san.pem b/letsencrypt/tests/testdata/csr-san.pem similarity index 100% rename from letsencrypt/client/tests/testdata/csr-san.pem rename to letsencrypt/tests/testdata/csr-san.pem diff --git a/letsencrypt/client/tests/testdata/csr.der b/letsencrypt/tests/testdata/csr.der similarity index 100% rename from letsencrypt/client/tests/testdata/csr.der rename to letsencrypt/tests/testdata/csr.der diff --git a/letsencrypt/client/tests/testdata/csr.pem b/letsencrypt/tests/testdata/csr.pem similarity index 100% rename from letsencrypt/client/tests/testdata/csr.pem rename to letsencrypt/tests/testdata/csr.pem diff --git a/letsencrypt/client/tests/testdata/rsa512_key.pem b/letsencrypt/tests/testdata/rsa512_key.pem similarity index 100% rename from letsencrypt/client/tests/testdata/rsa512_key.pem rename to letsencrypt/tests/testdata/rsa512_key.pem diff --git a/letsencrypt_apache/configurator.py b/letsencrypt_apache/configurator.py index a7c7b68dc..102718e13 100644 --- a/letsencrypt_apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -11,12 +11,12 @@ import zope.interface from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import augeas_configurator -from letsencrypt.client import constants as core_constants -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import augeas_configurator +from letsencrypt import constants as core_constants +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util from letsencrypt_apache import constants from letsencrypt_apache import dvsni @@ -66,7 +66,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): needs of clients are clarified with the new and developing protocol. :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` :ivar parser: Handles low level parsing :type parser: :class:`~letsencrypt_apache.parser` @@ -547,9 +547,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~letsencrypt.client.constants.ENHANCEMENTS` + :const:`~letsencrypt.constants.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~letsencrypt.client.constants.ENHANCEMENTS` + See :const:`~letsencrypt.constants.ENHANCEMENTS` documentation for appropriate parameter. """ diff --git a/letsencrypt_apache/dvsni.py b/letsencrypt_apache/dvsni.py index 790c4e025..ed7a216bb 100644 --- a/letsencrypt_apache/dvsni.py +++ b/letsencrypt_apache/dvsni.py @@ -11,7 +11,7 @@ class ApacheDvsni(object): :ivar configurator: ApacheConfigurator object :type configurator: :class:`~apache.configurator.ApacheConfigurator` - :ivar list achalls: Annotated :class:`~letsencrypt.client.achallenges.DVSNI` + :ivar list achalls: Annotated :class:`~letsencrypt.achallenges.DVSNI` challenges. :param list indices: Meant to hold indices of challenges in a @@ -54,7 +54,7 @@ class ApacheDvsni(object): """Add challenge to DVSNI object to perform at once. :param achall: Annotated DVSNI challenge. - :type achall: :class:`letsencrypt.client.achallenges.DVSNI` + :type achall: :class:`letsencrypt.achallenges.DVSNI` :param int idx: index to challenge in a larger array @@ -128,7 +128,7 @@ class ApacheDvsni(object): Result: Apache config includes virtual servers for issued challs :param list ll_addrs: list of list of - :class:`letsencrypt.client.plugins.apache.obj.Addr` to apply + :class:`letsencrypt.plugins.apache.obj.Addr` to apply """ # TODO: Use ip address of existing vhost instead of relying on FQDN @@ -164,7 +164,7 @@ class ApacheDvsni(object): """Chocolate virtual server configuration text :param achall: Annotated DVSNI challenge. - :type achall: :class:`letsencrypt.client.achallenges.DVSNI` + :type achall: :class:`letsencrypt.achallenges.DVSNI` :param list ip_addrs: addresses of challenged domain :class:`list` of type :class:`~apache.obj.Addr` @@ -191,7 +191,7 @@ class ApacheDvsni(object): """Returns standardized name for challenge certificate. :param achall: Annotated DVSNI challenge. - :type achall: :class:`letsencrypt.client.achallenges.DVSNI` + :type achall: :class:`letsencrypt.achallenges.DVSNI` :returns: certificate file name :rtype: str diff --git a/letsencrypt_apache/parser.py b/letsencrypt_apache/parser.py index b713c8f6a..9e6e9efe6 100644 --- a/letsencrypt_apache/parser.py +++ b/letsencrypt_apache/parser.py @@ -2,7 +2,7 @@ import os import re -from letsencrypt.client import errors +from letsencrypt import errors class ApacheParser(object): diff --git a/letsencrypt_apache/tests/configurator_test.py b/letsencrypt_apache/tests/configurator_test.py index d7e6b95cf..5cfa22842 100644 --- a/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt_apache/tests/configurator_test.py @@ -8,11 +8,11 @@ import mock from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import errors -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import errors +from letsencrypt import le_util -from letsencrypt.client.tests import acme_util +from letsencrypt.tests import acme_util from letsencrypt_apache import configurator from letsencrypt_apache import obj diff --git a/letsencrypt_apache/tests/dvsni_test.py b/letsencrypt_apache/tests/dvsni_test.py index 323bf8c31..958918529 100644 --- a/letsencrypt_apache/tests/dvsni_test.py +++ b/letsencrypt_apache/tests/dvsni_test.py @@ -7,10 +7,10 @@ import mock from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import le_util -from letsencrypt.client.tests import acme_util +from letsencrypt.tests import acme_util from letsencrypt_apache import obj from letsencrypt_apache.tests import util diff --git a/letsencrypt_apache/tests/parser_test.py b/letsencrypt_apache/tests/parser_test.py index b8fbb5dea..17a34cad8 100644 --- a/letsencrypt_apache/tests/parser_test.py +++ b/letsencrypt_apache/tests/parser_test.py @@ -8,8 +8,8 @@ import augeas import mock import zope.component -from letsencrypt.client import errors -from letsencrypt.client.display import util as display_util +from letsencrypt import errors +from letsencrypt.display import util as display_util from letsencrypt_apache.tests import util diff --git a/letsencrypt_nginx/configurator.py b/letsencrypt_nginx/configurator.py index e1d3bd367..e13cc555a 100644 --- a/letsencrypt_nginx/configurator.py +++ b/letsencrypt_nginx/configurator.py @@ -11,14 +11,14 @@ import zope.interface from acme import challenges -from letsencrypt.client import achallenges -from letsencrypt.client import constants as core_constants -from letsencrypt.client import errors -from letsencrypt.client import interfaces -from letsencrypt.client import le_util -from letsencrypt.client import reverter +from letsencrypt import achallenges +from letsencrypt import constants as core_constants +from letsencrypt import errors +from letsencrypt import interfaces +from letsencrypt import le_util +from letsencrypt import reverter -from letsencrypt.client.plugins import common +from letsencrypt.plugins import common from letsencrypt_nginx import constants from letsencrypt_nginx import dvsni @@ -35,7 +35,7 @@ class NginxConfigurator(common.Plugin): config files modified by the configurator will lose all their comments. :ivar config: Configuration. - :type config: :class:`~letsencrypt.client.interfaces.IConfig` + :type config: :class:`~letsencrypt.interfaces.IConfig` :ivar parser: Handles low level parsing :type parser: :class:`~letsencrypt_nginx.parser` @@ -43,7 +43,7 @@ class NginxConfigurator(common.Plugin): :ivar str save_notes: Human-readable config change notes :ivar reverter: saves and reverts checkpoints - :type reverter: :class:`letsencrypt.client.reverter.Reverter` + :type reverter: :class:`letsencrypt.reverter.Reverter` :ivar tup version: version of Nginx @@ -302,9 +302,9 @@ class NginxConfigurator(common.Plugin): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~letsencrypt.client.constants.ENHANCEMENTS` + :const:`~letsencrypt.constants.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~letsencrypt.client.constants.ENHANCEMENTS` + See :const:`~letsencrypt.constants.ENHANCEMENTS` documentation for appropriate parameter. """ diff --git a/letsencrypt_nginx/dvsni.py b/letsencrypt_nginx/dvsni.py index c845db916..7ea69e052 100644 --- a/letsencrypt_nginx/dvsni.py +++ b/letsencrypt_nginx/dvsni.py @@ -13,7 +13,7 @@ class NginxDvsni(ApacheDvsni): :ivar configurator: NginxConfigurator object :type configurator: :class:`~nginx.configurator.NginxConfigurator` - :ivar list achalls: Annotated :class:`~letsencrypt.client.achallenges.DVSNI` + :ivar list achalls: Annotated :class:`~letsencrypt.achallenges.DVSNI` challenges. :param list indices: Meant to hold indices of challenges in a diff --git a/letsencrypt_nginx/parser.py b/letsencrypt_nginx/parser.py index 7e11fe0c3..22e062ee0 100644 --- a/letsencrypt_nginx/parser.py +++ b/letsencrypt_nginx/parser.py @@ -5,7 +5,7 @@ import os import pyparsing import re -from letsencrypt.client import errors +from letsencrypt import errors from letsencrypt_nginx import obj from letsencrypt_nginx import nginxparser diff --git a/letsencrypt_nginx/tests/configurator_test.py b/letsencrypt_nginx/tests/configurator_test.py index 12101949f..86fe55499 100644 --- a/letsencrypt_nginx/tests/configurator_test.py +++ b/letsencrypt_nginx/tests/configurator_test.py @@ -7,9 +7,9 @@ import mock from acme import challenges from acme import messages2 -from letsencrypt.client import achallenges -from letsencrypt.client import errors -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import errors +from letsencrypt import le_util from letsencrypt_nginx.tests import util diff --git a/letsencrypt_nginx/tests/dvsni_test.py b/letsencrypt_nginx/tests/dvsni_test.py index 94708e282..a13638069 100644 --- a/letsencrypt_nginx/tests/dvsni_test.py +++ b/letsencrypt_nginx/tests/dvsni_test.py @@ -8,8 +8,8 @@ import mock from acme import challenges from acme import messages2 -from letsencrypt.client import achallenges -from letsencrypt.client import le_util +from letsencrypt import achallenges +from letsencrypt import le_util from letsencrypt_nginx.tests import util diff --git a/letsencrypt_nginx/tests/parser_test.py b/letsencrypt_nginx/tests/parser_test.py index 47b44e671..2770320a1 100644 --- a/letsencrypt_nginx/tests/parser_test.py +++ b/letsencrypt_nginx/tests/parser_test.py @@ -5,7 +5,7 @@ import re import shutil import unittest -from letsencrypt.client.errors import LetsEncryptMisconfigurationError +from letsencrypt.errors import LetsEncryptMisconfigurationError from letsencrypt_nginx import nginxparser from letsencrypt_nginx import obj diff --git a/letsencrypt_nginx/tests/util.py b/letsencrypt_nginx/tests/util.py index 57d11c188..5faa3da18 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -1,4 +1,4 @@ -"""Common utilities for letsencrypt.client.nginx.""" +"""Common utilities for letsencrypt_nginx.""" import os import pkg_resources import shutil diff --git a/setup.py b/setup.py index 1c65ff880..8478ab132 100644 --- a/setup.py +++ b/setup.py @@ -111,11 +111,11 @@ setup( entry_points={ 'console_scripts': [ - 'letsencrypt = letsencrypt.client.cli:main', + 'letsencrypt = letsencrypt.cli:main', 'jws = letsencrypt.acme.jose.jws:CLI.run', ], 'letsencrypt.plugins': [ - 'standalone = letsencrypt.client.plugins.standalone.authenticator' + 'standalone = letsencrypt.plugins.standalone.authenticator' ':StandaloneAuthenticator', # to be moved to separate pypi packages From b103aae808ffcbad858faa2093d38912b2dea732 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 12:52:40 +0000 Subject: [PATCH 65/76] tox: split test/cover per pkg --- setup.py | 4 +++- tox.ini | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 8478ab132..c719bea60 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,9 @@ setup( }, tests_require=install_requires, - test_suite='letsencrypt', # TODO: test acme (this is run by tox.ini) + # to test all packages run "python setup.py test -s + # {acme,letsencrypt_apache,letsencrypt_nginx}" + test_suite='letsencrypt', entry_points={ 'console_scripts': [ diff --git a/tox.ini b/tox.ini index add0a15e7..11aadf69b 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,11 @@ envlist = py26,py27,cover,lint [testenv] commands = pip install -r requirements.txt -e .[testing] - python setup.py test -q # -q does not suppress errors - # TODO: test_suite is set to letsencrypt only + # -q does not suppress errors + python setup.py test -q + python setup.py test -q -s acme + python setup.py test -q -s letsencrypt_apache + python setup.py test -q -s letsencrypt_nginx setenv = PYTHONPATH = {toxinidir} @@ -20,7 +23,10 @@ setenv = basepython = python2.7 commands = pip install -e .[testing] - python setup.py nosetests --with-coverage --cover-min-percentage=89 + nosetests --with-coverage --cover-min-percentage=68 letsencrypt + nosetests --with-coverage --cover-min-percentage=100 acme + nosetests --with-coverage --cover-min-percentage=57 letsencrypt_apache + nosetests --with-coverage --cover-min-percentage=58 letsencrypt_nginx [testenv:lint] # recent versions of pylint do not support Python 2.6 (#97, #187) From 6f3b03db777c64bbb83dadb641d02bb491fa6935 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 13:03:48 +0000 Subject: [PATCH 66/76] Fix doumentation for bundled packages --- docs/index.rst | 4 +--- docs/pkgs.rst | 14 ++++++++++++++ docs/{ => pkgs}/acme/index.rst | 0 docs/{ => pkgs}/acme/jose.rst | 0 docs/{ => pkgs}/letsencrypt_apache.rst | 0 docs/{ => pkgs}/letsencrypt_nginx.rst | 0 6 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 docs/pkgs.rst rename docs/{ => pkgs}/acme/index.rst (100%) rename docs/{ => pkgs}/acme/jose.rst (100%) rename docs/{ => pkgs}/letsencrypt_apache.rst (100%) rename docs/{ => pkgs}/letsencrypt_nginx.rst (100%) diff --git a/docs/index.rst b/docs/index.rst index 6bd5c06c9..b076b45c6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,14 +8,12 @@ Welcome to the Let's Encrypt client documentation! using contributing plugins - acme - letsencrypt_apache - letsencrypt_nginx .. toctree:: :maxdepth: 1 api + pkgs Indices and tables diff --git a/docs/pkgs.rst b/docs/pkgs.rst new file mode 100644 index 000000000..8119ffc7e --- /dev/null +++ b/docs/pkgs.rst @@ -0,0 +1,14 @@ +======== +Packages +======== + +.. note:: It is planned to distribute `acme` and plugins separately as + described in `#358`_. For the time being those packages are bundled + together into a single repo, and single documentation. + +.. _`#358`: https://github.com/letsencrypt/lets-encrypt-preview/issues/358 + +.. toctree:: + :glob: + + pkgs/** diff --git a/docs/acme/index.rst b/docs/pkgs/acme/index.rst similarity index 100% rename from docs/acme/index.rst rename to docs/pkgs/acme/index.rst diff --git a/docs/acme/jose.rst b/docs/pkgs/acme/jose.rst similarity index 100% rename from docs/acme/jose.rst rename to docs/pkgs/acme/jose.rst diff --git a/docs/letsencrypt_apache.rst b/docs/pkgs/letsencrypt_apache.rst similarity index 100% rename from docs/letsencrypt_apache.rst rename to docs/pkgs/letsencrypt_apache.rst diff --git a/docs/letsencrypt_nginx.rst b/docs/pkgs/letsencrypt_nginx.rst similarity index 100% rename from docs/letsencrypt_nginx.rst rename to docs/pkgs/letsencrypt_nginx.rst From 0604a393f0ff84147a253b69348b0d3ee243eb59 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 13:11:51 +0000 Subject: [PATCH 67/76] examples: update restified --- examples/restified.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/restified.py b/examples/restified.py index b7b0257e2..c0252c1eb 100644 --- a/examples/restified.py +++ b/examples/restified.py @@ -4,8 +4,8 @@ import pkg_resources import M2Crypto -from letsencrypt.acme import messages2 -from letsencrypt.acme import jose +from acme import messages2 +from acme import jose from letsencrypt import network2 @@ -16,7 +16,7 @@ logger.setLevel(logging.DEBUG) NEW_REG_URL = 'https://www.letsencrypt-demo.org/acme/new-reg' key = jose.JWKRSA.load(pkg_resources.resource_string( - 'letsencrypt.acme.jose', os.path.join('testdata', 'rsa512_key.pem'))) + 'acme.jose', os.path.join('testdata', 'rsa512_key.pem'))) net = network2.Network(NEW_REG_URL, key) regr = net.register(contact=( @@ -29,7 +29,7 @@ logging.debug(regr) authzr = net.request_challenges( identifier=messages2.Identifier( typ=messages2.IDENTIFIER_FQDN, value='example1.com'), - regr=regr) + new_authzr_uri=regr.new_authzr_uri) logging.debug(authzr) authzr, authzr_response = net.poll(authzr) From 771ddf0aaf007bb9fb967ec46cdca1cf39d1cc9f Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 14:53:59 +0000 Subject: [PATCH 68/76] Update docs for the new CLI --- README.rst | 10 +++++++--- docs/using.rst | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index fac36dbd7..4f170f11b 100644 --- a/README.rst +++ b/README.rst @@ -17,11 +17,15 @@ It's all automated: * If domain control has been proven, a certificate will get issued and the tool will automatically install it. -All you need to do is: +All you need to do is:: -:: + user@www:~$ sudo letsencrypt -d www.example.org auth - user@www:~$ sudo letsencrypt -d www.example.org +and if you have a compatbile web server (Apache), Let's Encrypt can +not only get a new certificate, but also deploy it and configure your +server automatically!:: + + user@www:~$ sudo letsencrypt -d www.example.org run **Encrypt ALL the things!** diff --git a/docs/using.rst b/docs/using.rst index f10966602..daa2425ea 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -54,7 +54,6 @@ Installation virtualenv --no-site-packages -p python2 venv ./venv/bin/pip install -r requirements.txt - sudo ./venv/bin/letsencrypt Usage From a5e927c657a127a4079c17470a5b3f211c8460fb Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 14:56:44 +0000 Subject: [PATCH 69/76] name_with_description -> description_with_name --- letsencrypt/client/display/ops.py | 2 +- letsencrypt/client/plugins/disco.py | 6 +++--- letsencrypt/client/plugins/disco_test.py | 7 ++++--- letsencrypt/client/tests/display/ops_test.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/letsencrypt/client/display/ops.py b/letsencrypt/client/display/ops.py index dc6992c8c..706e8bd7c 100644 --- a/letsencrypt/client/display/ops.py +++ b/letsencrypt/client/display/ops.py @@ -22,7 +22,7 @@ def choose_plugin(prepared, question): :rtype: `~.PluginEntryPoint` """ - opts = [plugin_ep.name_with_description + opts = [plugin_ep.description_with_name + (" [Misconfigured]" if plugin_ep.misconfigured else "") for plugin_ep in prepared] diff --git a/letsencrypt/client/plugins/disco.py b/letsencrypt/client/plugins/disco.py index 50e0bce50..6ab110a20 100644 --- a/letsencrypt/client/plugins/disco.py +++ b/letsencrypt/client/plugins/disco.py @@ -39,9 +39,9 @@ class PluginEntryPoint(object): return self.plugin_cls.description @property - def name_with_description(self): - """Name with description. Handy for UI.""" - return "{0} ({1})".format(self.name, self.description) + def description_with_name(self): + """Description with name. Handy for UI.""" + return "{0} ({1})".format(self.description, self.name) def ifaces(self, *ifaces_groups): """Does plugin implements specified interface groups?""" diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index f5ea9e6ee..88aa1275c 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -52,9 +52,10 @@ class PluginEntryPointTest(unittest.TestCase): def test_description(self): self.assertEqual("Standalone Authenticator", self.plugin_ep.description) - def test_name_with_description(self): - self.assertTrue( - self.plugin_ep.name_with_description.startswith("sa (")) + def test_description_with_name(self): + self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc") + self.assertEqual( + "Desc (sa)", self.plugin_ep.description_with_name) def test_ifaces(self): self.assertTrue(self.plugin_ep.ifaces((interfaces.IAuthenticator,))) diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/client/tests/display/ops_test.py index 7c5c1f74f..4716a5b11 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/client/tests/display/ops_test.py @@ -20,9 +20,9 @@ class ChoosePluginTest(unittest.TestCase): def setUp(self): zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) self.mock_apache = mock.Mock( - name_with_description="a", misconfigured=True) + description_with_name="a", misconfigured=True) self.mock_stand = mock.Mock( - name_with_description="s", misconfigured=False) + description_with_name="s", misconfigured=False) self.mock_stand.init().more_info.return_value = "standalone" self.plugins = [ self.mock_apache, From 5fbc5cee2e2ee3bbaa131d29391c9e61ea8465a2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 16:52:32 +0000 Subject: [PATCH 70/76] Add tox.cover.sh for proper coveralls experience. --- tox.cover.sh | 21 +++++++++++++++++++++ tox.ini | 5 +---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100755 tox.cover.sh diff --git a/tox.cover.sh b/tox.cover.sh new file mode 100755 index 000000000..2cc2484ed --- /dev/null +++ b/tox.cover.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# This script is used by tox.ini (and thus Travis CI) in order to +# generate separate stats for each package. It should be removed once +# those packages are moved to separate repo. + +cover () { + # "-c /dev/null" makes sure setup.cfg is not loaded (multiple + # --with-cover add up, --cover-erase must not be set for coveralls + # to get all the data); --with-cover scopes coverage to only + # specific package, positional argument scopes tests only to + # specific package directory + nosetests -c /dev/null --with-cover --cover-package "$1" \ + --cover-min-percentage="$2" "$1" +} + +# don't use sequential composition (;), if letsencrypt_nginx returns +# 0, coveralls submit will be triggered (c.f. .travis.yml, +# after_success) +cover letsencrypt 88 && cover acme 100 && \ + cover letsencrypt_apache 67 && cover letsencrypt_nginx 90 diff --git a/tox.ini b/tox.ini index 11aadf69b..0367b5498 100644 --- a/tox.ini +++ b/tox.ini @@ -23,10 +23,7 @@ setenv = basepython = python2.7 commands = pip install -e .[testing] - nosetests --with-coverage --cover-min-percentage=68 letsencrypt - nosetests --with-coverage --cover-min-percentage=100 acme - nosetests --with-coverage --cover-min-percentage=57 letsencrypt_apache - nosetests --with-coverage --cover-min-percentage=58 letsencrypt_nginx + ./tox.cover.sh [testenv:lint] # recent versions of pylint do not support Python 2.6 (#97, #187) From 99b2003e6172995ae6e8abf2441acd16bf0f44db Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 17:34:25 +0000 Subject: [PATCH 71/76] Cover tests (fixes #402). 1. --cover-tests, to make sure every test is run, helped to find broken determine_account tests 2. fix determine_account test 3. unittest.main() # pragma: no cover 4. bump coverage coveralls.io always reported higher coverage, because it also looked at test files. --- letsencrypt/acme/challenges_test.py | 2 +- letsencrypt/acme/fields_test.py | 4 ++++ letsencrypt/acme/jose/b64_test.py | 2 +- letsencrypt/acme/jose/errors_test.py | 2 +- letsencrypt/acme/jose/interfaces_test.py | 2 +- letsencrypt/acme/jose/json_util_test.py | 2 +- letsencrypt/acme/jose/jwa_test.py | 2 +- letsencrypt/acme/jose/jwk_test.py | 2 +- letsencrypt/acme/jose/jws_test.py | 2 +- letsencrypt/acme/jose/util_test.py | 2 +- letsencrypt/acme/messages2_test.py | 2 +- letsencrypt/acme/messages_test.py | 2 +- letsencrypt/acme/other_test.py | 2 +- .../client/plugins/apache/tests/configurator_test.py | 2 +- letsencrypt/client/plugins/apache/tests/dvsni_test.py | 2 +- letsencrypt/client/plugins/apache/tests/obj_test.py | 2 +- letsencrypt/client/plugins/apache/tests/parser_test.py | 2 +- letsencrypt/client/plugins/common_test.py | 2 +- letsencrypt/client/plugins/disco_test.py | 2 +- letsencrypt/client/plugins/nginx/tests/configurator_test.py | 2 +- letsencrypt/client/plugins/nginx/tests/dvsni_test.py | 2 +- letsencrypt/client/plugins/nginx/tests/nginxparser_test.py | 2 +- letsencrypt/client/plugins/nginx/tests/obj_test.py | 2 +- letsencrypt/client/plugins/nginx/tests/parser_test.py | 2 +- .../client/plugins/standalone/tests/authenticator_test.py | 2 +- letsencrypt/client/tests/account_test.py | 2 +- letsencrypt/client/tests/achallenges_test.py | 2 +- letsencrypt/client/tests/auth_handler_test.py | 2 +- letsencrypt/client/tests/cli_test.py | 2 +- letsencrypt/client/tests/client_test.py | 6 +++--- letsencrypt/client/tests/configuration_test.py | 2 +- letsencrypt/client/tests/continuity_auth_test.py | 2 +- letsencrypt/client/tests/crypto_util_test.py | 2 +- letsencrypt/client/tests/display/enhancements_test.py | 2 +- letsencrypt/client/tests/display/ops_test.py | 2 +- letsencrypt/client/tests/display/revocation_test.py | 2 +- letsencrypt/client/tests/display/util_test.py | 2 +- letsencrypt/client/tests/le_util_test.py | 2 +- letsencrypt/client/tests/log_test.py | 2 +- letsencrypt/client/tests/network2_test.py | 2 +- letsencrypt/client/tests/recovery_token_test.py | 2 +- letsencrypt/client/tests/reverter_test.py | 2 +- letsencrypt/client/tests/revoker_test.py | 2 +- setup.cfg | 1 + tox.ini | 3 ++- 45 files changed, 51 insertions(+), 45 deletions(-) diff --git a/letsencrypt/acme/challenges_test.py b/letsencrypt/acme/challenges_test.py index 9ca9f6dd8..69d6edff3 100644 --- a/letsencrypt/acme/challenges_test.py +++ b/letsencrypt/acme/challenges_test.py @@ -455,4 +455,4 @@ class DNSResponseTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/fields_test.py b/letsencrypt/acme/fields_test.py index 204849408..ca2464322 100644 --- a/letsencrypt/acme/fields_test.py +++ b/letsencrypt/acme/fields_test.py @@ -33,3 +33,7 @@ class RFC3339FieldTest(unittest.TestCase): from letsencrypt.acme.fields import RFC3339Field self.assertRaises( jose.DeserializationError, RFC3339Field.default_decoder, '') + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/b64_test.py b/letsencrypt/acme/jose/b64_test.py index 89ff27f5d..d43417ada 100644 --- a/letsencrypt/acme/jose/b64_test.py +++ b/letsencrypt/acme/jose/b64_test.py @@ -69,4 +69,4 @@ class B64DecodeTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/errors_test.py b/letsencrypt/acme/jose/errors_test.py index dd6af6c1a..ec9413ffc 100644 --- a/letsencrypt/acme/jose/errors_test.py +++ b/letsencrypt/acme/jose/errors_test.py @@ -14,4 +14,4 @@ class UnrecognizedTypeErrorTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/interfaces_test.py b/letsencrypt/acme/jose/interfaces_test.py index 4c0fc6eb9..ffec714db 100644 --- a/letsencrypt/acme/jose/interfaces_test.py +++ b/letsencrypt/acme/jose/interfaces_test.py @@ -112,4 +112,4 @@ class JSONDeSerializableTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/json_util_test.py b/letsencrypt/acme/jose/json_util_test.py index 88818ed07..49c8758be 100644 --- a/letsencrypt/acme/jose/json_util_test.py +++ b/letsencrypt/acme/jose/json_util_test.py @@ -294,4 +294,4 @@ class TypedJSONObjectWithFieldsTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/jwa_test.py b/letsencrypt/acme/jose/jwa_test.py index 48fdfce0d..737cffda4 100644 --- a/letsencrypt/acme/jose/jwa_test.py +++ b/letsencrypt/acme/jose/jwa_test.py @@ -102,4 +102,4 @@ class JWARSTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/jwk_test.py b/letsencrypt/acme/jose/jwk_test.py index 1328528e8..0958560c5 100644 --- a/letsencrypt/acme/jose/jwk_test.py +++ b/letsencrypt/acme/jose/jwk_test.py @@ -104,4 +104,4 @@ class JWKRSATest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/jws_test.py b/letsencrypt/acme/jose/jws_test.py index dca61c3d9..1dedc5672 100644 --- a/letsencrypt/acme/jose/jws_test.py +++ b/letsencrypt/acme/jose/jws_test.py @@ -238,4 +238,4 @@ class CLITest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/jose/util_test.py b/letsencrypt/acme/jose/util_test.py index fc75497e0..efedb23ba 100644 --- a/letsencrypt/acme/jose/util_test.py +++ b/letsencrypt/acme/jose/util_test.py @@ -137,4 +137,4 @@ class frozendictTest(unittest.TestCase): # pylint: disable=invalid-name if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/messages2_test.py b/letsencrypt/acme/messages2_test.py index 9e8ef33c8..bf4737443 100644 --- a/letsencrypt/acme/messages2_test.py +++ b/letsencrypt/acme/messages2_test.py @@ -246,4 +246,4 @@ class RevocationTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/messages_test.py b/letsencrypt/acme/messages_test.py index 56781db18..6554dea6c 100644 --- a/letsencrypt/acme/messages_test.py +++ b/letsencrypt/acme/messages_test.py @@ -477,4 +477,4 @@ class StatusRequestTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/acme/other_test.py b/letsencrypt/acme/other_test.py index eefcb2fc5..13a6826fe 100644 --- a/letsencrypt/acme/other_test.py +++ b/letsencrypt/acme/other_test.py @@ -96,4 +96,4 @@ class SignatureTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/apache/tests/configurator_test.py b/letsencrypt/client/plugins/apache/tests/configurator_test.py index ae2097b3e..e8f95b86b 100644 --- a/letsencrypt/client/plugins/apache/tests/configurator_test.py +++ b/letsencrypt/client/plugins/apache/tests/configurator_test.py @@ -213,4 +213,4 @@ class TwoVhost80Test(util.ApacheTest): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/apache/tests/dvsni_test.py b/letsencrypt/client/plugins/apache/tests/dvsni_test.py index 2780749b5..82b268246 100644 --- a/letsencrypt/client/plugins/apache/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/apache/tests/dvsni_test.py @@ -173,4 +173,4 @@ class DvsniPerformTest(util.ApacheTest): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/apache/tests/obj_test.py b/letsencrypt/client/plugins/apache/tests/obj_test.py index b0c65eadb..f6632d02d 100644 --- a/letsencrypt/client/plugins/apache/tests/obj_test.py +++ b/letsencrypt/client/plugins/apache/tests/obj_test.py @@ -65,4 +65,4 @@ class VirtualHostTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/apache/tests/parser_test.py b/letsencrypt/client/plugins/apache/tests/parser_test.py index 1696841f8..870a01bab 100644 --- a/letsencrypt/client/plugins/apache/tests/parser_test.py +++ b/letsencrypt/client/plugins/apache/tests/parser_test.py @@ -126,4 +126,4 @@ class ApacheParserTest(util.ApacheTest): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/common_test.py b/letsencrypt/client/plugins/common_test.py index bdb5f7f3c..3cd4a9749 100644 --- a/letsencrypt/client/plugins/common_test.py +++ b/letsencrypt/client/plugins/common_test.py @@ -58,4 +58,4 @@ class PluginTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/disco_test.py b/letsencrypt/client/plugins/disco_test.py index 88aa1275c..a1500017c 100644 --- a/letsencrypt/client/plugins/disco_test.py +++ b/letsencrypt/client/plugins/disco_test.py @@ -240,4 +240,4 @@ class PluginsRegistryTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/nginx/tests/configurator_test.py b/letsencrypt/client/plugins/nginx/tests/configurator_test.py index cb5fef6bf..5aba8b1f8 100644 --- a/letsencrypt/client/plugins/nginx/tests/configurator_test.py +++ b/letsencrypt/client/plugins/nginx/tests/configurator_test.py @@ -268,4 +268,4 @@ class NginxConfiguratorTest(util.NginxTest): self.assertTrue(self.config.config_test()) if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py index bf66367e6..5c76fb6ee 100644 --- a/letsencrypt/client/plugins/nginx/tests/dvsni_test.py +++ b/letsencrypt/client/plugins/nginx/tests/dvsni_test.py @@ -91,4 +91,4 @@ class DvsniPerformTest(util.NginxTest): self.assertEqual(mock_save.call_count, 1) if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py b/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py index 2e19e71d1..0bd7db5fd 100644 --- a/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py +++ b/letsencrypt/client/plugins/nginx/tests/nginxparser_test.py @@ -105,4 +105,4 @@ class TestRawNginxParser(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/nginx/tests/obj_test.py b/letsencrypt/client/plugins/nginx/tests/obj_test.py index d5591c763..0e4fe91d3 100644 --- a/letsencrypt/client/plugins/nginx/tests/obj_test.py +++ b/letsencrypt/client/plugins/nginx/tests/obj_test.py @@ -102,4 +102,4 @@ class VirtualHostTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/nginx/tests/parser_test.py b/letsencrypt/client/plugins/nginx/tests/parser_test.py index 21e96aa26..13020ca92 100644 --- a/letsencrypt/client/plugins/nginx/tests/parser_test.py +++ b/letsencrypt/client/plugins/nginx/tests/parser_test.py @@ -203,4 +203,4 @@ class NginxParserTest(util.NginxTest): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py index 288a04fcc..15640ec15 100644 --- a/letsencrypt/client/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/client/plugins/standalone/tests/authenticator_test.py @@ -606,4 +606,4 @@ class InitTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/account_test.py b/letsencrypt/client/tests/account_test.py index 6a79a94c7..263fbd128 100644 --- a/letsencrypt/client/tests/account_test.py +++ b/letsencrypt/client/tests/account_test.py @@ -208,4 +208,4 @@ class SafeEmailTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/achallenges_test.py b/letsencrypt/client/tests/achallenges_test.py index 72c610f31..3d3310558 100644 --- a/letsencrypt/client/tests/achallenges_test.py +++ b/letsencrypt/client/tests/achallenges_test.py @@ -44,4 +44,4 @@ class DVSNITest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index c6e3b6153..4e7b66c8b 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -458,4 +458,4 @@ def gen_path(required, challs): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/cli_test.py b/letsencrypt/client/tests/cli_test.py index bd25a9792..a7c70d37a 100644 --- a/letsencrypt/client/tests/cli_test.py +++ b/letsencrypt/client/tests/cli_test.py @@ -32,4 +32,4 @@ class CLITest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/client_test.py b/letsencrypt/client/tests/client_test.py index 33530a083..4d71103f0 100644 --- a/letsencrypt/client/tests/client_test.py +++ b/letsencrypt/client/tests/client_test.py @@ -27,11 +27,11 @@ class DetermineAccountTest(unittest.TestCase): @mock.patch("letsencrypt.client.client.account.Account.from_prompts") @mock.patch("letsencrypt.client.client.display_ops.choose_account") - def determine_account(self, mock_op, mock_prompt): + def test_determine_account(self, mock_op, mock_prompt): """Test determine account""" from letsencrypt.client import client - key = le_util.Key("file", "pem") + key = le_util.Key(tempfile.mkstemp()[1], "pem") test_acc = account.Account(self.config, key, "email1@gmail.com") mock_op.return_value = test_acc @@ -82,4 +82,4 @@ class RollbackTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/configuration_test.py b/letsencrypt/client/tests/configuration_test.py index cbbcd57ba..330c4b892 100644 --- a/letsencrypt/client/tests/configuration_test.py +++ b/letsencrypt/client/tests/configuration_test.py @@ -49,4 +49,4 @@ class NamespaceConfigTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/continuity_auth_test.py b/letsencrypt/client/tests/continuity_auth_test.py index 7a2279bcd..96813fc62 100644 --- a/letsencrypt/client/tests/continuity_auth_test.py +++ b/letsencrypt/client/tests/continuity_auth_test.py @@ -80,4 +80,4 @@ def gen_client_resp(chall): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index a36b96c99..d10d1f779 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -151,4 +151,4 @@ class MakeSSCertTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/display/enhancements_test.py b/letsencrypt/client/tests/display/enhancements_test.py index a7fb7f246..12d1ccd82 100644 --- a/letsencrypt/client/tests/display/enhancements_test.py +++ b/letsencrypt/client/tests/display/enhancements_test.py @@ -55,4 +55,4 @@ class RedirectTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/display/ops_test.py b/letsencrypt/client/tests/display/ops_test.py index 4716a5b11..50d249640 100644 --- a/letsencrypt/client/tests/display/ops_test.py +++ b/letsencrypt/client/tests/display/ops_test.py @@ -318,4 +318,4 @@ class SuccessInstallationTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/display/revocation_test.py b/letsencrypt/client/tests/display/revocation_test.py index 557648d9d..4c475002b 100644 --- a/letsencrypt/client/tests/display/revocation_test.py +++ b/letsencrypt/client/tests/display/revocation_test.py @@ -100,4 +100,4 @@ class ConfirmRevocationTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/display/util_test.py b/letsencrypt/client/tests/display/util_test.py index 42c948c79..b07dc8896 100644 --- a/letsencrypt/client/tests/display/util_test.py +++ b/letsencrypt/client/tests/display/util_test.py @@ -346,4 +346,4 @@ class PlaceParensTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/le_util_test.py b/letsencrypt/client/tests/le_util_test.py index 39926a9b5..9859a36f6 100644 --- a/letsencrypt/client/tests/le_util_test.py +++ b/letsencrypt/client/tests/le_util_test.py @@ -123,4 +123,4 @@ class UniqueFileTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/log_test.py b/letsencrypt/client/tests/log_test.py index 49fbdc7c2..8e82b5d61 100644 --- a/letsencrypt/client/tests/log_test.py +++ b/letsencrypt/client/tests/log_test.py @@ -45,4 +45,4 @@ class DialogHandlerTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/network2_test.py b/letsencrypt/client/tests/network2_test.py index d14d27f6a..b414aba40 100644 --- a/letsencrypt/client/tests/network2_test.py +++ b/letsencrypt/client/tests/network2_test.py @@ -479,4 +479,4 @@ class NetworkTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/recovery_token_test.py b/letsencrypt/client/tests/recovery_token_test.py index 0de31a8d0..f294a58dd 100644 --- a/letsencrypt/client/tests/recovery_token_test.py +++ b/letsencrypt/client/tests/recovery_token_test.py @@ -77,4 +77,4 @@ class RecoveryTokenTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/reverter_test.py b/letsencrypt/client/tests/reverter_test.py index 25da75611..46e546ecb 100644 --- a/letsencrypt/client/tests/reverter_test.py +++ b/letsencrypt/client/tests/reverter_test.py @@ -445,4 +445,4 @@ def update_file(filename, string): if __name__ == '__main__': - unittest.main() + unittest.main() # pragma: no cover diff --git a/letsencrypt/client/tests/revoker_test.py b/letsencrypt/client/tests/revoker_test.py index 1ceb8ae9a..90d980a7e 100644 --- a/letsencrypt/client/tests/revoker_test.py +++ b/letsencrypt/client/tests/revoker_test.py @@ -405,4 +405,4 @@ def create_revoker_certs(): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover diff --git a/setup.cfg b/setup.cfg index 75b1ef1a8..3b8e380b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,3 +8,4 @@ dev = develop easy_install letsencrypt[dev,docs,testing] nocapture=1 cover-package=letsencrypt cover-erase=1 +cover-tests=1 diff --git a/tox.ini b/tox.ini index cd6f3c7b0..890fd4d29 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,8 @@ setenv = basepython = python2.7 commands = pip install -e .[testing] - python setup.py nosetests --with-coverage --cover-min-percentage=89 + python setup.py nosetests --with-coverage \ + --cover-tests --cover-min-percentage=94 [testenv:lint] # recent versions of pylint do not support Python 2.6 (#97, #187) From 53fbedda53a031393b4cf0186ebda1d6ed5a2ea9 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 10 May 2015 19:16:31 +0000 Subject: [PATCH 72/76] 100% acme coverage --- letsencrypt/acme/jose/interfaces_test.py | 3 +-- letsencrypt/acme/jose/json_util_test.py | 4 ++-- letsencrypt/acme/jose/jwa_test.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/letsencrypt/acme/jose/interfaces_test.py b/letsencrypt/acme/jose/interfaces_test.py index ffec714db..f42d9d7cf 100644 --- a/letsencrypt/acme/jose/interfaces_test.py +++ b/letsencrypt/acme/jose/interfaces_test.py @@ -44,8 +44,7 @@ class JSONDeSerializableTest(unittest.TestCase): @classmethod def from_json(cls, jobj): - return cls(Basic.from_json(jobj.keys()[0]), - Basic.from_json(jobj.values()[0])) + pass # pragma: no cover self.basic1 = Basic('foo1') self.basic2 = Basic('foo2') diff --git a/letsencrypt/acme/jose/json_util_test.py b/letsencrypt/acme/jose/json_util_test.py index 49c8758be..12430827a 100644 --- a/letsencrypt/acme/jose/json_util_test.py +++ b/letsencrypt/acme/jose/json_util_test.py @@ -45,10 +45,10 @@ class FieldTest(unittest.TestCase): class MockField(interfaces.JSONDeSerializable): # pylint: disable=missing-docstring def to_partial_json(self): - return 'foo' + return 'foo' # pragma: no cover @classmethod def from_json(cls, jobj): - pass + pass # pragma: no cover mock_field = MockField() from letsencrypt.acme.jose.json_util import Field diff --git a/letsencrypt/acme/jose/jwa_test.py b/letsencrypt/acme/jose/jwa_test.py index 737cffda4..5bb42b78e 100644 --- a/letsencrypt/acme/jose/jwa_test.py +++ b/letsencrypt/acme/jose/jwa_test.py @@ -26,10 +26,10 @@ class JWASignatureTest(unittest.TestCase): # pylint: disable=missing-docstring,too-few-public-methods # pylint: disable=abstract-class-not-used def sign(self, key, msg): - raise NotImplementedError() + raise NotImplementedError() # pragma: no cover def verify(self, key, msg, sig): - raise NotImplementedError() + raise NotImplementedError() # pragma: no cover # pylint: disable=invalid-name self.Sig1 = MockSig('Sig1') From a496179f7436d02fb8ff73da59f18b89e46319ea Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 12 May 2015 11:56:26 -0400 Subject: [PATCH 73/76] Incorporated feedback and added extra error checking to POP --- letsencrypt/client/proof_of_possession.py | 35 +++++++------ .../client/tests/proof_of_possession_test.py | 52 ++++++++++--------- .../client/tests/testdata/dsa512_key.pem | 14 +++++ .../client/tests/testdata/dsa_cert.pem | 17 ++++++ 4 files changed, 78 insertions(+), 40 deletions(-) create mode 100644 letsencrypt/client/tests/testdata/dsa512_key.pem create mode 100644 letsencrypt/client/tests/testdata/dsa_cert.pem diff --git a/letsencrypt/client/proof_of_possession.py b/letsencrypt/client/proof_of_possession.py index a2f4956b9..8f11f56c8 100644 --- a/letsencrypt/client/proof_of_possession.py +++ b/letsencrypt/client/proof_of_possession.py @@ -33,15 +33,18 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods or False """ - if (not isinstance(achall.challb.hints.jwk, achall.challb.alg.kty) or - achall.challb.alg in [jose.HS256, jose.HS384, jose.HS512]): + if (achall.alg in [jose.HS256, jose.HS384, jose.HS512] or + not isinstance(achall.hints.jwk, achall.alg.kty)): return None - # This will work regardless of how JWKES is implemented for cert, key, _ in self.installer.get_all_certs_keys(): der_cert_key = M2Crypto.X509.load_cert(cert).get_pubkey().as_der() - cert_key = achall.challb.alg.kty.load(der_cert_key) - if cert_key == achall.challb.hints.jwk: + try: + cert_key = achall.alg.kty.load(der_cert_key) + # If JWKES.load raises other exceptions, they should be caught here + except (IndexError, ValueError, TypeError): + continue + if cert_key == achall.hints.jwk: return self._gen_response(achall, key) # Is there are different prompt we should give the user? @@ -54,29 +57,29 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods # If we get here, the key wasn't found return False - def _gen_response(self, challb, key_path): # pylint: disable=no-self-use + def _gen_response(self, achall, key_path): # pylint: disable=no-self-use """Create the response to the Proof of Possession Challenge. - :param challb: Proof of Possession Challenge - :type challb: :class:`letsencrypt.acme.challenges.ProofOfPossession` + :param achall: Proof of Possession Challenge + :type achall: :class:`letsencrypt.client.achallenges.ProofOfPossession` :param str key_path: Path to the key corresponding to the hinted to public key. - :returns: Response or None/False if the challenge cannot be completed + :returns: Response or False if the challenge cannot be completed :rtype: :class:`letsencrypt.acme.challenges.ProofOfPossessionResponse` or False """ - if os.path.isfile(key_path): with open(key_path, 'rb') as key: try: - jwk = challb.alg.kty.load(key.read()) - except (IndexError, ValueError, TypeError): + # Needs to be changed if JWKES doesn't have a key attribute + jwk = achall.alg.kty.load(key.read()) + sig = other.Signature.from_msg(achall.nonce, jwk.key, + alg=achall.alg) + except (IndexError, ValueError, TypeError, jose.errors.Error): return False - # If JWKES doesn't have a key attribute, this needs to be modified - sig = other.Signature.from_msg(challb.nonce, jwk.key, - alg=challb.alg) - return challenges.ProofOfPossessionResponse(nonce=challb.nonce, + return challenges.ProofOfPossessionResponse(nonce=achall.nonce, signature=sig) + return False diff --git a/letsencrypt/client/tests/proof_of_possession_test.py b/letsencrypt/client/tests/proof_of_possession_test.py index f068bc1b2..80e492887 100644 --- a/letsencrypt/client/tests/proof_of_possession_test.py +++ b/letsencrypt/client/tests/proof_of_possession_test.py @@ -8,6 +8,7 @@ import mock from letsencrypt.acme import challenges from letsencrypt.acme import jose +from letsencrypt.acme import messages2 from letsencrypt.client import achallenges from letsencrypt.client import proof_of_possession from letsencrypt.client.display import util as display_util @@ -19,46 +20,53 @@ CERT0_PATH = pkg_resources.resource_filename( CERT1_PATH = pkg_resources.resource_filename( BASE_PACKAGE, os.path.join("testdata", "cert-san.pem")) CERT2_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "dsa_cert.pem")) +CERT2_KEY_PATH = pkg_resources.resource_filename( + BASE_PACKAGE, os.path.join("testdata", "dsa512_key.pem")) +CERT3_PATH = pkg_resources.resource_filename( BASE_PACKAGE, os.path.join("testdata", "matching_cert.pem")) -KEY_PATH = pkg_resources.resource_filename( +CERT3_KEY_PATH = pkg_resources.resource_filename( BASE_PACKAGE, os.path.join("testdata", "rsa512_key.pem")) -KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( +CERT3_KEY = Crypto.PublicKey.RSA.importKey(pkg_resources.resource_string( BASE_PACKAGE, os.path.join('testdata', 'rsa512_key.pem'))).publickey() class ProofOfPossessionTest(unittest.TestCase): def setUp(self): self.installer = mock.MagicMock() + certs = [CERT0_PATH, CERT1_PATH, CERT2_PATH, CERT3_PATH] + keys = [None, None, CERT2_KEY_PATH, CERT3_KEY_PATH] self.installer.get_all_certs_keys.return_value = zip( - [CERT0_PATH, CERT1_PATH, CERT2_PATH], 3 * [KEY_PATH], 3 * [None]) + certs, keys, 4 * [None]) self.proof_of_pos = proof_of_possession.ProofOfPossession( self.installer) hints = challenges.ProofOfPossession.Hints( - jwk=jose.JWKRSA(key=KEY), cert_fingerprints=(), + jwk=jose.JWKRSA(key=CERT3_KEY), cert_fingerprints=(), certs=(), serial_numbers=(), subject_key_identifiers=(), issuers=(), authorized_for=()) - challenge = challenges.ProofOfPossession( + chall = challenges.ProofOfPossession( alg=jose.RS256, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) + challb = messages2.ChallengeBody( + chall=chall, uri="http://example", status=messages2.STATUS_PENDING) self.achall = achallenges.ProofOfPossession( - challb=challenge, domain="example.com") + challb=challb, domain="example.com") def test_perform_bad_challenge(self): hints = challenges.ProofOfPossession.Hints( - jwk=jose.jwk.JWKOct(key=KEY), cert_fingerprints=(), + jwk=jose.jwk.JWKOct(key=CERT3_KEY), cert_fingerprints=(), certs=(), serial_numbers=(), subject_key_identifiers=(), issuers=(), authorized_for=()) - challenge = challenges.ProofOfPossession( + chall = challenges.ProofOfPossession( alg=jose.HS512, nonce='zczv4HMLVe_0kimJ25Juig', hints=hints) + challb = messages2.ChallengeBody( + chall=chall, uri="http://example", status=messages2.STATUS_PENDING) self.achall = achallenges.ProofOfPossession( - challb=challenge, domain="example.com") - - response = self.proof_of_pos.perform(self.achall) - self.assertEqual(response, None) + challb=challb, domain="example.com") + self.assertEqual(self.proof_of_pos.perform(self.achall), None) def test_perform_no_input(self): - response = self.proof_of_pos.perform(self.achall) - self.assertTrue(response.verify()) + self.assertTrue(self.proof_of_pos.perform(self.achall).verify()) @mock.patch("letsencrypt.client.recovery_token.zope.component.getUtility") def test_perform_with_input(self, mock_input): @@ -66,16 +74,12 @@ class ProofOfPossessionTest(unittest.TestCase): self.installer.get_all_certs_keys.return_value.pop() mock_input().input.side_effect = [(display_util.CANCEL, ""), (display_util.OK, CERT0_PATH), - (display_util.OK, KEY_PATH)] - - response = self.proof_of_pos.perform(self.achall) - self.assertFalse(response) - - response = self.proof_of_pos.perform(self.achall) - self.assertFalse(response) - - response = self.proof_of_pos.perform(self.achall) - self.assertTrue(response.verify()) + (display_util.OK, "imaginary_file"), + (display_util.OK, CERT3_KEY_PATH)] + self.assertFalse(self.proof_of_pos.perform(self.achall)) + self.assertFalse(self.proof_of_pos.perform(self.achall)) + self.assertFalse(self.proof_of_pos.perform(self.achall)) + self.assertTrue(self.proof_of_pos.perform(self.achall).verify()) if __name__ == "__main__": diff --git a/letsencrypt/client/tests/testdata/dsa512_key.pem b/letsencrypt/client/tests/testdata/dsa512_key.pem new file mode 100644 index 000000000..78e164712 --- /dev/null +++ b/letsencrypt/client/tests/testdata/dsa512_key.pem @@ -0,0 +1,14 @@ +-----BEGIN DSA PARAMETERS----- +MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC +OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA +qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl +41pgNJpgu99YOYqPpS0g7A== +-----END DSA PARAMETERS----- +-----BEGIN DSA PRIVATE KEY----- +MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf +n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP +AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm +rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth +zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE +mNv063So6E+eYaIN +-----END DSA PRIVATE KEY----- diff --git a/letsencrypt/client/tests/testdata/dsa_cert.pem b/letsencrypt/client/tests/testdata/dsa_cert.pem new file mode 100644 index 000000000..ef94536e7 --- /dev/null +++ b/letsencrypt/client/tests/testdata/dsa_cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICuDCCAnWgAwIBAgIJAPjmErVMzwVLMAsGCWCGSAFlAwQDAjB3MQswCQYDVQQG +EwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQBgNVBAcMCUFubiBBcmJvcjErMCkG +A1UECgwiVW5pdmVyc2l0eSBvZiBNaWNoaWdhbiBhbmQgdGhlIEVGRjEUMBIGA1UE +AwwLZXhhbXBsZS5jb20wHhcNMTUwNTEyMTUzOTQzWhcNMTUwNjExMTUzOTQzWjB3 +MQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQBgNVBAcMCUFubiBB +cmJvcjErMCkGA1UECgwiVW5pdmVyc2l0eSBvZiBNaWNoaWdhbiBhbmQgdGhlIEVG +RjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wgfEwgakGByqGSM44BAEwgZ0CQQDB5sSg +YF+iQpB4AscecBkxDBhTfkgsQF1XyhSbO/uqlJVSgeKHKp+foYI6LEApI/wQlhxO +KUio9sVt8XI4+VsvAhUA2gUcQOJCCScC8qsbvykfMAl1BI8CQQCp+RrkGeX4J4Qy +nNVkas5WpkT8sV1kr15Ppi1aPOq0iR/eHBdRXEmxOcEbjGat++XjWmA0mmC731g5 +io+lLSDsA0MAAkBNDYtTOMZBIzpSWNw9jkjY4P1MeRRH2Qfa22HNl3vRSgj1u2tV +pOLOCphKG6iT3iCVJA0rQf3YmBSTexwk9oCQo1AwTjAdBgNVHQ4EFgQUZ2DlTDGU +PMwTUt0KztM6IyX61BcwHwYDVR0jBBgwFoAUZ2DlTDGUPMwTUt0KztM6IyX61Bcw +DAYDVR0TBAUwAwEB/zALBglghkgBZQMEAwIDMAAwLQIVAIbMgGx+KwBr4rgqZ2Lh +AAO8TegHAhQsuxpIIIphiReoWEtEJk4TqEIz/A== +-----END CERTIFICATE----- From 27511d48225ef298383ea363472676685ec94270 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 12 May 2015 12:42:59 -0400 Subject: [PATCH 74/76] Added no cover line to unittest.main() --- letsencrypt/client/tests/proof_of_possession_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client/tests/proof_of_possession_test.py b/letsencrypt/client/tests/proof_of_possession_test.py index 80e492887..08f3f3797 100644 --- a/letsencrypt/client/tests/proof_of_possession_test.py +++ b/letsencrypt/client/tests/proof_of_possession_test.py @@ -83,4 +83,4 @@ class ProofOfPossessionTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() + unittest.main() # pragma: no cover From f4144f001794f6bec66d7232f48924666c904277 Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 12 May 2015 12:05:17 -0700 Subject: [PATCH 75/76] Update docs to reflect nginx support --- README.rst | 7 ++++--- letsencrypt/client/plugins/nginx/configurator.py | 8 +++++--- letsencrypt/client/plugins/nginx/dvsni.py | 3 --- letsencrypt/client/plugins/nginx/parser.py | 3 --- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 4f170f11b..3ec317c55 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ All you need to do is:: user@www:~$ sudo letsencrypt -d www.example.org auth -and if you have a compatbile web server (Apache), Let's Encrypt can +and if you have a compatbile web server (Apache or Nginx), Let's Encrypt can not only get a new certificate, but also deploy it and configure your server automatically!:: @@ -60,7 +60,8 @@ Current Features * web servers supported: - - apache2.x (tested and working on Ubuntu Linux) + - apache/2.x (tested and working on Ubuntu Linux) + - nginx/0.8.48+ (tested and mostly working on Ubuntu Linux) - standalone (runs its own webserver to prove you control the domain) * the private key is generated locally on your system @@ -70,7 +71,7 @@ Current Features * can revoke certificates * adjustable RSA key bitlength (2048 (default), 4096, ...) * optionally can install a http->https redirect, so your site effectively - runs https only + runs https only (Apache only) * fully automated * configuration changes are logged and can be reverted using the CLI * text and ncurses UI diff --git a/letsencrypt/client/plugins/nginx/configurator.py b/letsencrypt/client/plugins/nginx/configurator.py index e4cc05d40..982aeeedf 100644 --- a/letsencrypt/client/plugins/nginx/configurator.py +++ b/letsencrypt/client/plugins/nginx/configurator.py @@ -30,8 +30,6 @@ class NginxConfigurator(common.Plugin): # pylint: disable=too-many-instance-attributes,too-many-public-methods """Nginx configurator. - .. warning:: This plugin is a stub, does not support DVSNI yet! - .. todo:: Add proper support for comments in the config. Currently, config files modified by the configurator will lose all their comments. @@ -270,7 +268,7 @@ class NginxConfigurator(common.Plugin): a 'listen 443 ssl' directive to the server block. .. todo:: Maybe this should create a new block instead of modifying - the existing one? + the existing one? :param vhost: The vhost to add SSL to. :type vhost: :class:`~letsencrypt.client.plugins.nginx.obj.VirtualHost` @@ -554,6 +552,10 @@ class NginxConfigurator(common.Plugin): def nginx_restart(nginx_ctl): """Restarts the Nginx Server. + .. todo:: Nginx restart is fatal if the configuration references + non-existent SSL cert/key files. Remove references to /etc/letsencrypt + before restart. + :param str nginx_ctl: Path to the Nginx binary. """ diff --git a/letsencrypt/client/plugins/nginx/dvsni.py b/letsencrypt/client/plugins/nginx/dvsni.py index 0e4f125f6..7293d9a30 100644 --- a/letsencrypt/client/plugins/nginx/dvsni.py +++ b/letsencrypt/client/plugins/nginx/dvsni.py @@ -12,9 +12,6 @@ from letsencrypt.client.plugins.nginx.nginxparser import dump class NginxDvsni(ApacheDvsni): """Class performs DVSNI challenges within the Nginx configurator. - .. todo:: This is basically copied-and-pasted from the Apache equivalent. - It doesn't actually work yet. - :ivar configurator: NginxConfigurator object :type configurator: :class:`~nginx.configurator.NginxConfigurator` diff --git a/letsencrypt/client/plugins/nginx/parser.py b/letsencrypt/client/plugins/nginx/parser.py index 099a8e36d..526e8816b 100644 --- a/letsencrypt/client/plugins/nginx/parser.py +++ b/letsencrypt/client/plugins/nginx/parser.py @@ -41,9 +41,6 @@ class NginxParser(object): directives inside 'http' and 'server' blocks. Note that this only reads Nginx files that potentially declare a virtual host. - .. todo:: Can Nginx 'virtual hosts' be defined somewhere other than in - the server context? - :param str filepath: The path to the files to parse, as a glob """ From cf9a4b60128f7f92e3d6b03e76b03a8838eade5d Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 12 May 2015 20:15:10 +0000 Subject: [PATCH 76/76] Remove unused import --- letsencrypt_nginx/tests/dvsni_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/letsencrypt_nginx/tests/dvsni_test.py b/letsencrypt_nginx/tests/dvsni_test.py index e5f1b1d88..1ea7793cc 100644 --- a/letsencrypt_nginx/tests/dvsni_test.py +++ b/letsencrypt_nginx/tests/dvsni_test.py @@ -6,7 +6,6 @@ import shutil import mock from acme import challenges -from acme import messages2 from letsencrypt import achallenges from letsencrypt import errors