git push origin masterMerge branch 'kuba-zope.interface-display'

This commit is contained in:
James Kasten 2014-12-21 21:41:45 -08:00
commit 684a9aae9d
9 changed files with 106 additions and 138 deletions

View file

@ -9,13 +9,14 @@ import string
import sys
import M2Crypto
import zope.component
from letsencrypt.client import acme
from letsencrypt.client import challenge
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
from letsencrypt.client import network
@ -206,7 +207,8 @@ class Client(object):
# sites may have been enabled / final cleanup
self.installer.restart()
display.success_installation(self.names)
zope.component.getUtility(
interfaces.IDisplay).success_installation(self.names)
return vhost
@ -223,7 +225,8 @@ class Client(object):
"""
if redirect is None:
redirect = display.redirect_by_default()
redirect = zope.component.getUtility(
interfaces.IDisplay).redirect_by_default()
if redirect:
self.redirect_to_ssl(vhost)

View file

@ -1,73 +1,37 @@
import textwrap
import dialog
import zope.interface
from letsencrypt.client import interfaces
WIDTH = 72
HEIGHT = 20
class SingletonD(object):
_instance = None
class NcursesDisplay(object):
zope.interface.implements(interfaces.IDisplay)
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(SingletonD, cls).__new__(
cls, *args, **kwargs)
return cls._instance
class Display(SingletonD):
"""Generic display."""
def generic_notification(self, message, width=WIDTH, height=HEIGHT):
raise NotImplementedError()
def generic_menu(self, message, choices, input_text="",
width=WIDTH, height=HEIGHT):
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):
def __init__(self):
def __init__(self, width=WIDTH, height=HEIGHT):
super(NcursesDisplay, self).__init__()
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)
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 +40,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 +52,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 +66,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,29 +77,45 @@ 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)
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(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, 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]
@ -232,41 +212,11 @@ class FileDisplay(Display):
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, width=WIDTH, height=HEIGHT):
display.generic_notification(message, width, height)
def generic_menu(message, choices, input_text="", width=WIDTH, height=HEIGHT):
return display.generic_menu(message, choices, input_text, width, height)
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)
@ -303,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)

View file

@ -110,6 +110,40 @@ class IInstaller(zope.interface.Interface):
"""Restart or refresh the server content."""
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
def redirect_by_default():
pass
class IValidator(object):
"""Configuration validator."""

View file

@ -3,9 +3,9 @@
.. note:: This challenge has not been implemented into the project yet
"""
import zope.component
import zope.interface
from letsencrypt.client import display
from letsencrypt.client import interfaces
@ -22,8 +22,9 @@ class RecoveryToken(object):
self.token = ""
def perform(self, quiet=True):
cancel, self.token = display.generic_input(
"Please Input Recovery Token: ")
cancel, self.token = zope.component.getUtility(
interfaces.IDisplay).generic_input(
"Please Input Recovery Token: ")
return cancel != 1
def cleanup(self):

View file

@ -5,11 +5,13 @@ import os
import shutil
import M2Crypto
import zope.component
from letsencrypt.client import acme
from letsencrypt.client import CONFIG
from letsencrypt.client import crypto_util
from letsencrypt.client import display
from letsencrypt.client import interfaces
from letsencrypt.client import network
@ -35,9 +37,9 @@ class Revoker(object):
revocation = self.network.send_and_receive_expected(
acme.revocation_request(cert_der, key), "revocation")
display.generic_notification(
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()
@ -84,7 +86,7 @@ class Revoker(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.")
@ -94,17 +96,18 @@ class Revoker(object):
:param list certs: List of cert dicts.
"""
code, tag = display.display_certs(certs)
displayer = zope.component.getUtility(interfaces.IDisplay)
code, tag = displayer.display_certs(certs)
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)

View file

@ -5,6 +5,7 @@ import shutil
import unittest
import mock
import zope.component
from letsencrypt.client import display
from letsencrypt.client import errors
@ -20,7 +21,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, self.config_dir, self.work_dir = config_util.dir_setup(
"debian_apache_2_4/two_vhost_80")

View file

@ -5,6 +5,7 @@ import unittest
import augeas
import mock
import zope.component
from letsencrypt.client import display
from letsencrypt.client import errors
@ -15,7 +16,7 @@ from letsencrypt.client.tests import config_util
class ApacheParserTest(unittest.TestCase):
def setUp(self):
display.set_display(display.FileDisplay(sys.stdout))
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")

View file

@ -5,6 +5,8 @@ import logging
import os
import sys
import zope.component
from letsencrypt.client import CONFIG
from letsencrypt.client import client
from letsencrypt.client import display
@ -68,9 +70,10 @@ def main():
logger.setLevel(logging.INFO)
if args.use_curses:
logger.addHandler(log.DialogHandler())
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)
installer = determine_installer()
server = CONFIG.ACME_SERVER if args.server is None else args.server
@ -129,7 +132,7 @@ def main():
def display_eula():
"""Displays the end user agreement."""
with open('EULA') as eula_file:
if not display.generic_yesno(
if not zope.component.getUtility(interfaces.IDisplay).generic_yesno(
eula_file.read(), "Agree", "Cancel"):
sys.exit(0)
@ -144,7 +147,8 @@ 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 = display.filter_names(get_all_names(installer))
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
return [names[0]]

View file

@ -11,6 +11,7 @@ install_requires = [
'python-augeas',
'python2-pythondialog',
'requests',
'zope.component',
'zope.interface',
]