FreeRDP
Loading...
Searching...
No Matches
test_qt_aad_auth_helper Namespace Reference

Data Structures

class  HelperProcess
 
class  RedirectHandler
 

Functions

 skip (message)
 
 fail (message)
 
 main ()
 

Variables

int HELLO_TIMEOUT = 20
 
int REQUEST_TIMEOUT = 20
 
int SKIP_EXIT_CODE = 125
 
str BROKER_TARGET = "ms-appx-web://microsoft.aad.brokerplugin/test-client-id?code=TESTCODE123"
 

Detailed Description

Drives the real freerdp-qt-aad-helper binary over its JSON-RPC protocol (see
client/common/aad-auth-helper-protocol.md) against a local HTTP redirect fixture, under
QT_QPA_PLATFORM=offscreen. The protocol travels over a dedicated pair of pipes handed to the
helper via --cmdInFd=/--cmdOutFd= command line arguments (not stdin/stdout - see
client/common/aad_helper.c), so this test builds those pipes itself and passes them the same way
FreeRDP does (see HelperProcess below for the platform-specific handle encoding/inheritance).

This exists to catch, as a regression test, two bugs found while developing the helper: AAD's
native-broker redirect_uri uses the non-standard "ms-appx-web" scheme, which QtWebEngine (1)
never surfaces to navigationRequested() at all unless the scheme is registered first (Chromium
instead hands it off to the desktop environment and the redirect is lost), and (2) - even once
registered - refused with net::ERR_UNSAFE_REDIRECT to let a real https-shaped redirect into it
unless the scheme is also flagged CORS-enabled (see kBrokerScheme's comment in main.cpp for
both). A plain http(s) redirect_uri is exercised too, as a regression check that fix didn't
break the common case.

If the helper doesn't answer the initial "hello" handshake at all, the environment is assumed
unable to run Qt WebEngine (e.g. a minimal CI image missing GL/X11 libs) and the test is skipped
rather than failed.

Function Documentation

◆ fail()

test_qt_aad_auth_helper.fail (   message)

Definition at line 169 of file test_qt_aad_auth_helper.py.

169def fail(message):
170 print(f"FAIL: {message}")
171 sys.exit(1)
172
173

◆ main()

test_qt_aad_auth_helper.main ( void  )

Definition at line 174 of file test_qt_aad_auth_helper.py.

174def main():
175 if len(sys.argv) != 2:
176 fail("usage: test_qt_aad_auth_helper.py <path-to-freerdp-qt-aad-helper>")
177 helper_path = sys.argv[1]
178 if not os.path.isfile(helper_path):
179 fail(f"helper binary not found: {helper_path}")
180
181 server = http.server.HTTPServer(("127.0.0.1", 0), RedirectHandler)
182 server.last_user_agent = None
183 port = server.server_port
184 server_thread = threading.Thread(target=server.serve_forever, daemon=True)
185 server_thread.start()
186
187 env = dict(os.environ)
188 env["QT_QPA_PLATFORM"] = "offscreen"
189 env["QT_QUICK_BACKEND"] = "software"
190 env["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer"
191
192 helper = HelperProcess(helper_path, env)
193 try:
194 try:
195 hello = helper.request(
196 "hello", {"protocol_version": 1, "client": "freerdp"}, timeout=HELLO_TIMEOUT
197 )
198 except TimeoutError:
199 skip("helper did not answer 'hello' - environment likely can't run Qt WebEngine")
200 if "error" in hello:
201 fail(f"hello returned an error: {hello['error']}")
202 print(f"hello ok: {hello['result']}")
203
204 # Case 1: real HTTP redirect into AAD's ms-appx-web native-broker scheme - the exact
205 # shape that broke twice during development (scheme not observed at all, then
206 # ERR_UNSAFE_REDIRECT).
207 nav = helper.request(
208 "navigate",
209 {
210 "title": "test",
211 "url": f"http://127.0.0.1:{port}/redirect",
212 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
213 "timeout_ms": REQUEST_TIMEOUT * 1000,
214 },
215 )
216 if "error" in nav:
217 fail(f"navigate (broker redirect) failed: {nav['error']}")
218 got = nav["result"].get("redirect_url")
219 if got != BROKER_TARGET:
220 fail(f"navigate (broker redirect): expected {BROKER_TARGET!r}, got {got!r}")
221 print("broker redirect ok")
222
223 # Case 2: plain http(s)-shaped redirect_uri - regression check, must keep working.
224 plain_url = f"http://127.0.0.1:{port}/"
225 nav2 = helper.request(
226 "navigate",
227 {
228 "title": "test",
229 "url": plain_url,
230 "redirect_uri": plain_url,
231 "timeout_ms": REQUEST_TIMEOUT * 1000,
232 },
233 )
234 if "error" in nav2:
235 fail(f"navigate (plain redirect_uri) failed: {nav2['error']}")
236 got2 = nav2["result"].get("redirect_url")
237 if got2 != plain_url:
238 fail(f"navigate (plain redirect_uri): expected {plain_url!r}, got {got2!r}")
239 print("plain redirect_uri ok")
240
241 # Case 3: no user_agent supplied -> native (QtWebEngine) UA is used. Records the native
242 # UA that Case 5/6 assert is restored after an override.
243 default_nav = helper.request(
244 "navigate",
245 {
246 "title": "test",
247 "url": f"http://127.0.0.1:{port}/redirect",
248 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
249 "timeout_ms": REQUEST_TIMEOUT * 1000,
250 },
251 )
252 if "error" in default_nav:
253 fail(f"navigate (default UA) failed: {default_nav['error']}")
254 default_ua = server.last_user_agent
255 if not default_ua:
256 fail("server saw no User-Agent for the default case")
257 if "TestAgent/1.0" in default_ua:
258 fail(f"default UA unexpectedly equals the override: {default_ua!r}")
259 print(f"default UA ok: {default_ua}")
260
261 # Case 4: user_agent supplied -> helper presents it verbatim.
262 override_ua = "TestAgent/1.0"
263 ua_nav = helper.request(
264 "navigate",
265 {
266 "title": "test",
267 "url": f"http://127.0.0.1:{port}/redirect",
268 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
269 "user_agent": override_ua,
270 "timeout_ms": REQUEST_TIMEOUT * 1000,
271 },
272 )
273 if "error" in ua_nav:
274 fail(f"navigate (override UA) failed: {ua_nav['error']}")
275 if server.last_user_agent != override_ua:
276 fail(f"expected UA {override_ua!r}, server saw {server.last_user_agent!r}")
277 print("override UA ok")
278
279 # Case 5: an explicitly empty user_agent must also restore the native UA (contract:
280 # absent OR empty means native, even after a previous override).
281 empty_nav = helper.request(
282 "navigate",
283 {
284 "title": "test",
285 "url": f"http://127.0.0.1:{port}/redirect",
286 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
287 "user_agent": "",
288 "timeout_ms": REQUEST_TIMEOUT * 1000,
289 },
290 )
291 if "error" in empty_nav:
292 fail(f"navigate (empty UA) failed: {empty_nav['error']}")
293 if server.last_user_agent != default_ua:
294 fail(
295 f"expected native UA after empty override {default_ua!r}, "
296 f"saw {server.last_user_agent!r}"
297 )
298 print("empty UA restore ok")
299
300 # Case 6: override again, then omit -> native UA restored. Covers the omitted path
301 # transitioning from an override, not only from the native state Case 5 left behind.
302 reoverride_nav = helper.request(
303 "navigate",
304 {
305 "title": "test",
306 "url": f"http://127.0.0.1:{port}/redirect",
307 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
308 "user_agent": override_ua,
309 "timeout_ms": REQUEST_TIMEOUT * 1000,
310 },
311 )
312 if "error" in reoverride_nav:
313 fail(f"navigate (re-override UA) failed: {reoverride_nav['error']}")
314 omitted_nav = helper.request(
315 "navigate",
316 {
317 "title": "test",
318 "url": f"http://127.0.0.1:{port}/redirect",
319 "redirect_uri": "ms-appx-web://microsoft.aad.brokerplugin/test-client-id",
320 "timeout_ms": REQUEST_TIMEOUT * 1000,
321 },
322 )
323 if "error" in omitted_nav:
324 fail(f"navigate (omitted UA) failed: {omitted_nav['error']}")
325 if server.last_user_agent != default_ua:
326 fail(f"expected native UA restored {default_ua!r}, server saw {server.last_user_agent!r}")
327 print("omitted UA restore ok")
328
329 shut = helper.request("shutdown")
330 if shut.get("result") is not None:
331 fail(f"shutdown expected a null result, got {shut}")
332 helper.notify("exit")
333
334 rc = helper.wait(timeout=10)
335 if rc != 0:
336 fail(f"helper exited with code {rc}, expected 0")
337 print("shutdown/exit ok")
338 finally:
339 server.shutdown()
340 if helper.proc.poll() is None:
341 helper.proc.kill()
342
343 print("PASS")
344
345

◆ skip()

test_qt_aad_auth_helper.skip (   message)

Definition at line 164 of file test_qt_aad_auth_helper.py.

164def skip(message):
165 print(f"SKIP: {message}")
166 sys.exit(SKIP_EXIT_CODE)
167
168

Variable Documentation

◆ BROKER_TARGET

str test_qt_aad_auth_helper.BROKER_TARGET = "ms-appx-web://microsoft.aad.brokerplugin/test-client-id?code=TESTCODE123"

Definition at line 55 of file test_qt_aad_auth_helper.py.

◆ HELLO_TIMEOUT

int test_qt_aad_auth_helper.HELLO_TIMEOUT = 20

Definition at line 51 of file test_qt_aad_auth_helper.py.

◆ REQUEST_TIMEOUT

int test_qt_aad_auth_helper.REQUEST_TIMEOUT = 20

Definition at line 52 of file test_qt_aad_auth_helper.py.

◆ SKIP_EXIT_CODE

int test_qt_aad_auth_helper.SKIP_EXIT_CODE = 125

Definition at line 53 of file test_qt_aad_auth_helper.py.