#!/bin/sh set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT INT TERM export HOME="$TMP_DIR/home" export LEMANA_VPN_BIN_DIR="$HOME/bin" export LEMANA_VPN_CONFIG_DIR="$HOME/.config/lemana-vpn" export OPENCONNECT_LITE_CONFIG_DIR="$HOME/.config/openconnect-lite" mkdir -p "$HOME" output="$(cd "$ROOT" && sh install.sh --dry-run --non-interactive --minimal)" printf '%s\n' "$output" | grep -q 'Detected state:' printf '%s\n' "$output" | grep -q 'Interactive prompts: off' printf '%s\n' "$output" | grep -q 'Modules: auth_profile=manual credential_source=none bitwarden=0 touchid=0 sudoers=1 shell=1 app=1 autostart=1' printf '%s\n' "$output" | grep -q 'Проверяю Homebrew-зависимости' printf '%s\n' "$output" | grep -q 'Swift build может занять минуту' printf '%s\n' "$output" | grep -q 'sudo install -d -m 755 -o root -g wheel /usr/local/sbin' printf '%s\n' "$output" | grep -q 'swift build -c release --package-path' printf '%s\n' "$output" | grep -q 'launchctl load' printf '%s\n' "$output" | grep -q 'restart LemanaVPN.app if running' printf '%s\n' "$output" | grep -q '+ cp .*/bin/vpn-lemanapro.sh ' if printf '%s\n' "$output" | grep -q 'curl -fsSL .*raw/branch/main'; then echo "checkout install dry-run should use local files, not remote main" >&2 exit 1 fi esc="$(printf '\033')" if printf '%s\n' "$output" | grep -q "$esc"; then echo "non-tty dry-run output contains ANSI color codes" >&2 exit 1 fi if command -v expect >/dev/null 2>&1; then interactive_output="$( ROOT="$ROOT" expect <<'EXPECT' set timeout 30 spawn sh $env(ROOT)/install.sh --dry-run --interactive --without-app --no-shell --no-sudoers expect "Как подключаться по умолчанию?" expect "Выбор" send "\r" expect "Сохранить логин и пароль" send "y\r" expect eof EXPECT )" printf '%s\n' "$interactive_output" | grep -q 'По умолчанию ничего не сохраняется' printf '%s\n' "$interactive_output" | grep -q 'Как подключаться по умолчанию?' printf '%s\n' "$interactive_output" | grep -q '1) Вводить всё вручную' printf '%s\n' "$interactive_output" | grep -q '2) Запомнить логин и пароль' printf '%s\n' "$interactive_output" | grep -q '3) Настроить автоматический вход' printf '%s\n' "$interactive_output" | grep -q 'Сохранить логин и пароль в macOS Keychain для подстановки при входе?' printf '%s\n' "$interactive_output" | grep -q 'Выбран режим: сохранить логин и пароль, одноразовый код вводить вручную' printf '%s\n' "$interactive_output" | grep -q '+ .*/vpn-lemanapro.sh --configure-login' if printf '%s\n' "$interactive_output" | grep -Eq 'credential_source|auth_profile|TOTP seed'; then echo "basic interactive wizard should not expose technical credential wording" >&2 exit 1 fi fi status_json="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)" printf '%s\n' "$status_json" | grep -q '"modules":' printf '%s\n' "$status_json" | grep -q '"app":' printf '%s\n' "$status_json" | grep -q '"app_menu":{"install_label":"Режим: полностью вручную","configured_mode":"manual","available_modes":\["configured"\],"setup_hint":null}' keychain_status_json="$(LEMANA_VPN_CREDENTIAL_SOURCE=keychain bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)" printf '%s\n' "$keychain_status_json" | grep -q '"credentials":{"source":"keychain","auth_profile":"manual","login_ready":false,"keychain_ready":false}' printf '%s\n' "$keychain_status_json" | grep -q '"bitwarden":{"enabled":false' printf '%s\n' "$keychain_status_json" | grep -q '"touchid":{"enabled":false' bitwarden_status_json="$(LEMANA_VPN_CREDENTIAL_SOURCE=bitwarden bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)" printf '%s\n' "$bitwarden_status_json" | grep -q '"credentials":{"source":"bitwarden","auth_profile":"manual","login_ready":false,"keychain_ready":false}' printf '%s\n' "$bitwarden_status_json" | grep -q '"bitwarden":{"enabled":true' status_password_bin="$TMP_DIR/status-password-bin" mkdir -p "$status_password_bin" cat > "$status_password_bin/security" <<'SH' #!/bin/sh if [ "${1:-}" = "find-generic-password" ]; then account="" while [ "$#" -gt 0 ]; do if [ "$1" = "-a" ]; then shift account="${1:-}" break fi shift done case "$account" in ""|totp/*) exit 1 ;; *) exit 0 ;; esac fi exit 1 SH chmod +x "$status_password_bin/security" status_full_bin="$TMP_DIR/status-full-bin" mkdir -p "$status_full_bin" cat > "$status_full_bin/security" <<'SH' #!/bin/sh if [ "${1:-}" = "find-generic-password" ]; then account="" while [ "$#" -gt 0 ]; do if [ "$1" = "-a" ]; then shift account="${1:-}" break fi shift done [ -n "$account" ] exit $? fi exit 1 SH chmod +x "$status_full_bin/security" saved_login_missing_status="$( LEMANA_VPN_USERNAME="lemana-status-login-missing" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --status --json )" printf '%s\n' "$saved_login_missing_status" | grep -q '"app_menu":{"install_label":"Режим: логин и пароль не настроены","configured_mode":"saved-login","available_modes":\["manual-full"\],"setup_hint":"Настройте в Terminal: vpn --configure-login"}' saved_login_ready_status="$( PATH="$status_password_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-status-login-ready" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --status --json )" printf '%s\n' "$saved_login_ready_status" | grep -q '"app_menu":{"install_label":"Режим: логин и пароль сохранены","configured_mode":"saved-login","available_modes":\["configured","manual-full"\],"setup_hint":null}' saved_totp_missing_status="$( LEMANA_VPN_USERNAME="lemana-status-totp-missing" \ LEMANA_VPN_AUTH_PROFILE=saved-totp \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --status --json )" printf '%s\n' "$saved_totp_missing_status" | grep -q '"app_menu":{"install_label":"Режим: сохранённые данные не настроены","configured_mode":"saved-totp","available_modes":\["manual-full"\],"setup_hint":"Настройте в Terminal: vpn --configure-keychain"}' saved_totp_ready_status="$( PATH="$status_full_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-status-totp-ready" \ LEMANA_VPN_AUTH_PROFILE=saved-totp \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --status --json )" printf '%s\n' "$saved_totp_ready_status" | grep -q '"app_menu":{"install_label":"Режим: сохранённые данные без автоотправки","configured_mode":"saved-totp","available_modes":\["configured","saved-login","manual-full","auto"\],"setup_hint":null}' auto_ready_status="$( PATH="$status_full_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-status-auto-ready" \ LEMANA_VPN_AUTH_PROFILE=auto \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --status --json )" printf '%s\n' "$auto_ready_status" | grep -q '"app_menu":{"install_label":"Режим: автоматический вход","configured_mode":"auto","available_modes":\["configured","saved-totp","saved-login","manual-full"\],"setup_hint":null}' personal_ldap='601''03293' if grep -R "$personal_ldap" "$ROOT/bin" "$ROOT/install.sh" "$ROOT/uninstall.sh" "$ROOT/README.md" "$ROOT/templates" "$ROOT/app" "$ROOT/.agents" >/dev/null; then echo "personal LDAP username must not be hardcoded in runtime, installer, app, docs, or agent guidance" >&2 exit 1 fi grep -q 'LemanaVPN-openconnect-lite.log' "$ROOT/bin/vpn-lemanapro.sh" grep -q '"event":"waiting"' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--patch-only' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--auto' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--manual' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--manual-full' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--manual-sso' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'LEMANA_VPN_AUTOFILL_CLICK' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'LEMANA_VPN_AUTOFILL_TOTP' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'vpn-auto' "$ROOT/install.sh" grep -q 'vpn-manual' "$ROOT/install.sh" grep -q 'vpn-manual-full' "$ROOT/install.sh" grep -q 'ForEach(vpnManager.availableLaunchModes)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'vpnManager.installModeLabel' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'vpnManager.installSetupHint' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'Button("Изменить логин и пароль")' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'vpnManager.configureLogin()' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" if grep -Fq 'connect(mode: .auto)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"; then echo "disconnected app menu should not hardcode the auto button" >&2 exit 1 fi if grep -Fq 'Button("Подставить только логин и пароль")' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"; then echo "disconnected app menu should not hardcode saved-login button visibility" >&2 exit 1 fi grep -q 'app_menu' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'available_modes' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'menuTitle' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'Подключить полностью вручную' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'case "auth_profile":' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'case "manual_sso", "auto_sso":' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'case "username_required", "username_saved":' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'func configureLogin()' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q -- '--configure-login' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'enum VPNLaunchMode' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'struct Credentials: Decodable' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'credential_source' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" fake_webengine="$TMP_DIR/webengine_process.py" fake_authenticator="$TMP_DIR/authenticator.py" cat > "$fake_webengine" <<'PY' import json import sys class Browser: def run(self, display_mode, credentials, url_pattern, rules): argv = sys.argv.copy() if display_mode == "hidden": argv += ["-platform", "minimal"] if credentials: logger.info("Initiating autologin", cred=credentials) for url_pattern, rules in auto_fill_rules.items(): script = QWebEngineScript() script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady) script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld) script.setSourceCode( f""" // ==UserScript== // @include {url_pattern} // ==/UserScript== function autoFill() {{ {get_selectors(rules, credentials)} setTimeout(autoFill, 1000); }} autoFill(); """ ) self.page().scripts().insert(script) def get_selectors(rules, credentials): statements = [] for rule in rules: selector = json.dumps(rule.selector) if rule.action == "stop": statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ return; }}""" ) elif rule.fill: value = json.dumps(getattr(credentials, rule.fill, None)) if value: statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("blur")); }}""" ) else: logger.warning( "Credential info not available", type=rule.fill, possibilities=dir(credentials), ) elif rule.action == "click": statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.click(); }}""" ) return "\n".join(statements) PY cat > "$fake_authenticator" <<'PY' import requests import structlog logger = structlog.get_logger() class Authenticator: def _detect_authentication_target_url(self): # Follow possible redirects in a GET request # Authentication will occur using a POST request on the final URL response = requests.get(self.host.vpn_url) response.raise_for_status() self.host.address = response.url logger.debug("Auth target url", url=self.host.vpn_url) def _start_authentication(self): pass PY LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/patch-backups" \ bash "$ROOT/bin/vpn-lemanapro.sh" --patch-only >/dev/null grep -q '"offscreen"' "$fake_webengine" grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$fake_webengine" grep -q 'new RegExp' "$fake_webengine" grep -q 'script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)' "$fake_webengine" grep -q 'new Event("input", {{bubbles: true}})' "$fake_webengine" grep -q 'LEMANA_VPN_AUTOFILL_CLICK' "$fake_webengine" grep -q 'os.environ.get("LEMANA_VPN_AUTOFILL_CLICK", "1") != "0"' "$fake_webengine" grep -q 'LEMANA_VPN_AUTOFILL_TOTP' "$fake_webengine" grep -q 'rule.fill == "totp"' "$fake_webengine" if grep -q 'ScriptWorldId.MainWorld' "$fake_webengine"; then echo "patched auto-fill should keep the original ApplicationWorld behavior" >&2 exit 1 fi if grep -q '__lemanaVpnClicked' "$fake_webengine"; then echo "patched auto-fill should stay stateless like the original working setup" >&2 exit 1 fi if grep -q 'valueSetter' "$fake_webengine"; then echo "patched auto-fill should use the original direct value assignment with input/change events" >&2 exit 1 fi grep -q 'from urllib.parse import urljoin' "$fake_authenticator" grep -q 'self.session.get(self.host.vpn_url, allow_redirects=False)' "$fake_authenticator" grep -q 'response.headers.get("Location")' "$fake_authenticator" if grep -q 'requests.get(self.host.vpn_url)' "$fake_authenticator"; then echo "auth target detection must not follow redirects with bare requests.get" >&2 exit 1 fi status_text="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status)" printf '%s\n' "$status_text" | grep -q 'Modules:' printf '%s\n' "$status_text" | grep -q 'core=' printf '%s\n' "$status_text" | grep -q 'app=' printf '%s\n' "$status_text" | grep -q 'autostart=' uninstall_home="$TMP_DIR/uninstall-home" mkdir -p "$uninstall_home" uninstall_output="$( HOME="$uninstall_home" \ LEMANA_VPN_BIN_DIR="$uninstall_home/bin" \ LEMANA_VPN_CONFIG_DIR="$uninstall_home/.config/lemana-vpn" \ OPENCONNECT_LITE_CONFIG_DIR="$uninstall_home/.config/openconnect-lite" \ sh "$ROOT/uninstall.sh" --dry-run --remove-keychain --remove-touchid-helper --remove-openconnect-lite )" printf '%s\n' "$uninstall_output" | grep -q 'Начинаю удаление Lemana VPN' printf '%s\n' "$uninstall_output" | grep -q 'Проверяю runtime-патчи openconnect-lite' printf '%s\n' "$uninstall_output" | grep -q 'Удаляю sudoers и DNS cleanup wrapper' printf '%s\n' "$uninstall_output" | grep -q 'killall LemanaVPN # if running' printf '%s\n' "$uninstall_output" | grep -q 'Удаляю VPN-записи из macOS Keychain' if printf '%s\n' "$uninstall_output" | grep -q "$esc"; then echo "non-tty uninstall dry-run output contains ANSI color codes" >&2 exit 1 fi missing_user="lemana-smoke-missing-$$" set +e manual_output="$( HOME="$HOME" \ LEMANA_VPN_USERNAME="$missing_user" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --json 2>&1 )" manual_code=$? set -e [ "$manual_code" -ne 0 ] printf '%s\n' "$manual_output" | grep -q '"event":"keychain_required"' printf '%s\n' "$manual_output" | grep -q 'vpn --configure-login' if printf '%s\n' "$manual_output" | grep -q 'Cleaning up VPN DNS'; then echo "missing saved login should fail before VPN cleanup trap is installed" >&2 exit 1 fi missing_username_home="$TMP_DIR/missing-username-home" mkdir -p "$missing_username_home" set +e missing_username_output="$( HOME="$missing_username_home" \ LEMANA_VPN_CONFIG_DIR="$missing_username_home/.config/lemana-vpn" \ OPENCONNECT_LITE_CONFIG_DIR="$missing_username_home/.config/openconnect-lite" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ bash "$ROOT/bin/vpn-lemanapro.sh" --json 2>&1 )" missing_username_code=$? set -e [ "$missing_username_code" -ne 0 ] printf '%s\n' "$missing_username_output" | grep -q '"event":"username_required"' printf '%s\n' "$missing_username_output" | grep -q 'LDAP username is missing' if printf '%s\n' "$missing_username_output" | grep -q '"event":"keychain_required"'; then echo "missing username should fail before checking Keychain credentials" >&2 exit 1 fi fake_path_bin="$TMP_DIR/fake-path-bin" mkdir -p "$fake_path_bin" cat > "$fake_path_bin/security" <<'SH' #!/bin/sh if [ "${1:-}" = "find-generic-password" ]; then account="" while [ "$#" -gt 0 ]; do if [ "$1" = "-a" ]; then shift account="${1:-}" break fi shift done [ -n "$account" ] exit $? fi exit 1 SH chmod +x "$fake_path_bin/security" fake_oc_bin="$TMP_DIR/fake-openconnect-lite" manual_full_capture="$TMP_DIR/manual-full-capture" cat > "$fake_oc_bin" <<'SH' #!/bin/sh { printf 'args=%s\n' "$*" printf 'autofill_disable=%s\n' "${LEMANA_VPN_AUTOFILL_DISABLE:-}" printf 'autofill_click=%s\n' "${LEMANA_VPN_AUTOFILL_CLICK:-}" printf 'autofill_totp=%s\n' "${LEMANA_VPN_AUTOFILL_TOTP:-}" config_home="${XDG_CONFIG_HOME:-$HOME/.config}" config_file="$config_home/openconnect-lite/config.toml" printf 'xdg_config_home=%s\n' "$config_home" if [ -f "$config_file" ]; then if grep -q '^\[credentials\]' "$config_file"; then printf 'credentials_block=1\n' else printf 'credentials_block=0\n' fi if grep -q 'username = ""' "$config_file"; then printf 'empty_username=1\n' else printf 'empty_username=0\n' fi else printf 'credentials_block=missing\n' printf 'empty_username=missing\n' fi } > "$LEMANA_VPN_CAPTURE_LAUNCH" exit 0 SH chmod +x "$fake_oc_bin" fake_missing_keychain_bin="$TMP_DIR/fake-missing-keychain-bin" mkdir -p "$fake_missing_keychain_bin" cat > "$fake_missing_keychain_bin/security" <<'SH' #!/bin/sh exit 1 SH chmod +x "$fake_missing_keychain_bin/security" default_manual_home="$TMP_DIR/default-manual-home" default_manual_capture="$TMP_DIR/default-manual-capture" mkdir -p "$default_manual_home" mkdir -p "$default_manual_home/.config/openconnect-lite" cat > "$default_manual_home/.config/openconnect-lite/config.toml" <<'TOML' on_disconnect = "true" [default_profile] address = "vpn.lemanapro.ru" user_group = "" name = "Default-K" [credentials] username = "" TOML default_manual_output="$( HOME="$default_manual_home" \ PATH="$fake_missing_keychain_bin:$PATH" \ LEMANA_VPN_CONFIG_DIR="$default_manual_home/.config/lemana-vpn" \ OPENCONNECT_LITE_CONFIG_DIR="$default_manual_home/.config/openconnect-lite" \ LEMANA_VPN_OC_BIN="$fake_oc_bin" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/default-manual-patch-backups" \ LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/no-dns-cleanup" \ LEMANA_VPN_CONNECT_LOG="$TMP_DIR/default-manual.log" \ LEMANA_VPN_CAPTURE_LAUNCH="$default_manual_capture" \ bash "$ROOT/bin/vpn-lemanapro.sh" --json )" printf '%s\n' "$default_manual_output" | grep -q '"event":"manual_sso","auth_profile":"manual","autofill":false,"submit":false' if printf '%s\n' "$default_manual_output" | grep -Eq '"event":"(username_required|keychain_required)"'; then echo "default manual mode should open the browser without saved username or Keychain credentials" >&2 exit 1 fi grep -q -- '--browser-display-mode shown' "$default_manual_capture" grep -q '^autofill_disable=1$' "$default_manual_capture" grep -q '^autofill_click=0$' "$default_manual_capture" grep -q '^autofill_totp=0$' "$default_manual_capture" grep -q '^credentials_block=0$' "$default_manual_capture" grep -q '^empty_username=0$' "$default_manual_capture" fake_login_keychain_bin="$TMP_DIR/fake-login-keychain-bin" mkdir -p "$fake_login_keychain_bin" cat > "$fake_login_keychain_bin/security" <<'SH' #!/bin/sh if [ "${1:-}" = "find-generic-password" ]; then account="" while [ "$#" -gt 0 ]; do if [ "$1" = "-a" ]; then shift account="${1:-}" break fi shift done case "$account" in ""|totp/*) exit 1 ;; *) exit 0 ;; esac fi exit 1 SH chmod +x "$fake_login_keychain_bin/security" saved_login_capture="$TMP_DIR/saved-login-capture" saved_login_output="$( HOME="$HOME" \ PATH="$fake_login_keychain_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-saved-login-$$" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_BIN="$fake_oc_bin" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/saved-login-patch-backups" \ LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/no-dns-cleanup" \ LEMANA_VPN_CONNECT_LOG="$TMP_DIR/saved-login.log" \ LEMANA_VPN_CAPTURE_LAUNCH="$saved_login_capture" \ bash "$ROOT/bin/vpn-lemanapro.sh" --json )" printf '%s\n' "$saved_login_output" | grep -q '"event":"manual_sso","auth_profile":"saved-login","autofill":true,"totp":false,"submit":false' if printf '%s\n' "$saved_login_output" | grep -q '"event":"keychain_required"'; then echo "saved-login should not require a TOTP seed" >&2 exit 1 fi grep -q -- '--browser-display-mode shown' "$saved_login_capture" grep -q '^autofill_disable=0$' "$saved_login_capture" grep -q '^autofill_click=0$' "$saved_login_capture" grep -q '^autofill_totp=0$' "$saved_login_capture" saved_totp_capture="$TMP_DIR/saved-totp-capture" saved_totp_output="$( HOME="$HOME" \ PATH="$fake_path_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-saved-totp-$$" \ LEMANA_VPN_AUTH_PROFILE=saved-totp \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_BIN="$fake_oc_bin" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/saved-totp-patch-backups" \ LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/no-dns-cleanup" \ LEMANA_VPN_CONNECT_LOG="$TMP_DIR/saved-totp.log" \ LEMANA_VPN_CAPTURE_LAUNCH="$saved_totp_capture" \ bash "$ROOT/bin/vpn-lemanapro.sh" --json )" printf '%s\n' "$saved_totp_output" | grep -q '"event":"manual_sso","auth_profile":"saved-totp","autofill":true,"totp":true,"submit":false' grep -q -- '--browser-display-mode shown' "$saved_totp_capture" grep -q '^autofill_disable=0$' "$saved_totp_capture" grep -q '^autofill_click=0$' "$saved_totp_capture" grep -q '^autofill_totp=1$' "$saved_totp_capture" manual_full_no_credentials_home="$TMP_DIR/manual-full-no-credentials-home" manual_full_no_credentials_capture="$TMP_DIR/manual-full-no-credentials-capture" mkdir -p "$manual_full_no_credentials_home" mkdir -p "$manual_full_no_credentials_home/.config/openconnect-lite" cat > "$manual_full_no_credentials_home/.config/openconnect-lite/config.toml" <<'TOML' on_disconnect = "true" [default_profile] address = "vpn.lemanapro.ru" user_group = "" name = "Default-K" [credentials] username = "" TOML manual_full_no_credentials_output="$( HOME="$manual_full_no_credentials_home" \ PATH="$fake_missing_keychain_bin:$PATH" \ LEMANA_VPN_CONFIG_DIR="$manual_full_no_credentials_home/.config/lemana-vpn" \ OPENCONNECT_LITE_CONFIG_DIR="$manual_full_no_credentials_home/.config/openconnect-lite" \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_BIN="$fake_oc_bin" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/manual-full-no-credentials-patch-backups" \ LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/no-dns-cleanup" \ LEMANA_VPN_CONNECT_LOG="$TMP_DIR/manual-full-no-credentials.log" \ LEMANA_VPN_CAPTURE_LAUNCH="$manual_full_no_credentials_capture" \ bash "$ROOT/bin/vpn-lemanapro.sh" --manual-full --json )" printf '%s\n' "$manual_full_no_credentials_output" | grep -q '"event":"manual_sso","auth_profile":"manual","autofill":false,"submit":false' if printf '%s\n' "$manual_full_no_credentials_output" | grep -Eq '"event":"(username_required|keychain_required)"'; then echo "manual-full should not require saved username or Keychain credentials before opening the browser" >&2 exit 1 fi grep -q -- '--browser-display-mode shown' "$manual_full_no_credentials_capture" grep -q '^autofill_disable=1$' "$manual_full_no_credentials_capture" grep -q '^autofill_click=0$' "$manual_full_no_credentials_capture" grep -q '^autofill_totp=0$' "$manual_full_no_credentials_capture" grep -q '^credentials_block=0$' "$manual_full_no_credentials_capture" grep -q '^empty_username=0$' "$manual_full_no_credentials_capture" if command -v expect >/dev/null 2>&1; then first_username_home="$TMP_DIR/first-username-home" mkdir -p "$first_username_home/.config/lemana-vpn" "$first_username_home/.config/openconnect-lite" printf 'LEMANA_VPN_CREDENTIAL_SOURCE="keychain"\nLEMANA_VPN_USE_BITWARDEN="0"\nLEMANA_VPN_USE_TOUCHID="0"\n' > "$first_username_home/.config/lemana-vpn/env" printf 'username = ""\n' > "$first_username_home/.config/openconnect-lite/config.toml" FIRST_USERNAME_HOME="$first_username_home" \ ROOT="$ROOT" \ FAKE_PATH_BIN="$fake_path_bin" \ FAKE_OC_BIN="$fake_oc_bin" \ FAKE_WEBENGINE="$fake_webengine" \ FAKE_AUTHENTICATOR="$fake_authenticator" \ MANUAL_FULL_CAPTURE="$manual_full_capture" \ TMP_DIR="$TMP_DIR" \ expect <<'EXPECT' set timeout 30 log_user 0 set home $env(FIRST_USERNAME_HOME) spawn env \ HOME=$home \ PATH=$env(FAKE_PATH_BIN):$env(PATH) \ LEMANA_VPN_CONFIG_DIR=$home/.config/lemana-vpn \ OPENCONNECT_LITE_CONFIG_DIR=$home/.config/openconnect-lite \ LEMANA_VPN_OC_BIN=$env(FAKE_OC_BIN) \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS=$env(FAKE_WEBENGINE) \ LEMANA_VPN_AUTHENTICATOR=$env(FAKE_AUTHENTICATOR) \ LEMANA_VPN_PATCH_BACKUP_DIR=$env(TMP_DIR)/first-username-patch-backups \ LEMANA_VPN_DNS_CLEANUP=$env(TMP_DIR)/no-dns-cleanup \ LEMANA_VPN_CONNECT_LOG=$env(TMP_DIR)/first-username.log \ LEMANA_VPN_CAPTURE_LAUNCH=$env(MANUAL_FULL_CAPTURE) \ bash $env(ROOT)/bin/vpn-lemanapro.sh --manual --json expect "Corporate LDAP username:" send "lemana-first-user\r" expect eof EXPECT grep -q 'LEMANA_VPN_USERNAME="lemana-first-user"' "$first_username_home/.config/lemana-vpn/env" grep -q 'username = "lemana-first-user"' "$first_username_home/.config/openconnect-lite/config.toml" fi manual_full_output="$( HOME="$HOME" \ PATH="$fake_path_bin:$PATH" \ LEMANA_VPN_USERNAME="lemana-manual-full-$$" \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_BIN="$fake_oc_bin" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/manual-full-patch-backups" \ LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/no-dns-cleanup" \ LEMANA_VPN_CONNECT_LOG="$TMP_DIR/manual-full.log" \ LEMANA_VPN_CAPTURE_LAUNCH="$manual_full_capture" \ bash "$ROOT/bin/vpn-lemanapro.sh" --manual-full --json )" printf '%s\n' "$manual_full_output" | grep -q '"event":"manual_sso","auth_profile":"manual","autofill":false,"submit":false' grep -q -- '--browser-display-mode shown' "$manual_full_capture" grep -q '^autofill_disable=1$' "$manual_full_capture" grep -q '^autofill_click=0$' "$manual_full_capture" grep -q '^autofill_totp=0$' "$manual_full_capture" fake_oc_python="$TMP_DIR/fake-oc-python" captured_totp="$TMP_DIR/captured-totp" cat > "$fake_oc_python" <<'SH' #!/bin/sh cat >/dev/null printf '%s' "${_VPN_TOTP_SECRET:-}" > "$LEMANA_VPN_CAPTURE_TOTP" SH chmod +x "$fake_oc_python" configure_output="$( printf 'ldap-password\notpauth://totp/Lemana:test?secret=abcd2345efgh6723&issuer=Lemana\n' | HOME="$HOME" \ LEMANA_VPN_USERNAME="lemana-configure-$$" \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_PYTHON="$fake_oc_python" \ LEMANA_VPN_CAPTURE_TOTP="$captured_totp" \ bash "$ROOT/bin/vpn-lemanapro.sh" --configure-keychain )" printf '%s\n' "$configure_output" | grep -q 'Credentials are ready in macOS Keychain' grep -q '^ABCD2345EFGH6723$' "$captured_totp" captured_login_totp="$TMP_DIR/captured-login-totp" configure_login_output="$( printf 'ldap-password\n' | HOME="$HOME" \ LEMANA_VPN_USERNAME="lemana-login-$$" \ LEMANA_VPN_AUTH_PROFILE=saved-login \ LEMANA_VPN_CREDENTIAL_SOURCE=keychain \ LEMANA_VPN_OC_PYTHON="$fake_oc_python" \ LEMANA_VPN_CAPTURE_TOTP="$captured_login_totp" \ bash "$ROOT/bin/vpn-lemanapro.sh" --configure-login )" printf '%s\n' "$configure_login_output" | grep -q 'Login and password are ready in macOS Keychain' if [ -s "$captured_login_totp" ]; then echo "configure-login must not store a TOTP seed" >&2 exit 1 fi fake_pwd="$TMP_DIR/fake-pwd" mkdir -p "$fake_pwd/bin" printf 'stale local cli\n' > "$fake_pwd/bin/vpn-lemanapro.sh" piped_output="$( cd "$fake_pwd" && LEMANA_VPN_RAW_BASE_URL="file://$ROOT" sh -s -- --dry-run --non-interactive --minimal --without-app < "$ROOT/install.sh" )" printf '%s\n' "$piped_output" | grep -q "curl -fsSL file://$ROOT/bin/vpn-lemanapro.sh" if printf '%s\n' "$piped_output" | grep -q "$fake_pwd/bin/vpn-lemanapro.sh"; then echo "piped install used stale PWD/bin/vpn-lemanapro.sh" >&2 exit 1 fi printf 'smoke ok\n'