identity: present cert instead of waiting for 60
This commit is contained in:
parent
57f01720d6
commit
8b1561e689
|
@ -17,7 +17,9 @@ from bebop.colors import ColorPair, init_colors
|
||||||
from bebop.command_line import CommandLine
|
from bebop.command_line import CommandLine
|
||||||
from bebop.external import open_external_program
|
from bebop.external import open_external_program
|
||||||
from bebop.help import HELP_PAGE
|
from bebop.help import HELP_PAGE
|
||||||
|
from bebop.fs import get_identities_list_path
|
||||||
from bebop.history import History
|
from bebop.history import History
|
||||||
|
from bebop.identity import load_identities
|
||||||
from bebop.links import Links
|
from bebop.links import Links
|
||||||
from bebop.mime import MimeType
|
from bebop.mime import MimeType
|
||||||
from bebop.mouse import ButtonState
|
from bebop.mouse import ButtonState
|
||||||
|
@ -43,12 +45,13 @@ class Browser:
|
||||||
- status_data: 3-uple of status text, color pair and attributes of the
|
- 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.
|
- 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;
|
- 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
|
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
|
the page is accessed, and the optional "source" key maps to callable
|
||||||
returning the page source path.
|
returning the page source path.
|
||||||
- last_download: tuple of MimeType and path, or None.
|
- last_download: tuple of MimeType and path, or None.
|
||||||
|
- identities: identities map.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, cert_stash):
|
def __init__(self, config, cert_stash):
|
||||||
|
@ -65,6 +68,7 @@ class Browser:
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self.special_pages = self.setup_special_pages()
|
self.special_pages = self.setup_special_pages()
|
||||||
self.last_download: Optional[Tuple[MimeType, Path]] = None
|
self.last_download: Optional[Tuple[MimeType, Path]] = None
|
||||||
|
self.identities = load_identities(get_identities_list_path()) or {}
|
||||||
self._current_url = ""
|
self._current_url = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -26,7 +26,7 @@ def open_gemini_url(
|
||||||
url: str,
|
url: str,
|
||||||
redirects: int =0,
|
redirects: int =0,
|
||||||
use_cache: bool =True,
|
use_cache: bool =True,
|
||||||
identity=None
|
cert_and_key=None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Open a Gemini URL and set the formatted response as content.
|
"""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.
|
- url: a valid URL with Gemini scheme to open.
|
||||||
- redirects: current amount of redirections done to open the initial URL.
|
- 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.
|
- 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:
|
Returns:
|
||||||
The final successfully handled URL on success, None otherwise. Redirected
|
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}…"
|
loading_message = f"{loading_message_verb} {url}…"
|
||||||
browser.set_status(loading_message)
|
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:
|
if use_cache and url in browser.cache:
|
||||||
browser.load_page(browser.cache[url])
|
browser.load_page(browser.cache[url])
|
||||||
browser.current_url = url
|
browser.current_url = url
|
||||||
browser.set_status(url)
|
browser.set_status(url)
|
||||||
return 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"]
|
connect_timeout = browser.config["connect_timeout"]
|
||||||
connected = req.connect(connect_timeout)
|
connected = req.connect(connect_timeout)
|
||||||
if not connected:
|
if not connected:
|
||||||
|
@ -281,21 +288,20 @@ def _handle_cert_required(
|
||||||
The result of `open_gemini_url` with the client certificate provided.
|
The result of `open_gemini_url` with the client certificate provided.
|
||||||
"""
|
"""
|
||||||
identities = load_identities(get_identities_list_path())
|
identities = load_identities(get_identities_list_path())
|
||||||
if isinstance(identities, str):
|
if not identities:
|
||||||
browser.set_status_error(f"Can't load identities: {identities}")
|
browser.set_status_error(f"Can't load identities.")
|
||||||
return None
|
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:
|
if not url_identities:
|
||||||
identity = create_identity(browser, url)
|
identity = create_identity(browser, url)
|
||||||
if not identity:
|
if not identity:
|
||||||
return None
|
return None
|
||||||
identities[url] = [identity]
|
browser.identities[url] = [identity]
|
||||||
save_identities(identities, get_identities_list_path())
|
save_identities(browser.identities, get_identities_list_path())
|
||||||
else:
|
else:
|
||||||
# TODO support multiple identities; for now we just use the first
|
identity = select_identity(url_identities)
|
||||||
# available.
|
|
||||||
identity = url_identities[0]
|
|
||||||
|
|
||||||
cert_path, key_path = get_cert_and_key(identity["id"])
|
cert_path, key_path = get_cert_and_key(identity["id"])
|
||||||
return open_gemini_url(
|
return open_gemini_url(
|
||||||
|
@ -303,10 +309,16 @@ def _handle_cert_required(
|
||||||
url,
|
url,
|
||||||
redirects=redirects + 1,
|
redirects=redirects + 1,
|
||||||
use_cache=False,
|
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):
|
def create_identity(browser: Browser, url: str):
|
||||||
"""Walk the user through identity creation.
|
"""Walk the user through identity creation.
|
||||||
|
|
||||||
|
|
|
@ -33,24 +33,24 @@ from typing import Optional, Union
|
||||||
from bebop.fs import get_identities_path, get_user_data_path
|
from bebop.fs import get_identities_path, get_user_data_path
|
||||||
|
|
||||||
|
|
||||||
def load_identities(identities_path: Path) -> Union[dict, str]:
|
def load_identities(identities_path: Path) -> Optional[dict]:
|
||||||
"""Return saved identities, else an error str."""
|
"""Return saved identities or None on error."""
|
||||||
identities = {}
|
identities = {}
|
||||||
try:
|
try:
|
||||||
with open(identities_path, "rt") as identities_file:
|
with open(identities_path, "rt") as identities_file:
|
||||||
identities = json.load(identities_file)
|
identities = json.load(identities_file)
|
||||||
except (OSError, ValueError) as exc:
|
except (OSError, ValueError) as exc:
|
||||||
return f"Failed to load identities '{identities_path}': {exc}"
|
return None
|
||||||
return identities
|
return identities
|
||||||
|
|
||||||
|
|
||||||
def save_identities(identities: dict, identities_path: Path):
|
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:
|
try:
|
||||||
with open(identities_path, "wt") as identities_file:
|
with open(identities_path, "wt") as identities_file:
|
||||||
json.dump(identities, identities_file)
|
json.dump(identities, identities_file)
|
||||||
except (OSError, ValueError) as exc:
|
except (OSError, ValueError) as exc:
|
||||||
return f"Failed to save identities '{identities_path}': {exc}"
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,8 +102,8 @@ def create_certificate(url: str, common_name: str):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
command,
|
command,
|
||||||
# stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
# stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
error = "Could not create certificate: " + str(exc)
|
error = "Could not create certificate: " + str(exc)
|
||||||
|
|
Reference in a new issue