"""Browser branch execution in disposable user/mount/network namespaces. No host X socket, profile, service bus, device, or application data is exposed. """ import json import os from pathlib import Path import subprocess import tempfile import unittest ROOT = Path(__file__).resolve().parents[2] class BrowserTests(unittest.TestCase): def run_browser(self, missing=None, socket_present=True): with tempfile.TemporaryDirectory(prefix='c01-kiosk-test-') as directory: work = Path(directory) (work / 'chromium').write_text('#!/usr/bin/python3\nimport json,os,sys\nprint(json.dumps({"args":sys.argv[1:],"display":os.environ["DISPLAY"],"home":os.environ["HOME"],"runtime":os.environ["XDG_RUNTIME_DIR"],"uid":os.getuid(),"groups":os.getgroups()}))\n') (work / 'chromium').chmod(0o755) (work / 'driver.py').write_text('''import os,socket,subprocess,sys os.makedirs('/tmp/.X11-unix') os.makedirs('/var/lib/archipelago/chromium-kiosk') s=socket.socket(socket.AF_UNIX) if sys.argv[1]=='yes':s.bind('/tmp/.X11-unix/X0') os.environ.update(PATH='/test:/usr/bin',KIOSK_WIN_W='1280',KIOSK_WIN_H='720',KIOSK_SCALE='1.50',ENABLE_FEATURES='OverlayScrollbar,VaapiVideoDecodeLinuxGL',ARCHIPELAGO_UID='1000',GPU_FLAGS='--in-process-gpu',EXTRA_DISABLED_FEATURES='',KIOSK_SAFE_AREA_X_PX='32',KIOSK_SAFE_AREA_Y_PX='24') if sys.argv[2]!='none':os.environ.pop(sys.argv[2]) r=subprocess.run(['/usr/bin/bash','/launcher','--c01-browser']) sys.exit(r.returncode) ''') return subprocess.run(['bwrap','--unshare-all','--die-with-parent','--new-session', '--uid','1000','--gid','1000','--ro-bind','/usr','/usr','--symlink','usr/bin','/bin', '--symlink','usr/lib','/lib','--symlink','usr/lib64','/lib64', '--proc','/proc','--dev','/dev','--tmpfs','/tmp','--tmpfs','/var', '--ro-bind',str(work),'/test','--ro-bind',str(ROOT/'image-recipe/configs/archipelago-kiosk-launcher.sh'),'/launcher', '/usr/bin/python3','/test/driver.py','yes' if socket_present else 'no',missing or 'none'], capture_output=True,text=True,timeout=20) def test_preserves_browser_profile_display_audio_and_cdp_arguments(self): result = self.run_browser() self.assertEqual(result.returncode, 0, result.stderr) data = json.loads(result.stdout) self.assertEqual(data['uid'], 1000) self.assertEqual(data['display'], ':0') self.assertEqual(data['home'], '/home/archipelago') self.assertEqual(data['runtime'], '/run/user/1000') for argument in ('--user-data-dir=/var/lib/archipelago/chromium-kiosk', '--remote-debugging-port=9222','--window-size=1280,720', '--force-device-scale-factor=1.50','--in-process-gpu', '--app=http://localhost/kiosk?safe_area_x=32&safe_area_y=24'): self.assertIn(argument, data['args']) def test_waits_for_display_without_starting_browser(self): result = self.run_browser(socket_present=False) self.assertEqual(result.returncode, 75, result.stderr) self.assertEqual(result.stdout, '') def test_missing_display_owner_environment_refuses_browser(self): result = self.run_browser(missing='KIOSK_SCALE') self.assertEqual(result.returncode, 75, result.stderr) self.assertEqual(result.stdout, '') if __name__ == '__main__': unittest.main()