codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import hmac4import logging5import secrets6import webbrowser7from collections.abc import Sequence8from typing import TYPE_CHECKING9 10import argon211 12from mitmproxy import ctx13from mitmproxy import exceptions14from mitmproxy.tools.web.web_columns import AVAILABLE_WEB_COLUMNS15 16if TYPE_CHECKING:17 from mitmproxy.tools.web.master import WebMaster18 19logger = logging.getLogger(__name__)20 21 22class WebAuth:23 _password: str24 _hasher: argon2.PasswordHasher25 26 def __init__(self):27 self._password = secrets.token_hex(16)28 self._hasher = argon2.PasswordHasher()29 30 def load(self, loader):31 loader.add_option(32 "web_password",33 str,34 "",35 "Password to protect the mitmweb user interface. "36 "Values starting with `$` are interpreted as an argon2 hash, "37 "everything else is considered a plaintext password. "38 "If no password is provided, a random token is generated on startup."39 "For automated calls, you can pass the password as token query parameter"40 "or as `Authorization: Bearer ...` header.",41 )42 43 def configure(self, updated) -> None:44 if "web_password" in updated:45 if ctx.options.web_password.startswith("$"):46 try:47 argon2.extract_parameters(ctx.options.web_password)48 except argon2.exceptions.InvalidHashError:49 raise exceptions.OptionsError(50 "`web_password` starts with `$`, but it's not a valid argon2 hash."51 )52 elif ctx.options.web_password:53 logger.warning(54 "Using a plaintext password to protect the mitmweb user interface. "55 "Consider using an argon2 hash for `web_password` instead."56 )57 self._password = ctx.options.web_password or secrets.token_hex(16)58 59 @property60 def web_url(self) -> str:61 if ctx.options.web_password:62 auth = "" # We don't want to print plaintext passwords (and it doesn't work for argon2 anyhow).63 else:64 auth = f"?token={self._password}"65 web_host = ctx.options.web_host66 if ":" in web_host: # ipv667 web_host = f"[{web_host}]"68 # noinspection HttpUrlsUsage69 return f"http://{web_host}:{ctx.options.web_port}/{auth}"70 71 @staticmethod72 def auth_cookie_name() -> str:73 return f"mitmproxy-auth-{ctx.options.web_port}"74 75 def is_valid_password(self, password: str) -> bool:76 if self._password.startswith("$"):77 try:78 return self._hasher.verify(self._password, password)79 except argon2.exceptions.VerificationError:80 return False81 else:82 return hmac.compare_digest(83 self._password,84 password,85 )86 87 88class WebAddon:89 def load(self, loader):90 loader.add_option("web_open_browser", bool, True, "Start a browser.")91 loader.add_option("web_debug", bool, False, "Enable mitmweb debugging.")92 loader.add_option("web_port", int, 8081, "Web UI port.")93 loader.add_option("web_host", str, "127.0.0.1", "Web UI host.")94 loader.add_option(95 "web_columns",96 Sequence[str],97 ["tls", "icon", "path", "method", "status", "size", "time"],98 f"Columns to show in the flow list. Can be one of the following: {', '.join(AVAILABLE_WEB_COLUMNS)}",99 )100 101 def running(self):102 if hasattr(ctx.options, "web_open_browser") and ctx.options.web_open_browser:103 master: WebMaster = ctx.master # type: ignore104 success = open_browser(master.web_url)105 if not success:106 logger.info(107 f"No web browser found. Please open a browser and point it to {master.web_url}",108 )109 if not success and not ctx.options.web_password:110 logger.info(111 f"You can configure a fixed authentication token by setting the `web_password` option "112 f"(https://docs.mitmproxy.org/stable/concepts-options/#web_password).",113 )114 115 116def open_browser(url: str) -> bool:117 """118 Open a URL in a browser window.119 In contrast to webbrowser.open, we limit the list of suitable browsers.120 This gracefully degrades to a no-op on headless servers, where webbrowser.open121 would otherwise open lynx.122 123 Returns:124 True, if a browser has been opened125 False, if no suitable browser has been found.126 """127 browsers = (128 "windows-default",129 "macosx",130 "wslview %s",131 "gio",132 "x-www-browser",133 "gnome-open %s",134 "xdg-open",135 "google-chrome",136 "chrome",137 "chromium",138 "chromium-browser",139 "firefox",140 "opera",141 "safari",142 )143 for browser in browsers:144 try:145 b = webbrowser.get(browser)146 except webbrowser.Error:147 pass148 else:149 if b.open(url):150 return True151 return False152 