From 49914f3307d0f08b8015e5c49fcfcbd9dcc12185 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 17 Dec 2014 11:35:11 +0100 Subject: [PATCH 1/5] display: width and height instance variables --- letsencrypt/client/client.py | 3 +-- letsencrypt/client/display.py | 45 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index a68d8dd39..b69295a38 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -206,8 +206,7 @@ class Client(object): acme.revocation_request(cert_der, key), "revocation") display.generic_notification( - "You have successfully revoked the certificate for " - "%s" % cert["cn"], width=70, height=9) + "You have successfully revoked the certificate for %s" % cert["cn"]) remove_cert_key(cert) self.list_certs_keys() diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index ac1f9d819..e74148916 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -20,11 +20,10 @@ class SingletonD(object): class Display(SingletonD): """Generic display.""" - def generic_notification(self, message, width=WIDTH, height=HEIGHT): + def generic_notification(self, message): raise NotImplementedError() - def generic_menu(self, message, choices, input_text="", - width=WIDTH, height=HEIGHT): + def generic_menu(self, message, choices, input_text=""): raise NotImplementedError() def generic_input(self, message): @@ -51,23 +50,24 @@ class Display(SingletonD): class NcursesDisplay(Display): - def __init__(self): + def __init__(self, width=WIDTH, height=HEIGHT): self.dialog = dialog.Dialog() + self.width = width + self.height = height - def generic_notification(self, message, w=WIDTH, h=HEIGHT): - self.dialog.msgbox(message, width=w, height=h) + def generic_notification(self, message): + self.dialog.msgbox(message, width=self.width, height=self.height) - def generic_menu(self, message, choices, input_text="", width=WIDTH, - height=HEIGHT): + def generic_menu(self, message, choices, input_text=""): # Can accept either tuples or just the actual choices if choices and isinstance(choices[0], tuple): code, selection = self.dialog.menu( - message, choices=choices, width=WIDTH, height=HEIGHT) + message, choices=choices, width=self.width, height=self.height) return code, str(selection) else: choices = list(enumerate(choices, 1)) code, tag = self.dialog.menu( - message, choices=choices, width=WIDTH, height=HEIGHT) + message, choices=choices, width=self.width, height=self.height) return code(int(tag) - 1) @@ -76,7 +76,7 @@ class NcursesDisplay(Display): def generic_yesno(self, message, yes="Yes", no="No"): return self.dialog.DIALOG_OK == self.dialog.yesno( - message, HEIGHT, WIDTH, yes_label=yes, no_label=no) + message, self.height, self.width, yes_label=yes, no_label=no) def filter_names(self, names): choices = [(n, "", 0) for n in names] @@ -88,12 +88,12 @@ class NcursesDisplay(Display): def success_installation(self, domains): self.dialog.msgbox( "\nCongratulations! You have successfully enabled " - + gen_https_names(domains) + "!", width=WIDTH) + + gen_https_names(domains) + "!", width=self.width) def display_certs(self, certs): list_choices = [ (str(i+1), "%s | %s | %s" % - (str(c["cn"].ljust(WIDTH - 39)), + (str(c["cn"].ljust(self.width - 39)), c["not_before"].strftime("%m-%d-%y"), "Installed" if c["installed"] else "")) for i, c in enumerate(certs)] @@ -102,7 +102,7 @@ class NcursesDisplay(Display): "Which certificates would you like to revoke?", choices=list_choices, help_button=True, help_label="More Info", ok_label="Revoke", - width=WIDTH, height=HEIGHT) + width=self.width, height=self.height) if not tag: tag = -1 return code, (int(tag) - 1) @@ -113,13 +113,13 @@ class NcursesDisplay(Display): text += cert_info_frame(cert) text += "This action cannot be reversed!" return self.dialog.DIALOG_OK == self.dialog.yesno( - text, width=WIDTH, height=HEIGHT) + text, width=self.width, height=self.height) def more_info_cert(self, cert): text = "Certificate Information:\n" text += cert_info_frame(cert) print text - self.dialog.msgbox(text, width=WIDTH, height=HEIGHT) + self.dialog.msgbox(text, width=self.width, height=self.height) class FileDisplay(Display): @@ -127,15 +127,14 @@ class FileDisplay(Display): def __init__(self, outfile): self.outfile = outfile - def generic_notification(self, message, width=WIDTH, height=HEIGHT): + 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) raw_input("Press Enter to Continue") - def generic_menu(self, message, choices, input_text="", - width=WIDTH, height=HEIGHT): + def generic_menu(self, message, choices, input_text=""): # 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] @@ -243,12 +242,12 @@ def set_display(display_inst): display = display_inst -def generic_notification(message, width=WIDTH, height=HEIGHT): - display.generic_notification(message, width, height) +def generic_notification(message): + display.generic_notification(message) -def generic_menu(message, choices, input_text="", width=WIDTH, height=HEIGHT): - return display.generic_menu(message, choices, input_text, width, height) +def generic_menu(message, choices, input_text=""): + return display.generic_menu(message, choices, input_text) def generic_input(message): From 3a459c95c929304d79eeb6aaa07893736645ddd2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 17 Dec 2014 11:35:45 +0100 Subject: [PATCH 2/5] Remove SingletonD --- letsencrypt/client/display.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index e74148916..700d69f8e 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -7,17 +7,7 @@ WIDTH = 72 HEIGHT = 20 -class SingletonD(object): - _instance = None - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = super(SingletonD, cls).__new__( - cls, *args, **kwargs) - return cls._instance - - -class Display(SingletonD): +class Display(object): """Generic display.""" def generic_notification(self, message): From 7c3abe7ba738de25c981624bbed23bcabf4a73ab Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 17 Dec 2014 11:44:31 +0100 Subject: [PATCH 3/5] IDisplay --- letsencrypt/client/display.py | 42 +++++++------------------------- letsencrypt/client/interfaces.py | 31 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index 700d69f8e..5fff122b9 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -1,46 +1,20 @@ import textwrap import dialog +import zope.interface + +from letsencrypt.client import interfaces WIDTH = 72 HEIGHT = 20 -class Display(object): - """Generic display.""" - - def generic_notification(self, message): - raise NotImplementedError() - - def generic_menu(self, message, choices, input_text=""): - raise NotImplementedError() - - def generic_input(self, message): - raise NotImplementedError() - - def generic_yesno(self, message, yes_label="Yes", no_label="No"): - raise NotImplementedError() - - def filter_names(self, names): - raise NotImplementedError() - - def success_installation(self, domains): - raise NotImplementedError() - - def display_certs(self, certs): - raise NotImplementedError() - - def confirm_revocation(self, cert): - raise NotImplementedError() - - def more_info_cert(self, cert): - raise NotImplementedError() - - -class NcursesDisplay(Display): +class NcursesDisplay(object): + zope.interface.implements(interfaces.IDisplay) def __init__(self, width=WIDTH, height=HEIGHT): + super(NcursesDisplay, self).__init__() self.dialog = dialog.Dialog() self.width = width self.height = height @@ -112,9 +86,11 @@ class NcursesDisplay(Display): self.dialog.msgbox(text, width=self.width, height=self.height) -class FileDisplay(Display): +class FileDisplay(object): + zope.interface.implements(interfaces.IDisplay) def __init__(self, outfile): + super(FileDisplay, self).__init__() self.outfile = outfile def generic_notification(self, message): diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index cbe71cd4c..8f7f10e97 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -103,6 +103,37 @@ class IConfigurator(zope.interface.Interface): """Cleanup configuration changes from challenge.""" +class IDisplay(zope.interface.Interface): + """Generic display.""" + + def generic_notification(message): + pass + + def generic_menu(message, choices, input_text=""): + pass + + def generic_input(message): + pass + + def generic_yesno(message, yes_label="Yes", no_label="No"): + pass + + def filter_names(names): + pass + + def success_installation(domains): + pass + + def display_certs(certs): + pass + + def confirm_revocation(cert): + pass + + def more_info_cert(cert): + pass + + class IValidator(object): """Configuration validator.""" From 37a015f983cb5c954e6106c2f8c5b98b5dd42597 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 17 Dec 2014 12:02:15 +0100 Subject: [PATCH 4/5] display using ZCA --- letsencrypt/client/client.py | 26 ++++--- letsencrypt/client/display.py | 75 ++++--------------- letsencrypt/client/interfaces.py | 3 + .../client/tests/apache_configurator_test.py | 3 +- letsencrypt/scripts/main.py | 7 +- setup.py | 1 + 6 files changed, 43 insertions(+), 72 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index b69295a38..d18a9331e 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -13,6 +13,7 @@ import time import jsonschema import M2Crypto import requests +import zope.component from letsencrypt.client import acme from letsencrypt.client import apache_configurator @@ -21,6 +22,7 @@ from letsencrypt.client import CONFIG from letsencrypt.client import crypto_util from letsencrypt.client import display from letsencrypt.client import errors +from letsencrypt.client import interfaces from letsencrypt.client import le_util @@ -90,11 +92,13 @@ class Client(object): if not self.config.config_test(): sys.exit(1) + displayer = zope.component.getUtility(interfaces.IDisplay) + # Display preview warning if not eula: with open('EULA') as eula_file: - if not display.generic_yesno(eula_file.read(), - "Agree", "Cancel"): + if not displayer.generic_yesno( + eula_file.read(), "Agree", "Cancel"): sys.exit(0) # Display screen to select domains to validate @@ -105,7 +109,7 @@ class Client(object): # This function adds all names # found within the config to self.names # Then filters them based on user selection - code, self.names = display.filter_names(self.get_all_names()) + code, self.names = displayer.filter_names(self.get_all_names()) if code == display.OK and self.names: # TODO: Allow multiple names once it is setup self.names = [self.names[0]] @@ -205,7 +209,7 @@ class Client(object): revocation = self.send_and_receive_expected( acme.revocation_request(cert_der, key), "revocation") - display.generic_notification( + zope.component.getUtility(interface.IDisplay).generic_notification( "You have successfully revoked the certificate for %s" % cert["cn"]) remove_cert_key(cert) @@ -352,7 +356,7 @@ class Client(object): if certs: self.choose_certs(certs) else: - display.generic_notification( + zope.component.getUtility(interfaces.IDisplay).generic_notification( "There are not any trusted Let's Encrypt " "certificates for this server.") @@ -363,16 +367,18 @@ class Client(object): """ code, tag = display.display_certs(certs) + + displayer = zope.component.getUtility(interfaces.IDisplay) if code == display.OK: cert = certs[tag] - if display.confirm_revocation(cert): + if displayer.confirm_revocation(cert): self.acme_revocation(cert) else: self.choose_certs(certs) elif code == display.HELP: cert = certs[tag] - display.more_info_cert(cert) + displayer.more_info_cert(cert) self.choose_certs(certs) else: exit(0) @@ -416,7 +422,8 @@ class Client(object): # sites may have been enabled / final cleanup self.config.restart(quiet=self.use_curses) - display.success_installation(self.names) + zope.component.getUtility( + interfaces.IDisplay).success_installation(self.names) return cert_file @@ -432,7 +439,8 @@ class Client(object): """ # TODO: this should most definitely be moved to __init__ if redirect is None: - redirect = display.redirect_by_default() + redirect = zope.component.getUtility( + intefaces.IDisplay).redirect_by_default() if redirect: self.redirect_to_ssl(vhost) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index 5fff122b9..9da2f78b7 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -85,6 +85,21 @@ class NcursesDisplay(object): print text self.dialog.msgbox(text, width=self.width, height=self.height) + def redirect_by_default(self): + choices = [ + ("Easy", "Allow both HTTP and HTTPS access to these sites"), + ("Secure", "Make all requests redirect to secure HTTPS access")] + + result = self.generic_menu( + "Please choose whether HTTPS access is required or optional.", + choices, "Please enter the appropriate number") + + if result[0] != OK: + return False + + # different answer for each type of display + return str(result[1]) == "Secure" or result[1] == 1 + class FileDisplay(object): zope.interface.implements(interfaces.IDisplay) @@ -197,41 +212,11 @@ class FileDisplay(object): self.outfile.write("\nCertificate Information:\n") self.outfile.write(cert_info_frame(cert)) -display = None OK = "ok" CANCEL = "cancel" HELP = "help" -def set_display(display_inst): - global display - display = display_inst - - -def generic_notification(message): - display.generic_notification(message) - - -def generic_menu(message, choices, input_text=""): - return display.generic_menu(message, choices, input_text) - - -def generic_input(message): - return display.generic_message(message) - - -def generic_yesno(message, yes_label="Yes", no_label="No"): - return display.generic_yesno(message, yes_label, no_label) - - -def filter_names(names): - return display.filter_names(names) - - -def display_certs(certs): - return display.display_certs(certs) - - def cert_info_frame(cert): text = "-" * (WIDTH - 4) + "\n" text += cert_info_string(cert) @@ -268,33 +253,3 @@ def gen_https_names(domains): result = result + "https://" + domains[len(domains)-1] return result - - -def success_installation(domains): - return display.success_installation(domains) - - -def redirect_by_default(): - choices = [ - ("Easy", "Allow both HTTP and HTTPS access to these sites"), - ("Secure", "Make all requests redirect to secure HTTPS access")] - - result = display.generic_menu("Please choose whether HTTPS access " + - "is required or optional.", - choices, - "Please enter the appropriate number", - width=WIDTH) - - if result[0] != OK: - return False - - # different answer for each type of display - return str(result[1]) == "Secure" or result[1] == 1 - - -def confirm_revocation(cert): - return display.confirm_revocation(cert) - - -def more_info_cert(cert): - return display.more_info_cert(cert) diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index 8f7f10e97..74eb50ada 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -133,6 +133,9 @@ class IDisplay(zope.interface.Interface): def more_info_cert(cert): pass + def redirect_by_default(): + pass + class IValidator(object): """Configuration validator.""" diff --git a/letsencrypt/client/tests/apache_configurator_test.py b/letsencrypt/client/tests/apache_configurator_test.py index 08c99cbeb..a11f0898e 100644 --- a/letsencrypt/client/tests/apache_configurator_test.py +++ b/letsencrypt/client/tests/apache_configurator_test.py @@ -8,6 +8,7 @@ import tempfile import unittest import mock +import zope.component from letsencrypt.client import apache_configurator from letsencrypt.client import CONFIG @@ -23,7 +24,7 @@ class TwoVhost80Test(unittest.TestCase): """Test two standard well configured HTTP vhosts.""" def setUp(self): - display.set_display(display.NcursesDisplay()) + zope.component.provideUtility(display.NcursesDisplay()) self.temp_dir = os.path.join( tempfile.mkdtemp("temp"), "debian_apache_2_4") diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 8cbda62dc..96bca973b 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -5,6 +5,8 @@ import logging import os import sys +import zope.component + from letsencrypt.client import apache_configurator from letsencrypt.client import CONFIG from letsencrypt.client import client @@ -73,9 +75,10 @@ def main(): .format(os.linesep)) if args.use_curses: - display.set_display(display.NcursesDisplay()) + displayer = display.NcursesDisplay() else: - display.set_display(display.FileDisplay(sys.stdout)) + displayer = display.FileDisplay(sys.stdout) + zope.component.provideUtility(displayer) if args.rollback > 0: rollback(apache_configurator.ApacheConfigurator(), args.rollback) diff --git a/setup.py b/setup.py index f5190807c..902ca5204 100755 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ install_requires = [ 'python-augeas', 'python2-pythondialog', 'requests', + 'zope.component', 'zope.interface', ] From 7860db63cf66b453256b24f1e77fb3858858b1a0 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Sun, 21 Dec 2014 21:41:12 -0800 Subject: [PATCH 5/5] Make necessary fixes to pull request --- letsencrypt/client/client.py | 3 +-- letsencrypt/client/display.py | 2 +- letsencrypt/client/revoker.py | 2 +- letsencrypt/scripts/main.py | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 0eab27f42..8c5f4525f 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -9,7 +9,6 @@ import string import sys import M2Crypto -import requests import zope.component from letsencrypt.client import acme @@ -227,7 +226,7 @@ class Client(object): """ if redirect is None: redirect = zope.component.getUtility( - intefaces.IDisplay).redirect_by_default() + interfaces.IDisplay).redirect_by_default() if redirect: self.redirect_to_ssl(vhost) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index 9da2f78b7..c6d90b5f0 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -20,7 +20,7 @@ class NcursesDisplay(object): self.height = height def generic_notification(self, message): - self.dialog.msgbox(message, width=self.width, height=self.height) + self.dialog.msgbox(message, width=self.width) def generic_menu(self, message, choices, input_text=""): # Can accept either tuples or just the actual choices diff --git a/letsencrypt/client/revoker.py b/letsencrypt/client/revoker.py index b7eb69216..073362501 100644 --- a/letsencrypt/client/revoker.py +++ b/letsencrypt/client/revoker.py @@ -39,7 +39,7 @@ class Revoker(object): zope.component.getUtility(interfaces.IDisplay).generic_notification( "You have successfully revoked the certificate for " - "%s" % cert["cn"], width=70, height=9) + "%s" % cert["cn"]) self.remove_cert_key(cert) self.list_certs_keys() diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 152db8c03..8aeb43136 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -132,7 +132,7 @@ def main(): def display_eula(): """Displays the end user agreement.""" with open('EULA') as eula_file: - if not zope_component.getUtility(interfaces.IDisplay).generic_yesno( + if not zope.component.getUtility(interfaces.IDisplay).generic_yesno( eula_file.read(), "Agree", "Cancel"): sys.exit(0) @@ -147,7 +147,7 @@ def choose_names(installer): # This function adds all names # found within the config to self.names # Then filters them based on user selection - code, names = zope_component.getUtility( + code, names = zope.component.getUtility( interfaces.IDisplay).filter_names(get_all_names(installer)) if code == display.OK and names: # TODO: Allow multiple names once it is setup