From 66c37a2d4092cb7165f56273e3eb51f002f8235d Mon Sep 17 00:00:00 2001 From: James Kasten Date: Mon, 10 Nov 2014 22:55:01 -0800 Subject: [PATCH] Pulled out all display code out of client and arranged code into a curses and stdout Singleton --- trustify/client/client.py | 70 ++++++++--------- trustify/client/display.py | 157 ++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 56 deletions(-) diff --git a/trustify/client/client.py b/trustify/client/client.py index 90ba9090c..998fc9342 100755 --- a/trustify/client/client.py +++ b/trustify/client/client.py @@ -34,12 +34,7 @@ class Client(object): def __init__(self, ca_server, domains=[], cert_signing_request=None, private_key=None, use_curses=True): global dialog - self.curses = use_curses - if self.curses: - import dialog - self.d = dialog.Dialog() - # Logger needs to be initialized before Configurator self.init_logger() @@ -74,8 +69,12 @@ class Client(object): sys.exit(1) # Display screen to select domains to validate - self.names = self.filter_names(self.names) - self.names = [self.names[0]] + code, self.names = display.filter_names(self.names) + if code == display.OK: + # TODO: Allow multiple names once it is setup + self.names = [self.names[0]] + else: + sys.exit(0) # Display choice of CA screen # TODO: Use correct server depending on CA @@ -135,7 +134,7 @@ class Client(object): revocation_dict = self.is_expected_msg(revocation_dict, "revocation") - self.d.msgbox("You have successfully revoked the certificate for %s" % c["cn"], width=70, height=16) + dialog.generic_notification("You have successfully revoked the certificate for %s" % c["cn"]) self.remove_cert_key(c) sys.exit(0) @@ -207,27 +206,21 @@ class Client(object): c["idx"] = int(row[0]) certs.append(c) if certs: - self.__display_certs(certs) + self.choose_certs(certs) else: - logger.info("There are not any trusted Let's Encrypt certificates for this server.") + display.generic_notification("There are not any trusted Let's Encrypt certificates for this server.") - def __display_certs(self, certs): + def choose_certs(self, certs): while True: - menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] + " - " + str(c["not_before"])[:-6]) for i, c in enumerate(certs)] - if self.curses: - code, selection = self.d.menu("Which certificate would you like to revoke?", choices = menu_choices, - help_button=True, help_label="More Info", ok_label="Revoke", - width=70, height=16) - - - if code == self.d.DIALOG_OK: - if display.confirm_revocation(certs[int(selection)-1]): - self.revoke(c) + code, selection = display.display_certs(certs) + if code == display.OK: + if display.confirm_revocation(certs[int(selection)-1]): + self.revoke(c) - elif code == self.d.DIALOG_CANCEL: - exit(0) - elif code == "help": - display.more_info_cert(certs[int(selection)-1]) + elif code == display.CANCEL: + exit(0) + elif code == display.HELP: + display.more_info_cert(certs[int(selection)-1]) @@ -340,6 +333,12 @@ class Client(object): return responses, challenge_objs def gen_challenge_path(self, challenges, combos): + """ + Generate a plan to get authority over the identity + TODO: Make sure that the challenges are feasible... + TODO Example: Do you have the recovery key? + """ + if combos: return self.__find_smart_path(challenges, combos) @@ -504,23 +503,17 @@ class Client(object): return key_pem, csr_pem - - def filter_names(self, names): - choices = [(n, "", 0) for n in names] - result = self.d.checklist("Which names would you like to activate HTTPS for?", choices=choices) - if result[0] != 0 or not result[1]: - sys.exit(1) - return result[1] - + def choice_of_ca(self): choices = self.get_cas() + message = "Pick a Certificate Authority. They're all unique and special!" + in_txt = "Enter the number of a Certificate Authority (c to cancel): " + code, selection = display.generic_menu(message, choices, in_txt) - result = self.d.menu("Pick a Certificate Authority. They're all unique and special!", width=70, choices=choices) + if code != display.OK: + sys.exit(0) - if result[0] != 0: - sys.exit(1) - - return result + return selection def get_cas(self): DV_choices = [] @@ -542,6 +535,7 @@ class Client(object): # random.shuffle(OV_choices) # random.shuffle(EV_choices) choices = DV_choices + OV_choices + EV_choices + choices = [(l[0], l[1]) for l in choices] except IOError as e: logger.fatal("Unable to find .ca_offerings file") diff --git a/trustify/client/display.py b/trustify/client/display.py index 9258d5dae..6eb35d443 100644 --- a/trustify/client/display.py +++ b/trustify/client/display.py @@ -2,8 +2,7 @@ import dialog from trustify.client import logger - -WIDTH = 70 +WIDTH = 70 HEIGHT = 16 class SingletonD(object): @@ -16,6 +15,12 @@ class SingletonD(object): class Display(SingletonD): + def generic_notification(self, message): + raise Exception("Error no display defined") + def generic_menu(self, message, choices, input_text): + raise Exception("Error no display defined") + def filter_names(self, names): + raise Exception("Error no display defined") def success_installation(self, domains): raise Exception("Error no display defined") def redirect_by_default(self): @@ -37,10 +42,12 @@ class Display(SingletonD): return result + def display_certs(self, certs): + raise Exception("Error no display define") + def confirm_revocation(self, cert): raise Exception("Error no display defined") - def cert_info_frame(self, cert): text = "-" * (WIDTH - 4) + "\n" text += self.cert_info_string(cert) @@ -60,27 +67,61 @@ class Display(SingletonD): def more_info_cert(self, cert): raise Exception("Error no display defined") - -import dialog + class NcursesDisplay(Display): + import dialog def __init__(self): self.d = dialog.Dialog() + def generic_notification(self, message): + self.d.msgbox(message, width=WIDTH, height=HEIGHT) + + def generic_menu(self, message, choices, input_text): + + return self.d.menu(message, choices = choices, + width=WIDTH, height=HEIGHT) + + def filter_names(self, names): + choices = [(n, "", 0) for n in names] + c, s = self.d.checklist("Which names would you like to activate \ + HTTPS for?", choices=choices) + + return c, s + + def success_installation(self, domains): - self.d.msgbox("\nCongratulations! You have successfully enabled " + self.gen_https_names(domains) + "!", width=WIDTH) - + self.d.msgbox("\nCongratulations! You have successfully enabled " + + self.gen_https_names(domains) + "!", width=WIDTH) + + def display_certs(self, certs): + menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] + " - " + + str(c["not_before"])[:-6]) + for i, c in enumerate(certs)] + + c, s = self.d.menu("Which certificate would you like to revoke?", + choices = menu_choices, help_button=True, + help_label="More Info", ok_label="Revoke", + width=WIDTH, height=HEIGHT) + return c, s + 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.d.menu("Please choose whether HTTPS access is required or optional.", width=WIDTH, choices=choices) + choices = [ + ("Easy", "Allow both HTTP and HTTPS access to these sites"), + ("Secure", "Make all requests redirect to secure HTTPS access")] + + result = self.d.menu("Please choose whether HTTPS access is required \ + or optional.", width=WIDTH, choices=choices) + if result[0] != 0: return False - return result[1] == "Secure" + return result[1] == "Secure" def confirm_revocation(self, cert): - text = "Are you sure you would like to revoke the following certificate:\n" + text = "Are you sure you would like to revoke the following \ + certificate:\n" text += self.cert_info_frame(cert) text += "This action cannot be reversed!" a = self.d.yesno(text, width=WIDTH, height=HEIGHT) @@ -96,30 +137,110 @@ class FileDisplay(Display): def __init__(self, outfile): self.outfile = outfile + def generic_notification(self, message): + side_frame = '-' * WIDTH - 4 + self.outfile.write("\n%s\n%s\n%s\n" % (side_frame, message, side_frame)) + + def generic_menu(self, message, choices, input_text): + self.outfile.write("\n%s\n" % message) + side_frame = '-' * (WIDTH - 4) + self.outfile.write("%s\n" % side_frame) + + for i, c in enumerate(choices): + self.outfile.write("%d: %s\n" % (i, c)) + + self.outfile.write("%s\n" % side_frame) + + code, selection = self.__get_valid_int_ans("Enter the number of a \ + Certificate Authority (c to cancel): ") + + return code, selection + + def filter_names(self, names): + c, s = self.generic_menu( + "Choose the names would you like to upgrade to HTTPS?", + names, + "Select the number of the name (c to cancel): ") + + # Make sure to return a list... + return c, [s] + + def display_certs(self, certs): + menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] + + " - " + str(c["not_before"])[:-6]) + for i, c in enumerate(certs)] + + self.outfile.write("Which certificate would you like to revoke?\n") + for c in menu_choices: + self.outfile.write("%s: %s - %s Signed (UTC): %s\n" + % (c[0], c[1], c[2], c[3])) + + return self.__get_valid_int_ans("Revoke Number (c to cancel): ") + + def __get_valid_int_ans(self, input_string): + valid_ans = False + + while not valid_ans: + + ans = raw_input(input_string) + if ans.startswith('c') or ans.startswith('C'): + code = CANCEL + selection = -1 + else: + try: + selection = int(ans) + code = OK + except ValueError: + self.outfile("Please input a number, \ + or the letter c to cancel") + + valid_ans = True + + return code, selection + + def success_installation(self, domains): - outfile.write("Congratulations! You have successfully enabled " + self.gen_https_names(domains) + "!\n") + self.outfile.write("Congratulations! You have successfully enabled " + + self.gen_https_names(domains) + "!\n") def redirect_by_default(self): - ans = raw_input("Would you like to redirect all normal HTTP traffic to HTTPS? y/n") + ans = raw_input("Would you like to redirect all \ + normal HTTP traffic to HTTPS? y/n") return ans.startswith('y') or ans.startswith('Y') def confirm_revocation(self, cert): - outfile.write("Are you sure you would like to revoke the following certificate:\n") - outfile.write(self.cert_info_frame(cert)) - outfile("This action cannot be reversed!\n"); + self.outfile.write("Are you sure you would like to revoke \ + the following certificate:\n") + self.outfile.write(self.cert_info_frame(cert)) + self.outfile("This action cannot be reversed!\n"); ans = raw_input("y/n") return ans.startswith('y') or ans.startswith('Y') def more_info_cert(self, cert): - outfile.write("\nCertificate Information:\n") - outfile.write(self.cert_info_frame(cert)) + self.outfile.write("\nCertificate Information:\n") + self.outfile.write(self.cert_info_frame(cert)) display = None +OK = 0 +CANCEL = 1 +HELP = "help" + def setDisplay(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 filter_names(names): + return display.filter_names(names) + +def display_certs(certs): + return display.display_certs(certs) def cert_info_string(cert): return display.cert_info_string(cert)