From ea5015be144ea88850ec5b51b0716399757869f3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 24 Jan 2015 01:32:39 +0100 Subject: [PATCH 01/14] better name for KEY_DIR, improve unique_file, adapt tests unique_file now generates filenames that sort correctly and also are better viisible / differentiable in narrow file manager filename columns (like e.g. mc). --- letsencrypt/client/CONFIG.py | 2 +- letsencrypt/client/le_util.py | 17 ++++++++--------- letsencrypt/client/tests/le_util_test.py | 23 +++++++++++------------ 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/letsencrypt/client/CONFIG.py b/letsencrypt/client/CONFIG.py index 7d0b581fb..777f9b25c 100644 --- a/letsencrypt/client/CONFIG.py +++ b/letsencrypt/client/CONFIG.py @@ -35,7 +35,7 @@ CERT_KEY_BACKUP = os.path.join(WORK_DIR, "keys-certs/") REV_TOKENS_DIR = os.path.join(WORK_DIR, "revocation_tokens/") """Directory where all revocation tokens are saved.""" -KEY_DIR = os.path.join(SERVER_ROOT, "ssl/") +KEY_DIR = os.path.join(SERVER_ROOT, "keys/") """Where all keys should be stored""" CERT_DIR = os.path.join(SERVER_ROOT, "certs/") diff --git a/letsencrypt/client/le_util.py b/letsencrypt/client/le_util.py index 08b0f6114..59b581a45 100644 --- a/letsencrypt/client/le_util.py +++ b/letsencrypt/client/le_util.py @@ -49,25 +49,24 @@ def check_permissions(filepath, mode, uid=0): return stat.S_IMODE(file_stat.st_mode) == mode and file_stat.st_uid == uid -def unique_file(default_name, mode=0o777): +def unique_file(path, mode=0o777): """Safely finds a unique file for writing only (by default). - :param str default_name: Default file name + :param str path: path/filename.ext :param int mode: File mode :return: tuple of file object and file name """ - count = 1 - f_parsed = os.path.splitext(default_name) - while 1: + path, tail = os.path.split(path) + count = 0 + while True: + fname = os.path.join(path, "%04d_%s" % (count, tail)) try: - file_d = os.open( - default_name, os.O_CREAT | os.O_EXCL | os.O_RDWR, mode) - return os.fdopen(file_d, 'w'), default_name + file_d = os.open(fname, os.O_CREAT | os.O_EXCL | os.O_RDWR, mode) + return os.fdopen(file_d, 'w'), fname except OSError: pass - default_name = f_parsed[0] + '_' + str(count) + f_parsed[1] count += 1 diff --git a/letsencrypt/client/tests/le_util_test.py b/letsencrypt/client/tests/le_util_test.py index f6c58ac0b..a9a0f7287 100644 --- a/letsencrypt/client/tests/le_util_test.py +++ b/letsencrypt/client/tests/le_util_test.py @@ -100,26 +100,25 @@ class UniqueFileTest(unittest.TestCase): self.assertEqual(0o700, os.stat(self._call(0o700)[1]).st_mode & 0o777) self.assertEqual(0o100, os.stat(self._call(0o100)[1]).st_mode & 0o777) - def test_default_not_exists(self): - self.assertEqual(self._call()[1], self.default_name) - def test_default_exists(self): - name1 = self._call()[1] # create foo.txt + name1 = self._call()[1] # create 0000_foo.txt name2 = self._call()[1] name3 = self._call()[1] self.assertNotEqual(name1, name2) - basename2 = os.path.basename(name2) - self.assertEqual(os.path.dirname(name2), self.root_path) - self.assertTrue(basename2.startswith('foo')) - self.assertTrue(basename2.endswith('.txt')) - self.assertNotEqual(name1, name3) self.assertNotEqual(name2, name3) - basename3 = os.path.basename(name3) + + self.assertEqual(os.path.dirname(name1), self.root_path) + self.assertEqual(os.path.dirname(name2), self.root_path) self.assertEqual(os.path.dirname(name3), self.root_path) - self.assertTrue(basename3.startswith('foo')) - self.assertTrue(basename3.endswith('.txt')) + + basename1 = os.path.basename(name2) + self.assertTrue(basename1.endswith('foo.txt')) + basename2 = os.path.basename(name2) + self.assertTrue(basename2.endswith('foo.txt')) + basename3 = os.path.basename(name3) + self.assertTrue(basename3.endswith('foo.txt')) # https://en.wikipedia.org/wiki/Base64#Examples From 002f98c72071c06841db639863a66b8bc3546305 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 24 Jan 2015 05:57:59 +0100 Subject: [PATCH 02/14] add missing dependencies to README, fixes #151 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f5113931..b2ff99efe 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ In general: ``` sudo apt-get install python python-setuptools python-virtualenv \ - python-dev gcc swig dialog libaugeas0 libssl-dev + python-dev gcc swig dialog libaugeas0 openssl libssl-dev ca-certificates ``` #### Mac OSX From 23cab4e6943ff1a61f985dd67d2ebcf115ca9775 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:12:45 +0000 Subject: [PATCH 03/14] pylint: 10/10 with missing-docstring --- .pylintrc | 20 +++---- letsencrypt/client/apache/configurator.py | 12 ++--- letsencrypt/client/apache/obj.py | 4 +- letsencrypt/client/auth_handler.py | 4 +- letsencrypt/client/crypto_util.py | 2 +- letsencrypt/client/display.py | 29 +++++----- letsencrypt/client/interactive_challenge.py | 1 + .../client/recovery_contact_challenge.py | 6 +-- letsencrypt/client/revoker.py | 2 +- letsencrypt/client/tests/acme_test.py | 53 +++++++++---------- .../client/tests/apache/configurator_test.py | 25 +++------ letsencrypt/client/tests/apache/dvsni_test.py | 24 ++++----- .../client/tests/apache/parser_test.py | 21 ++++---- .../tests/apache/{config_util.py => util.py} | 22 ++++++++ letsencrypt/client/tests/auth_handler_test.py | 28 +++++----- letsencrypt/client/tests/crypto_util_test.py | 16 +++--- letsencrypt/client/tests/le_util_test.py | 6 ++- letsencrypt/scripts/main.py | 20 ++----- 18 files changed, 146 insertions(+), 149 deletions(-) rename letsencrypt/client/tests/apache/{config_util.py => util.py} (81%) diff --git a/.pylintrc b/.pylintrc index 2af1870c4..131fa64a8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -38,7 +38,7 @@ load-plugins= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -#disable= +disable=fixme,locally-disabled [REPORTS] @@ -81,7 +81,7 @@ required-attributes= bad-functions=map,filter,apply,input,file # Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +good-names=i,j,k,ex,Run,_,fd # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata @@ -94,10 +94,10 @@ name-group= include-naming-hint=no # Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ +function-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ +function-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ @@ -148,14 +148,14 @@ module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ +method-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ +method-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match function or class names that do # not require a docstring. -no-docstring-rgx=(__.*__)|(test_[A-Za-z0-9_]*)|(_.*) +no-docstring-rgx=(__.*__)|(test_[A-Za-z0-9_]*)|(_.*)|(.*Test$) # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. @@ -182,7 +182,7 @@ init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). -dummy-variables-rgx=_$|dummy +dummy-variables-rgx=(unused)?_.*|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. @@ -278,8 +278,8 @@ int-import-graph= [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by +# instance to not check methods defined in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,implementedBy # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp diff --git a/letsencrypt/client/apache/configurator.py b/letsencrypt/client/apache/configurator.py index c9c64da93..edffd7ef4 100644 --- a/letsencrypt/client/apache/configurator.py +++ b/letsencrypt/client/apache/configurator.py @@ -427,7 +427,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "based virtual host", addr) self.add_name_vhost(addr) - def make_vhost_ssl(self, nonssl_vhost): + def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options @@ -523,7 +523,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): return ssl_vhost - def supported_enhancements(): + def supported_enhancements(self): # pylint: disable=no-self-use """Returns currently supported enhancements.""" return ["redirect"] @@ -547,7 +547,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): except errors.LetsEncryptConfiguratorError: logging.warn("Failed %s for %s", enhancement, domain) - def _enable_redirect(self, ssl_vhost, options): + def _enable_redirect(self, ssl_vhost, unused_options): """Redirect all equivalent HTTP traffic to ssl_vhost. .. todo:: This enhancement should be rewritten and will unfortunately @@ -562,8 +562,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param ssl_vhost: Destination of traffic, an ssl enabled vhost :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` - :param options: Not currently used - :type options: Not Available + :param unused_options: Not currently used + :type unused_options: Not Available :returns: Success, general_vhost (HTTP vhost) :rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`) @@ -889,7 +889,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ return apache_restart() - def config_test(self): + def config_test(self): # pylint: disable=no-self-use """Check the configuration of Apache for errors. :returns: Success diff --git a/letsencrypt/client/apache/obj.py b/letsencrypt/client/apache/obj.py index df2f36ec4..647724a48 100644 --- a/letsencrypt/client/apache/obj.py +++ b/letsencrypt/client/apache/obj.py @@ -2,7 +2,7 @@ class Addr(object): - """Represents an Apache VirtualHost address. + r"""Represents an Apache VirtualHost address. :param str addr: addr part of vhost address :param str port: port number or \*, or "" @@ -58,7 +58,7 @@ class VirtualHost(object): """ - def __init__(self, filep, path, addrs, ssl, enabled, names=None): + def __init__(self, filep, path, addrs, ssl, enabled, names=None): # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.path = path diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index f33aede1c..b85996818 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -8,7 +8,7 @@ from letsencrypt.client import challenge_util from letsencrypt.client import errors -class AuthHandler(object): +class AuthHandler(object): # pylint: disable=too-many-instance-attributes """ACME Authorization Handler for a client. :ivar dv_auth: Authenticator capable of solving CONFIG.DV_CHALLENGES @@ -286,7 +286,7 @@ class AuthHandler(object): raise errors.LetsEncryptClientError( "Unimplemented Auth Challenge: %s" % chall["type"]) - def _construct_client_chall(self, chall, domain): + def _construct_client_chall(self, chall, domain): # pylint: disable=no-self-use """Construct Client Type Challenges. :param dict chall: Single challenge diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index c11719343..49ab25206 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -238,7 +238,7 @@ def get_cert_info(filename): try: san = cert.get_ext("subjectAltName").get_value() - except: + except LookupError: san = "" return { diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index b3b26a39e..30f9deaf7 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -10,8 +10,8 @@ WIDTH = 72 HEIGHT = 20 -class CommonDisplayMixin(object): - """methods common to both NcursesDisplay and FileDisplay""" +class CommonDisplayMixin(object): # pylint: disable=too-few-public-methods + """Mixin with methods common to classes implementing IDisplay.""" def redirect_by_default(self): choices = [ @@ -41,7 +41,7 @@ class NcursesDisplay(CommonDisplayMixin): def generic_notification(self, message): self.dialog.msgbox(message, width=self.width) - def generic_menu(self, message, choices, input_text=""): + def generic_menu(self, message, choices, unused_input_text=""): # Can accept either tuples or just the actual choices if choices and isinstance(choices[0], tuple): code, selection = self.dialog.menu( @@ -57,9 +57,10 @@ class NcursesDisplay(CommonDisplayMixin): def generic_input(self, message): return self.dialog.inputbox(message) - def generic_yesno(self, message, yes="Yes", no="No"): + def generic_yesno(self, message, yes_label="Yes", no_label="No"): return self.dialog.DIALOG_OK == self.dialog.yesno( - message, self.height, self.width, yes_label=yes, no_label=no) + message, self.height, self.width, + yes_label=yes_label, no_label=no_label) def filter_names(self, names): choices = [(n, "", 0) for n in names] @@ -114,9 +115,8 @@ class FileDisplay(CommonDisplayMixin): def generic_notification(self, message): side_frame = '-' * 79 - wm = textwrap.fill(message, 80) - text = "\n%s\n%s\n%s\n" % (side_frame, wm, side_frame) - self.outfile.write(text) + msg = textwrap.fill(message, 80) + self.outfile.write("\n%s\n%s\n%s\n" % (side_frame, msg, side_frame)) raw_input("Press Enter to Continue") def generic_menu(self, message, choices, input_text=""): @@ -139,7 +139,7 @@ class FileDisplay(CommonDisplayMixin): return code, (selection - 1) - def generic_input(self, message): + def generic_input(self, message): # pylint: disable=no-self-use ans = raw_input("%s (Enter c to cancel)\n" % message) if ans.startswith('c') or ans.startswith('C'): @@ -147,7 +147,7 @@ class FileDisplay(CommonDisplayMixin): else: return OK, ans - def generic_yesno(self, message, yes_label="Yes", no_label="No"): + def generic_yesno(self, message, unused_yes_label="", unused_no_label=""): self.outfile.write("\n%s\n" % textwrap.fill(message, 80)) ans = raw_input("y/n: ") return ans.startswith('y') or ans.startswith('Y') @@ -198,11 +198,10 @@ class FileDisplay(CommonDisplayMixin): return code, selection def success_installation(self, domains): - s_f = '*' * 79 - wm = textwrap.fill(("Congratulations! You have successfully " + - "enabled %s!") % gen_https_names(domains)) - msg = "%s\n%s\n%s\n" - self.outfile.write(msg % (s_f, wm, s_f)) + side_frame = '*' * 79 + msg = textwrap.fill("Congratulations! You have successfully " + "enabled %s!" % gen_https_names(domains)) + self.outfile.write("%s\n%s\n%s\n" % (side_frame, msg, side_frame)) def confirm_revocation(self, cert): self.outfile.write("Are you sure you would like to revoke " diff --git a/letsencrypt/client/interactive_challenge.py b/letsencrypt/client/interactive_challenge.py index c802ca191..685d2d58f 100644 --- a/letsencrypt/client/interactive_challenge.py +++ b/letsencrypt/client/interactive_challenge.py @@ -1,3 +1,4 @@ +"""Interactive challenge.""" import textwrap import dialog diff --git a/letsencrypt/client/recovery_contact_challenge.py b/letsencrypt/client/recovery_contact_challenge.py index 6bafed829..c5ca404d9 100644 --- a/letsencrypt/client/recovery_contact_challenge.py +++ b/letsencrypt/client/recovery_contact_challenge.py @@ -31,7 +31,7 @@ class RecoveryContact(object): self.poll_delay = poll_delay def perform(self, quiet=True): - d = dialog.Dialog() + d = dialog.Dialog() # pylint: disable=invalid-name if quiet: if self.success_url: d.infobox(self.get_display_string()) @@ -50,7 +50,7 @@ class RecoveryContact(object): return True - def cleanup(self): + def cleanup(self): # pylint: disable=no-self-use return def get_display_string(self): @@ -91,7 +91,7 @@ class RecoveryContact(object): else: return False - def prompt_continue(self, quiet=True): + def prompt_continue(self, quiet=True): # pylint: disable=no-self-use """Prompt user for continuation. :param bool quiet: Display dialog box if True, raw prompt otherwise. diff --git a/letsencrypt/client/revoker.py b/letsencrypt/client/revoker.py index 073362501..8573868eb 100644 --- a/letsencrypt/client/revoker.py +++ b/letsencrypt/client/revoker.py @@ -61,7 +61,7 @@ class Revoker(object): try: c_sha1_vh[M2Crypto.X509.load_cert( cert).get_fingerprint(md='sha1')] = path - except: + except M2Crypto.X509.X509Error: continue with open(list_file, 'rb') as csvfile: diff --git a/letsencrypt/client/tests/acme_test.py b/letsencrypt/client/tests/acme_test.py index 808eefc1b..f3cf4a69a 100644 --- a/letsencrypt/client/tests/acme_test.py +++ b/letsencrypt/client/tests/acme_test.py @@ -45,7 +45,8 @@ class ACMEObjectValidateTest(unittest.TestCase): class PrettyTest(unittest.TestCase): """Tests for letsencrypt.client.acme.pretty.""" - def _call(self, json_string): + @classmethod + def _call(cls, json_string): from letsencrypt.client.acme import pretty return pretty(json_string) @@ -64,31 +65,19 @@ class MessageFactoriesTest(unittest.TestCase): self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' - def _validate(self, msg): + @classmethod + def _validate(cls, msg): from letsencrypt.client.acme import SCHEMATA jsonschema.validate(msg, SCHEMATA[msg['type']]) - def _signature(self, sig): - return { - 'nonce': self.b64nonce, - 'alg': 'RS256', - 'jwk': { - 'kty': 'RSA', - 'e': 'AQAB', - 'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5' - '80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q', - }, - 'sig': sig, - } - def test_challenge_request(self): from letsencrypt.client.acme import challenge_request msg = challenge_request('example.com') + self._validate(msg) self.assertEqual(msg, { 'type': 'challengeRequest', 'identifier': 'example.com', }) - self._validate(msg) def test_authorization_request(self): from letsencrypt.client.acme import authorization_request @@ -112,51 +101,57 @@ class MessageFactoriesTest(unittest.TestCase): self.nonce, ) + self._validate(msg) + self.assertEqual( + msg.pop('signature')['sig'], + 'VkpReso87ogwGul2MGck96TkYs4QoblIgNthgrm9O7EBGlzCRCnTHnx' + 'bj6loqaC4f5bn1rgS927Gp1Kvbqnmqg' + ) self.assertEqual(msg, { 'type': 'authorizationRequest', 'sessionID': 'aefoGaavieG9Wihuk2aufai3aeZ5EeW4', 'nonce': 'czpsrF0KMH6dgajig3TGHw', - 'signature': self._signature( - 'VkpReso87ogwGul2MGck96TkYs4QoblIgNthgrm9O7EBGlzCRCnTHnx' - 'bj6loqaC4f5bn1rgS927Gp1Kvbqnmqg'), 'responses': responses, }) - self._validate(msg) def test_certificate_request(self): from letsencrypt.client.acme import certificate_request msg = certificate_request( 'TODO: real DER CSR?', self.privkey, self.nonce) + self._validate(msg) + self.assertEqual( + msg.pop('signature')['sig'], + 'HEQVN4MU1yDrArP2T7WZQ12XlHCn5DgTPgb5eWT5_vjRPppLSNe6uWE' + 'x9SFwG9d9umqn49nZCSW7uskA2lcW6Q' + ) self.assertEqual(msg, { 'type': 'certificateRequest', 'csr': 'VE9ETzogcmVhbCBERVIgQ1NSPw', - 'signature': self._signature( - 'HEQVN4MU1yDrArP2T7WZQ12XlHCn5DgTPgb5eWT5_vjRPppLSNe6uWE' - 'x9SFwG9d9umqn49nZCSW7uskA2lcW6Q'), }) - self._validate(msg) def test_revocation_request(self): from letsencrypt.client.acme import revocation_request msg = revocation_request( 'TODO: real DER cert?', self.privkey, self.nonce) + self._validate(msg) + self.assertEqual( + msg.pop('signature')['sig'], + 'ABXA1IsyTalTXIojxmGnIUGyZASmvqEvTQ98jJ5KFs2FTswLEmsoqFX' + 'fU6l5_fous-tsbXOfLN-7PjfZ5XWPvg' + ) self.assertEqual(msg, { 'type': 'revocationRequest', 'certificate': 'VE9ETzogcmVhbCBERVIgY2VydD8', - 'signature': self._signature( - 'ABXA1IsyTalTXIojxmGnIUGyZASmvqEvTQ98jJ5KFs2FTswLEmsoqFX' - 'fU6l5_fous-tsbXOfLN-7PjfZ5XWPvg'), }) - self._validate(msg) def test_status_request(self): from letsencrypt.client.acme import status_request msg = status_request(u'O7-s9MNq1siZHlgrMzi9_A') + self._validate(msg) self.assertEqual(msg, { 'type': 'statusRequest', 'token': u'O7-s9MNq1siZHlgrMzi9_A', }) - self._validate(msg) if __name__ == '__main__': diff --git a/letsencrypt/client/tests/apache/configurator_test.py b/letsencrypt/client/tests/apache/configurator_test.py index e34fca832..749ecb9c0 100644 --- a/letsencrypt/client/tests/apache/configurator_test.py +++ b/letsencrypt/client/tests/apache/configurator_test.py @@ -1,6 +1,5 @@ """Test for letsencrypt.client.apache.configurator.""" import os -import pkg_resources import re import shutil import unittest @@ -15,26 +14,19 @@ from letsencrypt.client.apache import configurator from letsencrypt.client.apache import obj from letsencrypt.client.apache import parser -from letsencrypt.client.tests.apache import config_util +from letsencrypt.client.tests.apache import util -class TwoVhost80Test(unittest.TestCase): +class TwoVhost80Test(util.ApacheTest): """Test two standard well configured HTTP vhosts.""" def setUp(self): - self.temp_dir, self.config_dir, self.work_dir = config_util.dir_setup( - "debian_apache_2_4/two_vhost_80") + super(TwoVhost80Test, self).setUp() - self.ssl_options = config_util.setup_apache_ssl_options(self.config_dir) - - # Final slash is currently important - self.config_path = os.path.join( - self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") - - self.config = config_util.get_apache_configurator( + self.config = util.get_apache_configurator( self.config_path, self.config_dir, self.work_dir, self.ssl_options) - self.vh_truth = config_util.get_vh_truth( + self.vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/two_vhost_80") def tearDown(self): @@ -168,12 +160,7 @@ class TwoVhost80Test(unittest.TestCase): 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 - rsa256_file = pkg_resources.resource_filename( - "letsencrypt.client.tests", 'testdata/rsa256_key.pem') - rsa256_pem = pkg_resources.resource_string( - "letsencrypt.client.tests", 'testdata/rsa256_key.pem') - - auth_key = client.Client.Key(rsa256_file, rsa256_pem) + auth_key = client.Client.Key(self.rsa256_file, self.rsa256_pem) chall1 = challenge_util.DvsniChall( "encryption-example.demo", "jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q", diff --git a/letsencrypt/client/tests/apache/dvsni_test.py b/letsencrypt/client/tests/apache/dvsni_test.py index bd6838869..feaec124f 100644 --- a/letsencrypt/client/tests/apache/dvsni_test.py +++ b/letsencrypt/client/tests/apache/dvsni_test.py @@ -1,5 +1,4 @@ """Test for letsencrypt.client.apache.dvsni.""" -import os import pkg_resources import unittest import shutil @@ -10,26 +9,19 @@ from letsencrypt.client import challenge_util from letsencrypt.client import client from letsencrypt.client import CONFIG -from letsencrypt.client.tests.apache import config_util +from letsencrypt.client.tests.apache import util -class DvsniPerformTest(unittest.TestCase): +class DvsniPerformTest(util.ApacheTest): """Test the ApacheDVSNI challenge.""" + def setUp(self): - from letsencrypt.client.apache import dvsni + super(DvsniPerformTest, self).setUp() - self.temp_dir, self.config_dir, self.work_dir = config_util.dir_setup( - "debian_apache_2_4/two_vhost_80") - - self.ssl_options = config_util.setup_apache_ssl_options(self.config_dir) - - # Final slash is currently important - self.config_path = os.path.join( - self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") - - config = config_util.get_apache_configurator( + config = util.get_apache_configurator( self.config_path, self.config_dir, self.work_dir, self.ssl_options) + from letsencrypt.client.apache import dvsni self.sni = dvsni.ApacheDvsni(config) rsa256_file = pkg_resources.resource_filename( @@ -144,3 +136,7 @@ class DvsniPerformTest(unittest.TestCase): self.assertEqual( vhost.names, set([str(self.challs[1].nonce + CONFIG.INVALID_EXT)])) + + +if __name__ == '__main__': + unittest.main() diff --git a/letsencrypt/client/tests/apache/parser_test.py b/letsencrypt/client/tests/apache/parser_test.py index 2acf6533e..df0046ff5 100644 --- a/letsencrypt/client/tests/apache/parser_test.py +++ b/letsencrypt/client/tests/apache/parser_test.py @@ -11,23 +11,18 @@ import zope.component from letsencrypt.client import display from letsencrypt.client import errors from letsencrypt.client.apache import parser -from letsencrypt.client.tests.apache import config_util + +from letsencrypt.client.tests.apache import util -class ApacheParserTest(unittest.TestCase): +class ApacheParserTest(util.ApacheTest): """Apache Parser Test.""" + def setUp(self): + super(ApacheParserTest, self).setUp() + zope.component.provideUtility(display.FileDisplay(sys.stdout)) - self.temp_dir, self.config_dir, self.work_dir = config_util.dir_setup( - "debian_apache_2_4/two_vhost_80") - - self.ssl_options = config_util.setup_apache_ssl_options(self.config_dir) - - # Final slash is currently important - self.config_path = os.path.join( - self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") - self.parser = parser.ApacheParser( augeas.Augeas(flags=augeas.Augeas.NONE), self.config_path, self.ssl_options) @@ -112,3 +107,7 @@ class ApacheParserTest(unittest.TestCase): self.assertEqual(results["default"], results["listen"]) self.assertEqual(results["default"], results["name"]) + + +if __name__ == '__main__': + unittest.main() diff --git a/letsencrypt/client/tests/apache/config_util.py b/letsencrypt/client/tests/apache/util.py similarity index 81% rename from letsencrypt/client/tests/apache/config_util.py rename to letsencrypt/client/tests/apache/util.py index ad38818ab..d5a662924 100644 --- a/letsencrypt/client/tests/apache/config_util.py +++ b/letsencrypt/client/tests/apache/util.py @@ -1,7 +1,9 @@ +"""Common utilities for letsencrypt.client.apache.""" import os import pkg_resources import shutil import tempfile +import unittest import mock @@ -10,6 +12,26 @@ from letsencrypt.client.apache import configurator from letsencrypt.client.apache import obj +class ApacheTest(unittest.TestCase): + + def setUp(self): + super(ApacheTest, self).setUp() + + self.temp_dir, self.config_dir, self.work_dir = dir_setup( + "debian_apache_2_4/two_vhost_80") + + self.ssl_options = setup_apache_ssl_options(self.config_dir) + + # Final slash is currently important + self.config_path = os.path.join( + self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") + + self.rsa256_file = pkg_resources.resource_filename( + "letsencrypt.client.tests", 'testdata/rsa256_key.pem') + self.rsa256_pem = pkg_resources.resource_string( + "letsencrypt.client.tests", 'testdata/rsa256_key.pem') + + def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"): """Setup the directories necessary for the configurator.""" temp_dir = tempfile.mkdtemp("temp") diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index 2cb801efc..9b2caebeb 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -1,21 +1,26 @@ -"""Test auth_handler.py.""" +"""Tests for letsencrypt.client.auth_handler.""" import unittest + import mock +from letsencrypt.client import errors from letsencrypt.client.tests import acme_util -TRANSLATE = {"dvsni": "DvsniChall", - "simpleHttps": "SimpleHttpsChall", - "dns": "DnsChall", - "recoveryToken": "RecTokenChall", - "recoveryContact": "RecContactChall", - "proofOfPossession": "PopChall"} +TRANSLATE = { + "dvsni": "DvsniChall", + "simpleHttps": "SimpleHttpsChall", + "dns": "DnsChall", + "recoveryToken": "RecTokenChall", + "recoveryContact": "RecContactChall", + "proofOfPossession": "PopChall", +} # pylint: disable=protected-access class SatisfyChallengesTest(unittest.TestCase): """verify_identities test.""" + def setUp(self): from letsencrypt.client.auth_handler import AuthHandler @@ -225,7 +230,7 @@ class SatisfyChallengesTest(unittest.TestCase): self.assertEqual( type(self.handler.client_c["4"][0].chall).__name__, "RecTokenChall") - def _get_exp_response(self, domain, path, challenges): + def _get_exp_response(self, domain, path, challenges): # pylint: disable=no-self-use exp_resp = ["null"] * len(challenges) for i in path: exp_resp[i] = TRANSLATE[challenges[i]["type"]] + str(domain) @@ -283,7 +288,6 @@ class GetAuthorizationsTest(unittest.TestCase): self.handler.dv_c[dom], self.handler.client_c[dom] = dv_c, c_c def test_progress_failure(self): - from letsencrypt.client.errors import LetsEncryptAuthHandlerError challenges = acme_util.get_challenges() self.handler.add_chall_msg( "0", @@ -294,7 +298,7 @@ class GetAuthorizationsTest(unittest.TestCase): self.mock_sat_chall.side_effect = self._sat_failure self.assertRaises( - LetsEncryptAuthHandlerError, self.handler.get_authorizations) + errors.LetsEncryptAuthHandlerError, self.handler.get_authorizations) # Check to make sure program didn't loop self.assertEqual(self.mock_sat_chall.call_count, 1) @@ -327,8 +331,6 @@ class GetAuthorizationsTest(unittest.TestCase): [mock.call("1"), mock.call("0")]) def _sat_incremental(self): - from letsencrypt.client.errors import LetsEncryptAuthHandlerError - # Exact responses don't matter, just path/response match if self.iteration == 0: # Only solve one of "0" required challs @@ -353,7 +355,7 @@ class GetAuthorizationsTest(unittest.TestCase): self.handler.responses["0"][3] = "finally!" else: - raise LetsEncryptAuthHandlerError( + raise errors.LetsEncryptAuthHandlerError( "Failed incremental test: too many invocations") def _test_finished(self): diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index e80988d83..49d7c02b1 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -30,7 +30,8 @@ class CreateSigTest(unittest.TestCase): 'AX9AFJHk-bCMQPJbSzXKjG6H1IWbvxjS2Ew', } - def _call(self, *args, **kwargs): + @classmethod + def _call(cls, *args, **kwargs): from letsencrypt.client.crypto_util import create_sig return create_sig(*args, **kwargs) @@ -50,7 +51,8 @@ class CreateSigTest(unittest.TestCase): class ValidCSRTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.valid_csr.""" - def _call(self, csr): + @classmethod + def _call(cls, csr): from letsencrypt.client.crypto_util import valid_csr return valid_csr(csr) @@ -80,7 +82,8 @@ class ValidCSRTest(unittest.TestCase): class CSRMatchesPubkeyTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.csr_matches_pubkey.""" - def _call_testdata(self, name, privkey): + @classmethod + def _call_testdata(cls, name, privkey): from letsencrypt.client.crypto_util import csr_matches_pubkey return csr_matches_pubkey(pkg_resources.resource_string( __name__, os.path.join('testdata', name)), privkey) @@ -95,7 +98,7 @@ class CSRMatchesPubkeyTest(unittest.TestCase): class MakeKeyTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.make_key.""" - def test_it(self): + def test_it(self): # pylint: disable=no-self-use from letsencrypt.client.crypto_util import make_key M2Crypto.RSA.load_key_string(make_key(1024)) @@ -103,7 +106,8 @@ class MakeKeyTest(unittest.TestCase): class ValidPrivkeyTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.valid_privkey.""" - def _call(self, privkey): + @classmethod + def _call(cls, privkey): from letsencrypt.client.crypto_util import valid_privkey return valid_privkey(privkey) @@ -120,7 +124,7 @@ class ValidPrivkeyTest(unittest.TestCase): class MakeSSCertTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.make_ss_cert.""" - def test_it(self): + def test_it(self): # pylint: disable=no-self-use from letsencrypt.client.crypto_util import make_ss_cert make_ss_cert(RSA256_KEY, ['example.com', 'www.example.com']) diff --git a/letsencrypt/client/tests/le_util_test.py b/letsencrypt/client/tests/le_util_test.py index f6c58ac0b..f7ab1ab2b 100644 --- a/letsencrypt/client/tests/le_util_test.py +++ b/letsencrypt/client/tests/le_util_test.py @@ -141,7 +141,8 @@ B64_URL_UNSAFE_EXAMPLES = { class JOSEB64EncodeTest(unittest.TestCase): """Tests for letsencrypt.client.le_util.jose_b64encode.""" - def _call(self, data): + @classmethod + def _call(cls, data): from letsencrypt.client.le_util import jose_b64encode return jose_b64encode(data) @@ -160,7 +161,8 @@ class JOSEB64EncodeTest(unittest.TestCase): class JOSEB64DecodeTest(unittest.TestCase): """Tests for letsencrypt.client.le_util.jose_b64decode.""" - def _call(self, data): + @classmethod + def _call(cls, data): from letsencrypt.client.le_util import jose_b64decode return jose_b64decode(data) diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index ff3c3c792..801e36121 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -6,18 +6,18 @@ import os import sys import zope.component +import zope.interface from letsencrypt.client import CONFIG from letsencrypt.client import client from letsencrypt.client import display from letsencrypt.client import interfaces -from letsencrypt.client import errors from letsencrypt.client import log from letsencrypt.client import revoker from letsencrypt.client.apache import configurator -def main(): +def main(): # pylint: disable=too-many-statements """Command line argument parsing and main script execution.""" if not os.geteuid() == 0: sys.exit( @@ -163,22 +163,12 @@ def get_all_names(installer): # This should be controlled by commandline parameters def determine_authenticator(): """Returns a valid IAuthenticator.""" - try: - if interfaces.IAuthenticator.implementedBy( - configurator.ApacheConfigurator): - return configurator.ApacheConfigurator() - except errors.LetsEncryptConfiguratorError: - logging.info("Unable to determine a way to authenticate the server") + return configurator.ApacheConfigurator() def determine_installer(): - """Returns a valid installer if one exists.""" - try: - if interfaces.IInstaller.implementedBy( - configurator.ApacheConfigurator): - return configurator.ApacheConfigurator() - except errors.LetsEncryptConfiguratorError: - logging.info("Unable to find a way to install the certificate.") + """Returns a valid IInstaller.""" + return configurator.ApacheConfigurator() def read_file(filename): From 48f1497af6a5fd4c787013eb28a67398680aff74 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:13:13 +0000 Subject: [PATCH 04/14] Fix docs warnings --- README.md | 2 +- docs/_static/.gitignore | 0 docs/api/client/auth_handler.rst | 2 +- docs/api/client/client_authenticator.rst | 2 +- letsencrypt/client/apache/configurator.py | 4 ++-- letsencrypt/client/apache/dvsni.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 docs/_static/.gitignore diff --git a/README.md b/README.md index 9f5113931..9192b7b17 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ In order to generate the sphinx documentation, run the following commands. ``` ./venv/bin/python setup.py docs cd docs -make html SPHINXBUILD='../venv/bin/python ../venv/bin/sphinx-build' +make clean html SPHINXBUILD=../venv/bin/sphinx-build ``` This should generate documentation in the /lets-encrypt-preview/docs/_build/html diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/docs/api/client/auth_handler.rst b/docs/api/client/auth_handler.rst index e84745d1e..b52006993 100644 --- a/docs/api/client/auth_handler.rst +++ b/docs/api/client/auth_handler.rst @@ -1,5 +1,5 @@ :mod:`letsencrypt.client.auth_handler` --------------------------------- +-------------------------------------- .. automodule:: letsencrypt.client.auth_handler :members: diff --git a/docs/api/client/client_authenticator.rst b/docs/api/client/client_authenticator.rst index a9050de50..267a0dd50 100644 --- a/docs/api/client/client_authenticator.rst +++ b/docs/api/client/client_authenticator.rst @@ -1,5 +1,5 @@ :mod:`letsencrypt.client.client_authenticator` --------------------------------- +---------------------------------------------- .. automodule:: letsencrypt.client.client_authenticator :members: diff --git a/letsencrypt/client/apache/configurator.py b/letsencrypt/client/apache/configurator.py index edffd7ef4..9f5e6929f 100644 --- a/letsencrypt/client/apache/configurator.py +++ b/letsencrypt/client/apache/configurator.py @@ -532,7 +532,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :class:`letsencrypt.client.CONFIG.ENHANCEMENTS + :class:`letsencrypt.client.CONFIG.ENHANCEMENTS` :param options: options for the enhancement :type options: See :class:`letsencrypt.client.CONFIG.ENHANCEMENTS` documentation for appropriate parameter. @@ -979,7 +979,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): fulfilled by configurator. :returns: list of responses. All responses are returned in the same - order as received by the perform function. A None response + order as received by the perform function. A None response indicates the challenge was not perfromed. :rtype: list diff --git a/letsencrypt/client/apache/dvsni.py b/letsencrypt/client/apache/dvsni.py index b513275da..ca8f2d0fb 100644 --- a/letsencrypt/client/apache/dvsni.py +++ b/letsencrypt/client/apache/dvsni.py @@ -17,7 +17,7 @@ class ApacheDvsni(object): :ivar dvsni_chall: Data required for challenges. where DvsniChall tuples have the following fields `domain` (`str`), `r_b64` (base64 `str`), `nonce` (hex `str`) - `key` (:class:`letsencrypt.client.client.Client.Key`) + `key` (:class:`letsencrypt.client.client.Client.Key`) :type dvsni_chall: `list` of :class:`letsencrypt.client.challenge_util.DvsniChall` From b9723b6cad039e0f1b9755efbc50528e8478af78 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:16:00 +0000 Subject: [PATCH 05/14] Fix line-too-long from master --- letsencrypt/client/client.py | 3 ++- letsencrypt/scripts/main.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 9ecaf1b35..0e4fa47f0 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -342,7 +342,8 @@ def init_key(key_size): key_pem = crypto_util.make_key(key_size) except ValueError as err: logging.fatal(str(err)) - logging.info("Note: The default RSA key size is %d bits.", CONFIG.RSA_KEY_SIZE) + logging.info("Note: The default RSA key size is %d bits.", + CONFIG.RSA_KEY_SIZE) sys.exit(1) # Save file diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index aea052b46..d4283a9db 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -40,7 +40,7 @@ def main(): # pylint: disable=too-many-statements help="Revert configuration N number of checkpoints.") parser.add_argument("-B", "--keysize", dest="key_size", type=int, default=CONFIG.RSA_KEY_SIZE, metavar="N", - help="RSA key shall be sized N bits. [%d]" % CONFIG.RSA_KEY_SIZE) + help="RSA key shall be sized N bits. [%(default)d]") parser.add_argument("-k", "--revoke", dest="revoke", action="store_true", help="Revoke a certificate.") parser.add_argument("-v", "--view-config-changes", From ceebb09d89ae4dd12fc00a61c6936d1ab3fa7307 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:40:05 +0000 Subject: [PATCH 06/14] Add some docs --- letsencrypt/client/crypto_util.py | 1 + letsencrypt/client/display.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index b7644aecc..feff99abe 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -255,5 +255,6 @@ def get_cert_info(filename): def b64_cert_to_pem(b64_der_cert): + """Convert JOSE Base-64 encoded DER cert to PEM.""" return M2Crypto.X509.load_cert_der_string( le_util.jose_b64decode(b64_der_cert)).as_pem() diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index 30f9deaf7..2c379fef8 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -1,3 +1,4 @@ +"""Lets Encrypt display.""" import textwrap import dialog @@ -30,6 +31,8 @@ class CommonDisplayMixin(object): # pylint: disable=too-few-public-methods class NcursesDisplay(CommonDisplayMixin): + """Ncurses-based display.""" + zope.interface.implements(interfaces.IDisplay) def __init__(self, width=WIDTH, height=HEIGHT): @@ -107,6 +110,8 @@ class NcursesDisplay(CommonDisplayMixin): class FileDisplay(CommonDisplayMixin): + """File-based display.""" + zope.interface.implements(interfaces.IDisplay) def __init__(self, outfile): From 1107c5a9715ffc9323f9785f9b451dcf4899152a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:40:35 +0000 Subject: [PATCH 07/14] pylint: locally disable missing-docstring --- letsencrypt/client/display.py | 42 +++++++++---------- letsencrypt/client/interactive_challenge.py | 4 +- .../client/recovery_contact_challenge.py | 6 +-- letsencrypt/client/tests/auth_handler_test.py | 4 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index 2c379fef8..05730bfc4 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -14,7 +14,7 @@ HEIGHT = 20 class CommonDisplayMixin(object): # pylint: disable=too-few-public-methods """Mixin with methods common to classes implementing IDisplay.""" - def redirect_by_default(self): + def redirect_by_default(self): # pylint: disable=missing-docstring choices = [ ("Easy", "Allow both HTTP and HTTPS access to these sites"), ("Secure", "Make all requests redirect to secure HTTPS access")] @@ -41,10 +41,10 @@ class NcursesDisplay(CommonDisplayMixin): self.width = width self.height = height - def generic_notification(self, message): + def generic_notification(self, message): # pylint: disable=missing-docstring self.dialog.msgbox(message, width=self.width) - def generic_menu(self, message, choices, unused_input_text=""): + def generic_menu(self, message, choices, unused_input_text=""): # pylint: disable=missing-docstring # Can accept either tuples or just the actual choices if choices and isinstance(choices[0], tuple): code, selection = self.dialog.menu( @@ -57,27 +57,27 @@ class NcursesDisplay(CommonDisplayMixin): return code(int(tag) - 1) - def generic_input(self, message): + def generic_input(self, message): # pylint: disable=missing-docstring return self.dialog.inputbox(message) - def generic_yesno(self, message, yes_label="Yes", no_label="No"): + def generic_yesno(self, message, yes_label="Yes", no_label="No"): # pylint: disable=missing-docstring return self.dialog.DIALOG_OK == self.dialog.yesno( message, self.height, self.width, yes_label=yes_label, no_label=no_label) - def filter_names(self, names): + def filter_names(self, names): # pylint: disable=missing-docstring choices = [(n, "", 0) for n in names] code, names = self.dialog.checklist( "Which names would you like to activate HTTPS for?", choices=choices) return code, [str(s) for s in names] - def success_installation(self, domains): + def success_installation(self, domains): # pylint: disable=missing-docstring self.dialog.msgbox( "\nCongratulations! You have successfully enabled " + gen_https_names(domains) + "!", width=self.width) - def display_certs(self, certs): + def display_certs(self, certs): # pylint: disable=missing-docstring list_choices = [ (str(i+1), "%s | %s | %s" % (str(c["cn"].ljust(self.width - 39)), @@ -94,7 +94,7 @@ class NcursesDisplay(CommonDisplayMixin): tag = -1 return code, (int(tag) - 1) - def confirm_revocation(self, cert): + def confirm_revocation(self, cert): # pylint: disable=missing-docstring text = ("Are you sure you would like to revoke the following " "certificate:\n") text += cert_info_frame(cert) @@ -102,7 +102,7 @@ class NcursesDisplay(CommonDisplayMixin): return self.dialog.DIALOG_OK == self.dialog.yesno( text, width=self.width, height=self.height) - def more_info_cert(self, cert): + def more_info_cert(self, cert): # pylint: disable=missing-docstring text = "Certificate Information:\n" text += cert_info_frame(cert) print text @@ -118,13 +118,13 @@ class FileDisplay(CommonDisplayMixin): super(FileDisplay, self).__init__() self.outfile = outfile - def generic_notification(self, message): + def generic_notification(self, message): # pylint: disable=missing-docstring side_frame = '-' * 79 msg = textwrap.fill(message, 80) self.outfile.write("\n%s\n%s\n%s\n" % (side_frame, msg, side_frame)) raw_input("Press Enter to Continue") - def generic_menu(self, message, choices, input_text=""): + def generic_menu(self, message, choices, input_text=""): # pylint: disable=missing-docstring # Can take either tuples or single items in choices list if choices and isinstance(choices[0], tuple): choices = ["%s - %s" % (c[0], c[1]) for c in choices] @@ -144,7 +144,7 @@ class FileDisplay(CommonDisplayMixin): return code, (selection - 1) - def generic_input(self, message): # pylint: disable=no-self-use + def generic_input(self, message): # pylint: disable=no-self-use,missing-docstring ans = raw_input("%s (Enter c to cancel)\n" % message) if ans.startswith('c') or ans.startswith('C'): @@ -152,12 +152,12 @@ class FileDisplay(CommonDisplayMixin): else: return OK, ans - def generic_yesno(self, message, unused_yes_label="", unused_no_label=""): + def generic_yesno(self, message, unused_yes_label="", unused_no_label=""): # pylint: disable=missing-docstring self.outfile.write("\n%s\n" % textwrap.fill(message, 80)) ans = raw_input("y/n: ") return ans.startswith('y') or ans.startswith('Y') - def filter_names(self, names): + def filter_names(self, names): # pylint: disable=missing-docstring code, tag = self.generic_menu( "Choose the names would you like to upgrade to HTTPS?", names, "Select the number of the name: ") @@ -165,7 +165,7 @@ class FileDisplay(CommonDisplayMixin): # Make sure to return a list... return code, [names[tag]] - def display_certs(self, certs): + def display_certs(self, certs): # pylint: disable=missing-docstring menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] + " - " + str(c["not_before"])[:-6]) for i, c in enumerate(certs)] @@ -202,13 +202,13 @@ class FileDisplay(CommonDisplayMixin): return code, selection - def success_installation(self, domains): + def success_installation(self, domains): # pylint: disable=missing-docstring side_frame = '*' * 79 msg = textwrap.fill("Congratulations! You have successfully " "enabled %s!" % gen_https_names(domains)) self.outfile.write("%s\n%s\n%s\n" % (side_frame, msg, side_frame)) - def confirm_revocation(self, cert): + def confirm_revocation(self, cert): # pylint: disable=missing-docstring self.outfile.write("Are you sure you would like to revoke " "the following certificate:\n") self.outfile.write(cert_info_frame(cert)) @@ -216,7 +216,7 @@ class FileDisplay(CommonDisplayMixin): ans = raw_input("y/n") return ans.startswith('y') or ans.startswith('Y') - def more_info_cert(self, cert): + def more_info_cert(self, cert): # pylint: disable=missing-docstring self.outfile.write("\nCertificate Information:\n") self.outfile.write(cert_info_frame(cert)) @@ -225,14 +225,14 @@ CANCEL = "cancel" HELP = "help" -def cert_info_frame(cert): +def cert_info_frame(cert): # pylint: disable=missing-docstring text = "-" * (WIDTH - 4) + "\n" text += cert_info_string(cert) text += "-" * (WIDTH - 4) return text -def cert_info_string(cert): +def cert_info_string(cert): # pylint: disable=missing-docstring text = "Subject: %s\n" % cert["subject"] text += "SAN: %s\n" % cert["san"] text += "Issuer: %s\n" % cert["issuer"] diff --git a/letsencrypt/client/interactive_challenge.py b/letsencrypt/client/interactive_challenge.py index 685d2d58f..4130525f5 100644 --- a/letsencrypt/client/interactive_challenge.py +++ b/letsencrypt/client/interactive_challenge.py @@ -24,7 +24,7 @@ class InteractiveChallenge(object): super(InteractiveChallenge, self).__init__() self.string = string - def perform(self, quiet=True): + def perform(self, quiet=True): # pylint: disable=missing-docstring if quiet: dialog.Dialog().msgbox( self.get_display_string(), width=self.BOX_SIZE) @@ -34,7 +34,7 @@ class InteractiveChallenge(object): return True - def get_display_string(self): + def get_display_string(self): # pylint: disable=missing-docstring return (textwrap.fill(self.string, width=self.BOX_SIZE) + "\n\nPlease Press Enter to Continue") diff --git a/letsencrypt/client/recovery_contact_challenge.py b/letsencrypt/client/recovery_contact_challenge.py index c5ca404d9..6cfab00d0 100644 --- a/letsencrypt/client/recovery_contact_challenge.py +++ b/letsencrypt/client/recovery_contact_challenge.py @@ -30,7 +30,7 @@ class RecoveryContact(object): self.contact = contact self.poll_delay = poll_delay - def perform(self, quiet=True): + def perform(self, quiet=True): # pylint: disable=missing-docstring d = dialog.Dialog() # pylint: disable=invalid-name if quiet: if self.success_url: @@ -50,7 +50,7 @@ class RecoveryContact(object): return True - def cleanup(self): # pylint: disable=no-self-use + def cleanup(self): # pylint: disable=no-self-use,missing-docstring return def get_display_string(self): @@ -109,7 +109,7 @@ class RecoveryContact(object): return ans.startswith('y') or ans.startswith('Y') - def generate_response(self): + def generate_response(self): # pylint: disable=missing-docstring if not self.token: return {"type": "recoveryContact"} return { diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index 9b2caebeb..c5e97dace 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -410,12 +410,12 @@ class PathSatisfiedTest(unittest.TestCase): self.assertFalse(self.handler._path_satisfied(dom[i])) -def gen_auth_resp(chall_list): +def gen_auth_resp(chall_list): # pylint: disable=missing-docstring return ["%s%s" % (type(chall).__name__, chall.domain) for chall in chall_list] -def gen_path(str_list, challenges): +def gen_path(str_list, challenges): # pylint: disable=missing-docstring path = [] for i, chall in enumerate(challenges): for str_chall in str_list: From 8ef9e13e0930ba4847879f26b0fee5144e3cf511 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 13:59:19 +0000 Subject: [PATCH 08/14] pylint: ignore providedBy --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 131fa64a8..fa1c5be45 100644 --- a/.pylintrc +++ b/.pylintrc @@ -279,7 +279,7 @@ int-import-graph= # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defined in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,implementedBy +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,implementedBy,providedBy # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp From a507293bd850637ed96eecd00595564e831ee4c1 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 14:09:46 +0000 Subject: [PATCH 09/14] Quickfix for #147 --- letsencrypt/scripts/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index d4283a9db..143b347af 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -94,7 +94,8 @@ def main(): # pylint: disable=too-many-statements display_eula() # Use the same object if possible - if interfaces.IAuthenticator.providedBy(installer): + if interfaces.IAuthenticator.providedBy(installer): # pylint: disable=no-member + auth = installer else: auth = determine_authenticator() From e9ed8c86e40cd9851676be76267c481c37461d6e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 14:27:15 +0000 Subject: [PATCH 10/14] Quickfix for #187 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 6c49c2f2e..290ba3924 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ testing_extras = [ 'nose', 'nosexcover', 'pylint<1.4', # py2.6 compat, c.f #97 + 'astroid<1.3.0', # py2.6 compat, c.f. #187 'tox', ] From 982fa1962879c5f9f54df227dfca707a3af74b6a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 14:33:19 +0000 Subject: [PATCH 11/14] Bump cover-min-percentage to 61 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b2d7b9ae9..c8c671ca1 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ commands = [testenv:cover] commands = python setup.py dev - python setup.py nosetests --with-coverage --cover-min-percentage=59 + python setup.py nosetests --with-coverage --cover-min-percentage=61 [testenv:lint] commands = From 6a4164a293d489b432a5ee5dd884887c748db864 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 19:50:51 +0000 Subject: [PATCH 12/14] Magical fix for #152 (M2Crypto install dependency order) --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c49c2f2e..411ac8083 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ from setuptools import setup install_requires = [ 'argparse', 'jsonschema', - 'M2Crypto', 'mock', 'pycrypto', 'python-augeas', @@ -13,6 +12,9 @@ install_requires = [ 'requests', 'zope.component', 'zope.interface', + # order of items in install_requires DOES matter and M2Crypto has + # to go last, see #152 + 'M2Crypto', ] docs_extras = [ From f12ef3b933932db9dc84a6374d9ca9bbaf87d8da Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 24 Jan 2015 20:30:58 +0000 Subject: [PATCH 13/14] whitespace --- letsencrypt/scripts/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 143b347af..0fd3f99c8 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -95,7 +95,6 @@ def main(): # pylint: disable=too-many-statements # Use the same object if possible if interfaces.IAuthenticator.providedBy(installer): # pylint: disable=no-member - auth = installer else: auth = determine_authenticator() From 55754c669758834d290e4b67ab91acc4273fb799 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 24 Jan 2015 21:44:52 +0100 Subject: [PATCH 14/14] remove openssl package from requirements, might be only needed for some special branches --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2ff99efe..87d1df0cd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ In general: ``` sudo apt-get install python python-setuptools python-virtualenv \ - python-dev gcc swig dialog libaugeas0 openssl libssl-dev ca-certificates + python-dev gcc swig dialog libaugeas0 libssl-dev ca-certificates ``` #### Mac OSX