From 8b1561e6890964e852d8df63bc5efd80fe443c58 Mon Sep 17 00:00:00 2001 From: dece Date: Thu, 13 May 2021 01:24:29 +0200 Subject: [PATCH] identity: present cert instead of waiting for 60 --- bebop/browser/browser.py | 14 +++++++++----- bebop/browser/gemini.py | 36 ++++++++++++++++++++++++------------ bebop/identity.py | 14 +++++++------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/bebop/browser/browser.py b/bebop/browser/browser.py index f164fb5..beaafd3 100644 --- a/bebop/browser/browser.py +++ b/bebop/browser/browser.py @@ -17,7 +17,9 @@ from bebop.colors import ColorPair, init_colors from bebop.command_line import CommandLine from bebop.external import open_external_program from bebop.help import HELP_PAGE +from bebop.fs import get_identities_list_path from bebop.history import History +from bebop.identity import load_identities from bebop.links import Links from bebop.mime import MimeType from bebop.mouse import ButtonState @@ -41,14 +43,15 @@ class Browser: - command_line: a CommandLine object for the user to interact with. - running: the browser will continue running while this is true. - status_data: 3-uple of status text, color pair and attributes of the - status line, used to reset status after an error. + status line, used to reset status after an error. - history: an History object. - - cache: a dict containing cached pages + - cache: a dict containing cached pages. - special_pages: a dict containing page names used with "bebop" scheme; - values are dicts as well: the "open" key maps to a callable to use when - the page is accessed, and the optional "source" key maps to callable - returning the page source path. + values are dicts as well: the "open" key maps to a callable to use when + the page is accessed, and the optional "source" key maps to callable + returning the page source path. - last_download: tuple of MimeType and path, or None. + - identities: identities map. """ def __init__(self, config, cert_stash): @@ -65,6 +68,7 @@ class Browser: self.cache = {} self.special_pages = self.setup_special_pages() self.last_download: Optional[Tuple[MimeType, Path]] = None + self.identities = load_identities(get_identities_list_path()) or {} self._current_url = "" @property diff --git a/bebop/browser/gemini.py b/bebop/browser/gemini.py index d3a456b..59e44b1 100644 --- a/bebop/browser/gemini.py +++ b/bebop/browser/gemini.py @@ -26,7 +26,7 @@ def open_gemini_url( url: str, redirects: int =0, use_cache: bool =True, - identity=None + cert_and_key=None ) -> Optional[str]: """Open a Gemini URL and set the formatted response as content. @@ -50,7 +50,7 @@ def open_gemini_url( - url: a valid URL with Gemini scheme to open. - redirects: current amount of redirections done to open the initial URL. - use_cache: if true, look up if the page is cached before requesting it. - - identity: if not None, a tuple of paths to a client cert/key to use. + - cert_and_key: if not None, a tuple of paths to a client cert/key to use. Returns: The final successfully handled URL on success, None otherwise. Redirected @@ -64,13 +64,20 @@ def open_gemini_url( loading_message = f"{loading_message_verb} {url}…" browser.set_status(loading_message) + # If this URL used to request an identity, provide it. + if not cert_and_key: + url_identities = get_identities_for_url(browser.identities, url) + identity = select_identity(url_identities) + if identity: + cert_and_key = get_cert_and_key(identity["id"]) + if use_cache and url in browser.cache: browser.load_page(browser.cache[url]) browser.current_url = url browser.set_status(url) return url - req = Request(url, browser.stash, identity=identity) + req = Request(url, browser.stash, identity=cert_and_key) connect_timeout = browser.config["connect_timeout"] connected = req.connect(connect_timeout) if not connected: @@ -281,21 +288,20 @@ def _handle_cert_required( The result of `open_gemini_url` with the client certificate provided. """ identities = load_identities(get_identities_list_path()) - if isinstance(identities, str): - browser.set_status_error(f"Can't load identities: {identities}") + if not identities: + browser.set_status_error(f"Can't load identities.") return None + browser.identities = identities - url_identities = get_identities_for_url(identities, url) + url_identities = get_identities_for_url(browser.identities, url) if not url_identities: identity = create_identity(browser, url) if not identity: return None - identities[url] = [identity] - save_identities(identities, get_identities_list_path()) + browser.identities[url] = [identity] + save_identities(browser.identities, get_identities_list_path()) else: - # TODO support multiple identities; for now we just use the first - # available. - identity = url_identities[0] + identity = select_identity(url_identities) cert_path, key_path = get_cert_and_key(identity["id"]) return open_gemini_url( @@ -303,10 +309,16 @@ def _handle_cert_required( url, redirects=redirects + 1, use_cache=False, - identity=(cert_path, key_path) + cert_and_key=(cert_path, key_path) ) +def select_identity(identities: list): + """Let user select the appropriate identity among candidates.""" + # TODO support multiple identities; for now we just use the first available. + return identities[0] if identities else None + + def create_identity(browser: Browser, url: str): """Walk the user through identity creation. diff --git a/bebop/identity.py b/bebop/identity.py index 1c23d48..f4db28b 100644 --- a/bebop/identity.py +++ b/bebop/identity.py @@ -33,24 +33,24 @@ from typing import Optional, Union from bebop.fs import get_identities_path, get_user_data_path -def load_identities(identities_path: Path) -> Union[dict, str]: - """Return saved identities, else an error str.""" +def load_identities(identities_path: Path) -> Optional[dict]: + """Return saved identities or None on error.""" identities = {} try: with open(identities_path, "rt") as identities_file: identities = json.load(identities_file) except (OSError, ValueError) as exc: - return f"Failed to load identities '{identities_path}': {exc}" + return None return identities def save_identities(identities: dict, identities_path: Path): - """Save the certificate stash. Return True on success, else an error str.""" + """Save the certificate stash. Return True on success.""" try: with open(identities_path, "wt") as identities_file: json.dump(identities, identities_file) except (OSError, ValueError) as exc: - return f"Failed to save identities '{identities_path}': {exc}" + return False return True @@ -102,8 +102,8 @@ def create_certificate(url: str, common_name: str): try: subprocess.check_call( command, - # stdout=subprocess.DEVNULL, - # stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) except subprocess.CalledProcessError as exc: error = "Could not create certificate: " + str(exc)