987 lines
41 KiB
Bash
Executable File
987 lines
41 KiB
Bash
Executable File
#!/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"
|
|
# Every connection below uses fake clients; cleanup and status must stay isolated too.
|
|
safe_bin="$TMP_DIR/safe-bin"
|
|
mkdir -p "$safe_bin"
|
|
printf '#!/bin/sh\ncase "$*" in *" --recover") printf "no_snapshot\\n" ;; *" --begin "*) printf "ready\\n" ;; *" --recover-session "*) printf "cancelled\\n" ;; esac\nexit 0\n' > "$safe_bin/sudo"
|
|
printf '#!/bin/sh\nexit 0\n' > "$safe_bin/networksetup"
|
|
printf '#!/bin/sh\nexit 1\n' > "$safe_bin/security"
|
|
chmod +x "$safe_bin/sudo" "$safe_bin/networksetup" "$safe_bin/security"
|
|
export PATH="$safe_bin:$PATH"
|
|
export LEMANA_VPN_CONNECT_WAIT_SECONDS=0.1
|
|
printf '#!/bin/sh\n# LEMANA_DNS_PROTOCOL=2\n# LEMANA_SESSION_PROTOCOL=1\n# LEMANA_RECONNECT_PROTOCOL=1\nprintf "no_snapshot\\n"\n' > "$TMP_DIR/fake-dns-cleanup"
|
|
chmod +x "$TMP_DIR/fake-dns-cleanup"
|
|
export LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup"
|
|
|
|
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: mode=manual-keychain 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 -m 755 -o root -g wheel .* /usr/local/sbin/lemana-vpn-dns-cleanup'
|
|
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 '
|
|
printf '%s\n' "$output" | grep -q '+ cp .*/bin/lemana-vpn-import-yandex.py '
|
|
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
|
|
|
|
import_plan="$(sh "$ROOT/install.sh" --dry-run --non-interactive --import-yandex-key --without-app --no-shell --no-sudoers)"
|
|
printf '%s\n' "$import_plan" | grep -q -- 'vpn-lemanapro.sh --configure-login'
|
|
printf '%s\n' "$import_plan" | grep -q -- 'vpn-lemanapro.sh --import-yandex-key'
|
|
if sh "$ROOT/install.sh" --dry-run --non-interactive --mode bitwarden-auto --import-yandex-key >/dev/null 2>&1; then
|
|
echo 'installer must reject mixing Bitwarden mode with local OTP import' >&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 "n\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 'Выбран режим: ручной вход: логин и пароль в Keychain, одноразовый код вручную'
|
|
printf '%s\n' "$interactive_output" | grep -q '+ .*/vpn-lemanapro.sh --configure-login'
|
|
if printf '%s\n' "$interactive_output" | grep -Eq 'credential_source|auth_profile|saved-totp|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 '"mode":{"id":"manual-keychain"'
|
|
printf '%s\n' "$status_json" | grep -q '"app_menu":{"install_label":"Режим: ручной вход, логин и пароль не настроены","configured_mode":"manual-keychain","available_modes":\["configured","bitwarden-auto"\],"setup_hint":"Настройте в Terminal: vpn --configure-login"}'
|
|
|
|
keychain_status_json="$(LEMANA_VPN_CREDENTIAL_SOURCE=keychain bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)"
|
|
printf '%s\n' "$keychain_status_json" | grep -q '"mode":{"id":"manual-keychain"'
|
|
printf '%s\n' "$keychain_status_json" | grep -q '"credentials":{"source":"keychain","auth_profile":"saved-login","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 '"mode":{"id":"bitwarden-auto"'
|
|
printf '%s\n' "$bitwarden_status_json" | grep -q '"credentials":{"source":"bitwarden","auth_profile":"auto","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"
|
|
|
|
manual_keychain_missing_status="$(
|
|
LEMANA_VPN_USERNAME="lemana-status-login-missing" \
|
|
LEMANA_VPN_MODE=manual-keychain \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --status --json
|
|
)"
|
|
printf '%s\n' "$manual_keychain_missing_status" | grep -q '"mode":{"id":"manual-keychain","label":"Вручную","ready":false'
|
|
printf '%s\n' "$manual_keychain_missing_status" | grep -q '"app_menu":{"install_label":"Режим: ручной вход, логин и пароль не настроены","configured_mode":"manual-keychain","available_modes":\["configured","bitwarden-auto"\],"setup_hint":"Настройте в Terminal: vpn --configure-login"}'
|
|
|
|
manual_keychain_ready_status="$(
|
|
PATH="$status_password_bin:$PATH" \
|
|
LEMANA_VPN_USERNAME="lemana-status-login-ready" \
|
|
LEMANA_VPN_MODE=manual-keychain \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --status --json
|
|
)"
|
|
printf '%s\n' "$manual_keychain_ready_status" | grep -q '"mode":{"id":"manual-keychain","label":"Вручную","ready":true'
|
|
printf '%s\n' "$manual_keychain_ready_status" | grep -q '"app_menu":{"install_label":"Режим: ручной вход, логин и пароль сохранены","configured_mode":"manual-keychain","available_modes":\["configured","bitwarden-auto"\],"setup_hint":null}'
|
|
|
|
legacy_saved_totp_status="$(
|
|
LEMANA_VPN_USERNAME="lemana-status-legacy-saved-totp" \
|
|
LEMANA_VPN_AUTH_PROFILE=saved-totp \
|
|
LEMANA_VPN_CREDENTIAL_SOURCE=keychain \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --status --json
|
|
)"
|
|
printf '%s\n' "$legacy_saved_totp_status" | grep -q '"mode":{"id":"manual-keychain"'
|
|
|
|
bitwarden_auto_ready_status="$(
|
|
PATH="$status_full_bin:$PATH" \
|
|
LEMANA_VPN_USERNAME="lemana-status-bitwarden-ready" \
|
|
LEMANA_VPN_MODE=bitwarden-auto \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --status --json
|
|
)"
|
|
printf '%s\n' "$bitwarden_auto_ready_status" | grep -q '"mode":{"id":"bitwarden-auto"'
|
|
printf '%s\n' "$bitwarden_auto_ready_status" | grep -q '"app_menu":{"install_label":"Автоматически · Bitwarden","configured_mode":"bitwarden-auto","available_modes":\["configured","manual-keychain"\],"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 '"mode":{"id":"bitwarden-auto"'
|
|
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 'vpnManager.connect()' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
if grep -q 'vpnManager.connect(mode:' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"; then
|
|
echo 'Connect must use saved configuration, not a launch override' >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'vpnManager.setAuth(' "$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 'menuButton("Логин и пароль…"' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
grep -q 'vpnManager.configureLogin()' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
grep -q 'vpnManager.importYandexKey()' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
grep -q 'menuBarExtraStyle(.window)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
grep -q 'AppStorage("menuBarIndicatorStyle")' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift"
|
|
printf '%s\n' "$output" | grep -q 'app/Sources/LemanaVPN/SessionTiming.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 'case "mode", "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 __init__(self, profile):
|
|
page = QWebEnginePage(profile, self)
|
|
|
|
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 "obsolete click flag must not replace the bounded per-attempt guard" >&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'
|
|
printf '%s\n' "$uninstall_output" | grep -q 'rm -f .*lemana-vpn-import-yandex.py'
|
|
|
|
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
|
|
grep -q '^_tty_available() {' "$ROOT/bin/vpn-lemanapro.sh"
|
|
if ! sed -n '/^_prompt_read()/,/^}/p' "$ROOT/bin/vpn-lemanapro.sh" | grep -q 'if _tty_available; then'; then
|
|
echo "runtime prompt reads must use the robust /dev/tty availability probe" >&2
|
|
exit 1
|
|
fi
|
|
|
|
preserve_username_home="$TMP_DIR/preserve-username-home"
|
|
mkdir -p "$preserve_username_home/.config/lemana-vpn" "$preserve_username_home/.config/openconnect-lite"
|
|
printf 'LEMANA_VPN_USERNAME="lemana-preserved-user"\nLEMANA_VPN_MODE="bitwarden-auto"\n' > "$preserve_username_home/.config/lemana-vpn/env"
|
|
printf 'username = "lemana-preserved-user"\n' > "$preserve_username_home/.config/openconnect-lite/config.toml"
|
|
preserve_username_output="$(
|
|
cd "$ROOT" &&
|
|
HOME="$preserve_username_home" \
|
|
LEMANA_VPN_CONFIG_DIR="$preserve_username_home/.config/lemana-vpn" \
|
|
OPENCONNECT_LITE_CONFIG_DIR="$preserve_username_home/.config/openconnect-lite" \
|
|
sh install.sh --dry-run --non-interactive --mode bitwarden-auto --without-app --no-shell --no-sudoers
|
|
)"
|
|
printf '%s\n' "$preserve_username_output" | grep -q 'Preserving existing LDAP username from previous config'
|
|
|
|
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' "$*"
|
|
for arg in "$@"; do
|
|
printf 'arg=%s\n' "$arg"
|
|
done
|
|
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"
|
|
|
|
manual_keychain_missing_home="$TMP_DIR/manual-keychain-missing-home"
|
|
manual_keychain_missing_capture="$TMP_DIR/manual-keychain-missing-capture"
|
|
mkdir -p "$manual_keychain_missing_home"
|
|
mkdir -p "$manual_keychain_missing_home/.config/openconnect-lite"
|
|
cat > "$manual_keychain_missing_home/.config/openconnect-lite/config.toml" <<'TOML'
|
|
on_disconnect = "true"
|
|
|
|
[default_profile]
|
|
address = "vpn.lemanapro.ru"
|
|
user_group = ""
|
|
name = "Default-K"
|
|
|
|
[credentials]
|
|
username = ""
|
|
TOML
|
|
set +e
|
|
manual_keychain_missing_output="$(
|
|
HOME="$manual_keychain_missing_home" \
|
|
PATH="$fake_missing_keychain_bin:$PATH" \
|
|
LEMANA_VPN_CONFIG_DIR="$manual_keychain_missing_home/.config/lemana-vpn" \
|
|
OPENCONNECT_LITE_CONFIG_DIR="$manual_keychain_missing_home/.config/openconnect-lite" \
|
|
LEMANA_VPN_USERNAME="lemana-missing-login-$$" \
|
|
LEMANA_VPN_MODE=manual-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-keychain-missing-patch-backups" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$TMP_DIR/manual-keychain-missing.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$manual_keychain_missing_capture" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --json 2>&1
|
|
)"
|
|
manual_keychain_missing_code=$?
|
|
set -e
|
|
|
|
[ "$manual_keychain_missing_code" -ne 0 ]
|
|
printf '%s\n' "$manual_keychain_missing_output" | grep -q '"event":"keychain_required"'
|
|
printf '%s\n' "$manual_keychain_missing_output" | grep -q 'vpn --configure-login'
|
|
if [ -f "$manual_keychain_missing_capture" ]; then
|
|
echo "manual-keychain without saved password should fail before launching openconnect-lite" >&2
|
|
exit 1
|
|
fi
|
|
|
|
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"
|
|
|
|
manual_keychain_capture="$TMP_DIR/manual-keychain-capture"
|
|
manual_keychain_output="$(
|
|
HOME="$HOME" \
|
|
PATH="$fake_login_keychain_bin:$PATH" \
|
|
LEMANA_VPN_USERNAME="lemana-manual-keychain-$$" \
|
|
LEMANA_VPN_MODE=manual-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-keychain-patch-backups" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$TMP_DIR/manual-keychain.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$manual_keychain_capture" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --json
|
|
)"
|
|
|
|
printf '%s\n' "$manual_keychain_output" | grep -q '"event":"manual_sso","mode":"manual-keychain","autofill":true,"totp":false,"submit":false'
|
|
if printf '%s\n' "$manual_keychain_output" | grep -q '"event":"keychain_required"'; then
|
|
echo "manual-keychain should not require a TOTP seed" >&2
|
|
exit 1
|
|
fi
|
|
grep -q -- '--browser-display-mode shown' "$manual_keychain_capture"
|
|
grep -q '^autofill_disable=0$' "$manual_keychain_capture"
|
|
grep -q '^autofill_click=0$' "$manual_keychain_capture"
|
|
grep -q '^autofill_totp=0$' "$manual_keychain_capture"
|
|
|
|
# Legacy local OTP migrates to automatic local login; explicit manual modes override it.
|
|
for local_case in local manual full missing; do
|
|
local_capture="$TMP_DIR/otp-$local_case.capture"
|
|
local_path="$fake_path_bin:$PATH"
|
|
local_arg="--json"
|
|
[ "$local_case" != manual ] || local_arg="--manual"
|
|
[ "$local_case" != full ] || local_arg="--manual-full"
|
|
[ "$local_case" != missing ] || local_path="$fake_login_keychain_bin:$PATH"
|
|
set +e
|
|
local_output="$(
|
|
PATH="$local_path" \
|
|
LEMANA_VPN_USERNAME="lemana-local-otp" \
|
|
LEMANA_VPN_MODE=manual-keychain \
|
|
LEMANA_VPN_LOCAL_OTP=1 \
|
|
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/local-otp-patch-backups" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$TMP_DIR/otp-$local_case.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$local_capture" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" "$local_arg" --json
|
|
)"
|
|
local_code=$?
|
|
set -e
|
|
if [ "$local_case" = missing ]; then
|
|
[ "$local_code" -ne 0 ]
|
|
[ ! -f "$local_capture" ]
|
|
printf '%s\n' "$local_output" | grep -q 'vpn --import-yandex-key'
|
|
else
|
|
[ "$local_code" -eq 0 ]
|
|
if [ "$local_case" = local ]; then
|
|
grep -q -- '--browser-display-mode hidden' "$local_capture"
|
|
grep -q '^autofill_click=1$' "$local_capture"
|
|
grep -q '^autofill_totp=1$' "$local_capture"
|
|
printf '%s\n' "$local_output" | grep -q '"event":"auto_sso","mode":"keychain-auto","autofill":true,"totp":true,"submit":true'
|
|
else
|
|
grep -q -- '--browser-display-mode shown' "$local_capture"
|
|
grep -q '^autofill_click=0$' "$local_capture"
|
|
grep -q '^autofill_totp=0$' "$local_capture"
|
|
if [ "$local_case" = full ]; then
|
|
grep -q '^autofill_disable=1$' "$local_capture"
|
|
else
|
|
grep -q '^autofill_disable=0$' "$local_capture"
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
local_status="$(PATH="$fake_login_keychain_bin:$PATH" LEMANA_VPN_USERNAME=lemana-local-otp LEMANA_VPN_LOCAL_OTP=1 bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)"
|
|
printf '%s\n' "$local_status" | python3 -c 'import json,sys; m=json.load(sys.stdin)["modules"]; assert m["local_otp"] and not m["mode"]["ready"]; assert "--import-yandex-key" in m["mode"]["setup_hint"]'
|
|
|
|
fake_bw_sync_bin="$TMP_DIR/fake-bw-sync-bin"
|
|
mkdir -p "$fake_bw_sync_bin"
|
|
cat > "$fake_bw_sync_bin/bw" <<'SH'
|
|
#!/bin/sh
|
|
case "${1:-}:${2:-}" in
|
|
unlock:*)
|
|
printf 'fake-session\n'
|
|
;;
|
|
get:password)
|
|
printf 'ldap-password-from-bitwarden\n'
|
|
;;
|
|
get:item)
|
|
printf '{"login":{"username":"lemana-bw-user","totp":"otpauth://totp/Lemana:test?secret=abcd2345efgh6723&issuer=Lemana"}}\n'
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
SH
|
|
cat > "$fake_bw_sync_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
|
|
lemana-bw-user|totp/lemana-bw-user) exit 0 ;;
|
|
*) exit 1 ;;
|
|
esac
|
|
fi
|
|
exit 0
|
|
SH
|
|
cat > "$fake_bw_sync_bin/keychain-fingerprint" <<'SH'
|
|
#!/bin/sh
|
|
if [ "${1:-}" = "get" ]; then
|
|
printf 'fake-bitwarden-master-password\n'
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
SH
|
|
fake_store_python="$fake_bw_sync_bin/fake-oc-python"
|
|
cat > "$fake_store_python" <<'SH'
|
|
#!/bin/sh
|
|
if [ "$#" -eq 1 ] && [ "${1:-}" = "-" ] && [ -n "${LEMANA_VPN_CAPTURE_STORE:-}" ]; then
|
|
cat >/dev/null
|
|
{
|
|
printf 'username=%s\n' "${KC_USERNAME:-}"
|
|
printf 'password=%s\n' "${_VPN_PASS:-}"
|
|
printf 'totp=%s\n' "${_VPN_TOTP_SECRET:-}"
|
|
} > "$LEMANA_VPN_CAPTURE_STORE"
|
|
exit 0
|
|
fi
|
|
exec python3 "$@"
|
|
SH
|
|
chmod +x "$fake_bw_sync_bin/bw" "$fake_bw_sync_bin/security" "$fake_bw_sync_bin/keychain-fingerprint" "$fake_store_python"
|
|
|
|
bitwarden_username_home="$TMP_DIR/bitwarden-username-home"
|
|
bitwarden_username_capture="$TMP_DIR/bitwarden-username-capture"
|
|
bitwarden_store_capture="$TMP_DIR/bitwarden-store-capture"
|
|
mkdir -p "$bitwarden_username_home/.config/lemana-vpn" "$bitwarden_username_home/.config/openconnect-lite"
|
|
printf 'LEMANA_VPN_MODE="bitwarden-auto"\nLEMANA_VPN_USE_BITWARDEN="1"\nLEMANA_VPN_USE_TOUCHID="1"\n' > "$bitwarden_username_home/.config/lemana-vpn/env"
|
|
cat > "$bitwarden_username_home/.config/openconnect-lite/config.toml" <<'TOML'
|
|
on_disconnect = "true"
|
|
|
|
[default_profile]
|
|
address = "vpn.lemanapro.ru"
|
|
user_group = ""
|
|
name = "Default-K"
|
|
|
|
[credentials]
|
|
username = ""
|
|
TOML
|
|
bitwarden_username_output="$(
|
|
HOME="$bitwarden_username_home" \
|
|
PATH="$fake_bw_sync_bin:$PATH" \
|
|
LEMANA_VPN_CONFIG_DIR="$bitwarden_username_home/.config/lemana-vpn" \
|
|
OPENCONNECT_LITE_CONFIG_DIR="$bitwarden_username_home/.config/openconnect-lite" \
|
|
LEMANA_VPN_KEYCHAIN_FINGERPRINT="$fake_bw_sync_bin/keychain-fingerprint" \
|
|
LEMANA_VPN_OC_BIN="$fake_oc_bin" \
|
|
LEMANA_VPN_OC_PYTHON="$fake_store_python" \
|
|
LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \
|
|
LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \
|
|
LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/bitwarden-username-patch-backups" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$TMP_DIR/bitwarden-username.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$bitwarden_username_capture" \
|
|
LEMANA_VPN_CAPTURE_STORE="$bitwarden_store_capture" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --json
|
|
)"
|
|
printf '%s\n' "$bitwarden_username_output" | grep -q '"event":"bw_touchid"'
|
|
printf '%s\n' "$bitwarden_username_output" | grep -q '"event":"username_saved","source":"bitwarden"'
|
|
printf '%s\n' "$bitwarden_username_output" | grep -q '"event":"auto_sso","mode":"bitwarden-auto","autofill":true,"totp":true,"submit":true'
|
|
if printf '%s\n' "$bitwarden_username_output" | grep -q '"event":"username_required"'; then
|
|
echo "bitwarden-auto must read LDAP username from Bitwarden instead of prompting for it" >&2
|
|
exit 1
|
|
fi
|
|
grep -q '^username=lemana-bw-user$' "$bitwarden_store_capture"
|
|
grep -q '^password=ldap-password-from-bitwarden$' "$bitwarden_store_capture"
|
|
grep -q '^totp=ABCD2345EFGH6723$' "$bitwarden_store_capture"
|
|
grep -q 'LEMANA_VPN_USERNAME="lemana-bw-user"' "$bitwarden_username_home/.config/lemana-vpn/env"
|
|
grep -q 'username = "lemana-bw-user"' "$bitwarden_username_home/.config/openconnect-lite/config.toml"
|
|
grep -q -- '--browser-display-mode hidden' "$bitwarden_username_capture"
|
|
|
|
bitwarden_auto_capture="$TMP_DIR/bitwarden-auto-capture"
|
|
bitwarden_auto_output="$(
|
|
HOME="$HOME" \
|
|
PATH="$fake_path_bin:$PATH" \
|
|
LEMANA_VPN_USERNAME="lemana-bitwarden-auto-$$" \
|
|
LEMANA_VPN_MODE=bitwarden-auto \
|
|
LEMANA_VPN_LOCAL_OTP=1 \
|
|
LEMANA_VPN_USE_TOUCHID=0 \
|
|
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/bitwarden-auto-patch-backups" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$TMP_DIR/bitwarden-auto.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$bitwarden_auto_capture" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" --json
|
|
)"
|
|
|
|
printf '%s\n' "$bitwarden_auto_output" | grep -q '"event":"auto_sso","mode":"bitwarden-auto","autofill":true,"totp":true,"submit":true'
|
|
grep -q -- '--browser-display-mode hidden' "$bitwarden_auto_capture"
|
|
grep -q '^arg=--script$' "$bitwarden_auto_capture"
|
|
grep -Fq "arg=$TMP_DIR/fake-dns-cleanup --vpnc" "$bitwarden_auto_capture"
|
|
grep -q '^autofill_disable=0$' "$bitwarden_auto_capture"
|
|
grep -q '^autofill_click=1$' "$bitwarden_auto_capture"
|
|
grep -q '^autofill_totp=1$' "$bitwarden_auto_capture"
|
|
|
|
# A normal Connect has no mode argument and must use the last saved selection.
|
|
profile_bin="$TMP_DIR/profile-bin"
|
|
mkdir -p "$profile_bin"
|
|
printf '#!/bin/sh\nprintf '\''{"status":"locked"}\\n'\''\n' > "$profile_bin/bw"
|
|
chmod +x "$profile_bin/bw"
|
|
profile_home="$TMP_DIR/profiles"
|
|
mkdir -p "$profile_home/config"
|
|
printf 'LEMANA_VPN_USERNAME="fixture-profile"\nLEMANA_VPN_USE_TOUCHID="0"\n' > "$profile_home/config/env"
|
|
profile_cli() {
|
|
HOME="$profile_home" PATH="$profile_bin:$fake_path_bin:$PATH" \
|
|
LEMANA_VPN_CONFIG_DIR="$profile_home/config" \
|
|
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="$profile_home/patches" \
|
|
LEMANA_VPN_DNS_CLEANUP="$TMP_DIR/fake-dns-cleanup" \
|
|
LEMANA_VPN_CONNECT_LOG="$profile_home/connect.log" \
|
|
LEMANA_VPN_CAPTURE_LAUNCH="$profile_home/launch" \
|
|
bash "$ROOT/bin/vpn-lemanapro.sh" "$@"
|
|
}
|
|
for profile in manual keychain bitwarden; do
|
|
profile_cli "--set-auth=$profile" >/dev/null
|
|
profile_cli --json > "$profile_home/events"
|
|
if [ "$profile" = manual ]; then
|
|
grep -q -- '--browser-display-mode shown' "$profile_home/launch"
|
|
grep -q '^autofill_totp=0$' "$profile_home/launch"
|
|
grep -q '^autofill_click=0$' "$profile_home/launch"
|
|
else
|
|
grep -q -- '--browser-display-mode hidden' "$profile_home/launch"
|
|
grep -q '^autofill_totp=1$' "$profile_home/launch"
|
|
grep -q '^autofill_click=1$' "$profile_home/launch"
|
|
grep -q '"event":"auto_sso"' "$profile_home/events"
|
|
fi
|
|
done
|
|
|
|
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/fake-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","mode":"manual-keychain","diagnostic":true,"autofill":false,"totp":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)/fake-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/fake-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","mode":"manual-keychain","diagnostic":true,"autofill":false,"totp":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'
|