115 lines
5.8 KiB
Python
115 lines
5.8 KiB
Python
|
|
"""Native WSL opener boundaries; no GUI, identity request or real profile."""
|
||
|
|
|
||
|
|
import base64
|
||
|
|
import importlib.machinery
|
||
|
|
import importlib.util
|
||
|
|
import io
|
||
|
|
import json
|
||
|
|
import runpy
|
||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
from types import SimpleNamespace
|
||
|
|
from unittest.mock import patch
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1] / "scripts/operator-browser"
|
||
|
|
loader = importlib.machinery.SourceFileLoader("native_browser_fixture", str(ROOT / "windows_browser.py"))
|
||
|
|
spec = importlib.util.spec_from_loader(loader.name, loader)
|
||
|
|
native = importlib.util.module_from_spec(spec)
|
||
|
|
loader.exec_module(native)
|
||
|
|
launcher = runpy.run_path(str(ROOT / "xdg-open"))
|
||
|
|
URL = "https://kc.coulomb.social/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A8250%2Foidc%2Fcallback&state=synthetic"
|
||
|
|
|
||
|
|
|
||
|
|
class SavedPipe(io.BytesIO):
|
||
|
|
def close(self):
|
||
|
|
self.saved = self.getvalue()
|
||
|
|
super().close()
|
||
|
|
|
||
|
|
|
||
|
|
class WindowsBrowserBoundary(unittest.TestCase):
|
||
|
|
def process(self, marker=b"opened\r\n"):
|
||
|
|
return SimpleNamespace(stdin=SavedPipe(), stdout=SavedPipe(marker))
|
||
|
|
|
||
|
|
def test_worker_receives_url_only_via_pipe(self):
|
||
|
|
process = self.process()
|
||
|
|
with patch.object(native.subprocess, "Popen", return_value=process) as popen, \
|
||
|
|
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
|
||
|
|
self.assertTrue(native.launch(URL))
|
||
|
|
argv = popen.call_args.args[0]
|
||
|
|
self.assertEqual(json.loads(process.stdin.saved), {"url": URL})
|
||
|
|
self.assertNotIn(URL, " ".join(argv))
|
||
|
|
source = base64.b64decode(argv[-1]).decode("utf-16le")
|
||
|
|
self.assertNotIn("state=synthetic", source)
|
||
|
|
self.assertIn("[Console]::In.ReadToEnd()", source)
|
||
|
|
|
||
|
|
def test_uses_numeric_owner_sid_and_verifies_protection_before_launch(self):
|
||
|
|
source = base64.b64decode(native.command()[-1]).decode("utf-16le")
|
||
|
|
self.assertIn("$grant='*'+$sid.Value+':(OI)(CI)F'", source)
|
||
|
|
self.assertIn("'/inheritance:r'", source)
|
||
|
|
self.assertIn("$rules.Count -ne 1", source)
|
||
|
|
self.assertIn("$rules[0].IdentityReference.Value -ne $sid.Value", source)
|
||
|
|
self.assertLess(source.index("$verified=Get-Acl"), source.index("Start-Process"))
|
||
|
|
self.assertNotIn("Set-Acl", source)
|
||
|
|
|
||
|
|
def test_worker_rechecks_same_issuer_and_both_approved_callbacks(self):
|
||
|
|
source = native.SCRIPT
|
||
|
|
self.assertIn("$uri.Host -ne 'kc.coulomb.social'", source)
|
||
|
|
self.assertIn("$callbacks.Count -ne 1", source)
|
||
|
|
self.assertIn("http://localhost:8250/oidc/callback", source)
|
||
|
|
self.assertIn("http://127.0.0.1:8250/oidc/callback", source)
|
||
|
|
self.assertIn("'code','access_token','id_token','refresh_token','mfa_token','password','client_secret'", source)
|
||
|
|
for banned in ("--no-sandbox", "--disable-web-security", "--ignore-certificate-errors", "--remote-debugging"):
|
||
|
|
self.assertNotIn(banned, source)
|
||
|
|
|
||
|
|
def test_cleanup_checks_exact_pid_start_and_executable(self):
|
||
|
|
self.assertIn("$current.StartTime.ToUniversalTime().Ticks -eq $startTicks", native.SCRIPT)
|
||
|
|
self.assertIn("$current.Path -eq $browser", native.SCRIPT)
|
||
|
|
self.assertIn("$current.Kill()", native.SCRIPT)
|
||
|
|
self.assertNotIn("Stop-Process -Name", native.SCRIPT)
|
||
|
|
for value in (True, 0, 601, "600"):
|
||
|
|
with self.subTest(value=value), self.assertRaises(ValueError):
|
||
|
|
native.command(value)
|
||
|
|
|
||
|
|
def test_unknown_output_failure_and_timeout_do_not_become_opened(self):
|
||
|
|
for marker in (b"failed\r\n", b"unexpected synthetic output\n", b""):
|
||
|
|
process = self.process(marker)
|
||
|
|
with self.subTest(marker=marker), patch.object(native.subprocess, "Popen", return_value=process), \
|
||
|
|
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
|
||
|
|
self.assertFalse(native.launch(URL))
|
||
|
|
process = self.process()
|
||
|
|
with patch.object(native.subprocess, "Popen", return_value=process), \
|
||
|
|
patch.object(native.select, "select", return_value=([], [], [])):
|
||
|
|
self.assertFalse(native.launch(URL))
|
||
|
|
|
||
|
|
def test_wsl_uses_native_before_display_check_without_changing_url(self):
|
||
|
|
process = self.process()
|
||
|
|
markers = []
|
||
|
|
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
|
||
|
|
patch.object(launcher["platform"], "release", return_value="6.18-microsoft-standard-WSL2"), \
|
||
|
|
patch.dict(launcher["os"].environ, {}, clear=True), \
|
||
|
|
patch.object(native.subprocess, "Popen", return_value=process), \
|
||
|
|
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
|
||
|
|
self.assertEqual(launcher["open_url"](URL), 0)
|
||
|
|
self.assertEqual(markers, ["opened"])
|
||
|
|
self.assertEqual(json.loads(process.stdin.saved), {"url": URL})
|
||
|
|
|
||
|
|
def test_foreign_url_never_reaches_native_and_failure_never_falls_back(self):
|
||
|
|
markers = []
|
||
|
|
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
|
||
|
|
patch.object(launcher["platform"], "release", return_value="microsoft"), \
|
||
|
|
patch.object(native.subprocess, "Popen") as popen:
|
||
|
|
self.assertEqual(launcher["open_url"]("https://foreign.invalid/"), 1)
|
||
|
|
popen.assert_not_called()
|
||
|
|
process = self.process(b"failed\n")
|
||
|
|
with patch.dict(launcher["open_url"].__globals__, {"stamp": markers.append}), \
|
||
|
|
patch.object(launcher["platform"], "release", return_value="microsoft"), \
|
||
|
|
patch.object(native.subprocess, "Popen", return_value=process) as popen, \
|
||
|
|
patch.object(native.select, "select", return_value=([process.stdout], [], [])):
|
||
|
|
self.assertEqual(launcher["open_url"](URL), 1)
|
||
|
|
self.assertEqual(popen.call_count, 1)
|
||
|
|
self.assertEqual(markers, ["rejected", "windows-open-failed"])
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|