@@ -318,6 +318,7 @@ public function testValidServiceUrl(string $serviceParam, string $redirectURL, b
318318 parameters: $ queryParameters ,
319319 );
320320
321+ /** @psalm-suppress InvalidArgument */
321322 $ response = $ controllerMock ->login ($ loginRequest , ...$ queryParameters );
322323 $ this ->assertInstanceOf (RunnableResponse::class, $ response );
323324 $ arguments = $ response ->getArguments ();
@@ -327,15 +328,35 @@ public function testValidServiceUrl(string $serviceParam, string $redirectURL, b
327328 $ this ->assertEquals ('redirectTrustedURL ' , $ callable [1 ] ?? '' );
328329 }
329330
330- public function testGatewayPassiveDisabledRedirectsWithoutParams (): void
331+ /**
332+ * @return array<array{0:string}>
333+ */
334+ public function serviceUrlsProvider (): array
335+ {
336+ return [
337+ ['https://example.com/ssp/module.php/cas/linkback.php ' ],
338+ ['https://example.com/ssp/module.php/cas/linkback.php?foo=1&bar=2 ' ],
339+ ];
340+ }
341+
342+ /**
343+ * When passive is disabled and a service is provided, CAS must redirect to the service without appending CAS params
344+ *
345+ * @dataProvider serviceUrlsProvider
346+ */
347+ public function testGatewayPassiveDisabledRedirectsWithoutParams (string $ serviceUrl ): void
331348 {
332349 // enable_passive_mode disabled
333350 $ moduleConfig = $ this ->moduleConfig ;
334351 $ moduleConfig ['enable_passive_mode ' ] = false ;
352+
353+ // Ensure the exact service URL (including its query string, if any) is allowed
354+ $ moduleConfig ['legal_service_urls ' ] = [$ serviceUrl ];
355+
335356 $ casconfig = Configuration::loadFromArray ($ moduleConfig );
336357
337358 $ params = [
338- 'service ' => ' https://example.com/ssp/module.php/cas/linkback.php ' ,
359+ 'service ' => $ serviceUrl ,
339360 'gateway ' => true ,
340361 ];
341362 $ loginRequest = Request::create (
@@ -355,15 +376,16 @@ public function testGatewayPassiveDisabledRedirectsWithoutParams(): void
355376 $ controllerMock ->expects ($ this ->once ())->method ('getSession ' )->willReturn ($ this ->sessionMock );
356377 $ this ->sessionMock ->expects ($ this ->once ())->method ('getSessionId ' )->willReturn (session_create_id ());
357378
379+ // Execute
358380 $ response = $ controllerMock ->login ($ loginRequest , ...$ params );
359381
382+ // Validate redirect with original service URL and no CAS parameters appended
360383 $ this ->assertInstanceOf (RunnableResponse::class, $ response );
361384 $ callable = (array )$ response ->getCallable ();
362385 $ this ->assertEquals ('redirectTrustedURL ' , $ callable [1 ] ?? '' );
363386
364- // Service URL unchanged and NO CAS params appended
365387 $ arguments = $ response ->getArguments ();
366- $ this ->assertEquals (' https://example.com/ssp/module.php/cas/linkback.php ' , $ arguments [0 ]);
388+ $ this ->assertEquals ($ serviceUrl , $ arguments [0 ]);
367389 $ this ->assertSame ([], $ arguments [1 ] ?? []);
368390 }
369391
@@ -403,7 +425,7 @@ public function testGatewayPassiveEnabledPerformsPassiveAttempt(): void
403425 $ callable = (array )$ response ->getCallable ();
404426 $ this ->assertEquals ('login ' , $ callable [1 ] ?? '' );
405427
406- // Verify isPassive=true, ForceAuthn=false, ReturnTo contains gatewayTried=1
428+ // Verify isPassive=true, ForceAuthn=false
407429 $ arguments = $ response ->getArguments ();
408430 $ actualLoginParams = $ arguments [0 ] ?? [];
409431 $ this ->assertArrayHasKey ('ForceAuthn ' , $ actualLoginParams );
@@ -412,50 +434,6 @@ public function testGatewayPassiveEnabledPerformsPassiveAttempt(): void
412434 $ this ->assertTrue ($ actualLoginParams ['isPassive ' ]);
413435 $ this ->assertArrayHasKey ('ReturnTo ' , $ actualLoginParams );
414436 $ this ->assertIsString ($ actualLoginParams ['ReturnTo ' ]);
415- $ this ->assertStringContainsString ('gatewayTried=1 ' , $ actualLoginParams ['ReturnTo ' ]);
416- }
417-
418- public function testGatewayPassiveEnabledSecondPassRedirectsWithoutParams (): void
419- {
420- // enable_passive_mode enabled
421- $ moduleConfig = $ this ->moduleConfig ;
422- $ moduleConfig ['enable_passive_mode ' ] = true ;
423- $ casconfig = Configuration::loadFromArray ($ moduleConfig );
424-
425- // Include gatewayTried in the Request only
426- $ requestParams = [
427- 'service ' => 'https://example.com/ssp/module.php/cas/linkback.php ' ,
428- 'gateway ' => true ,
429- 'gatewayTried ' => '1 ' , // simulate second pass after passive attempt
430- ];
431- $ loginRequest = Request::create (
432- uri: Module::getModuleURL ('casserver/login ' ),
433- parameters: $ requestParams ,
434- );
435-
436- $ controllerMock = $ this ->getMockBuilder (LoginController::class)
437- ->setConstructorArgs ([$ this ->sspConfig , $ casconfig , $ this ->authSimpleMock , $ this ->httpUtils ])
438- ->onlyMethods (['getSession ' ])
439- ->getMock ();
440-
441- $ this ->authSimpleMock ->expects ($ this ->atLeastOnce ())->method ('isAuthenticated ' )->willReturn (false );
442-
443- $ controllerMock ->expects ($ this ->once ())->method ('getSession ' )->willReturn ($ this ->sessionMock );
444- $ this ->sessionMock ->expects ($ this ->once ())->method ('getSessionId ' )->willReturn (session_create_id ());
445-
446- // Do not pass 'gatewayTried' as named argument
447- $ callParams = $ requestParams ;
448- unset($ callParams ['gatewayTried ' ]);
449-
450- $ response = $ controllerMock ->login ($ loginRequest , ...$ callParams );
451-
452- $ this ->assertInstanceOf (RunnableResponse::class, $ response );
453- $ callable = (array )$ response ->getCallable ();
454- $ this ->assertEquals ('redirectTrustedURL ' , $ callable [1 ] ?? '' );
455-
456- $ arguments = $ response ->getArguments ();
457- $ this ->assertEquals ('https://example.com/ssp/module.php/cas/linkback.php ' , $ arguments [0 ]);
458- $ this ->assertSame ([], $ arguments [1 ] ?? []);
459437 }
460438
461439 public function testGatewayNoServicePassiveDisabledFallsBackToInteractive (): void
@@ -572,6 +550,7 @@ public function testAuthenticatedPostSubmitsViaPostWithTicket(): void
572550 ],
573551 ]);
574552
553+ /** @psalm-suppress InvalidArgument */
575554 $ response = $ controllerMock ->login ($ loginRequest , ...$ requestParams );
576555
577556 $ this ->assertInstanceOf (RunnableResponse::class, $ response );
@@ -585,54 +564,4 @@ public function testAuthenticatedPostSubmitsViaPostWithTicket(): void
585564 $ this ->assertArrayHasKey ('ticket ' , $ params );
586565 $ this ->assertStringStartsWith ('ST- ' , $ params ['ticket ' ]);
587566 }
588-
589- public function testGatewayRedirectPreservesServiceQueryWithoutTicket (): void
590- {
591- // Passive disabled to trigger direct redirect with no ticket
592- $ moduleConfig = $ this ->moduleConfig ;
593- $ moduleConfig ['enable_passive_mode ' ] = false ;
594-
595- // Service URL with existing query parameters
596- $ serviceWithQuery = 'https://example.com/ssp/module.php/cas/linkback.php?foo=1&bar=2 ' ;
597- // Ensure the exact service URL (including its query string) is allowed
598- $ moduleConfig ['legal_service_urls ' ] = [$ serviceWithQuery ];
599-
600- $ casconfig = Configuration::loadFromArray ($ moduleConfig );
601-
602- $ params = [
603- 'service ' => $ serviceWithQuery ,
604- 'gateway ' => true ,
605- ];
606-
607- $ loginRequest = Request::create (
608- uri: Module::getModuleURL ('casserver/login ' ),
609- parameters: $ params ,
610- );
611-
612- $ controllerMock = $ this ->getMockBuilder (LoginController::class)
613- ->setConstructorArgs ([$ this ->sspConfig , $ casconfig , $ this ->authSimpleMock , $ this ->httpUtils ])
614- ->onlyMethods (['getSession ' ])
615- ->getMock ();
616-
617- // Unauthenticated so gateway path is exercised
618- $ this ->authSimpleMock ->expects ($ this ->atLeastOnce ())->method ('isAuthenticated ' )->willReturn (false );
619-
620- // Session used to build ReturnTo
621- $ controllerMock ->expects ($ this ->once ())->method ('getSession ' )->willReturn ($ this ->sessionMock );
622- $ this ->sessionMock ->expects ($ this ->once ())->method ('getSessionId ' )->willReturn (session_create_id ());
623-
624- // Execute
625- $ response = $ controllerMock ->login ($ loginRequest , ...$ params );
626-
627- // Validate redirect with original query intact and no CAS parameters appended
628- $ this ->assertInstanceOf (RunnableResponse::class, $ response );
629- $ callable = (array )$ response ->getCallable ();
630- $ this ->assertEquals ('redirectTrustedURL ' , $ callable [1 ] ?? '' );
631-
632- $ arguments = $ response ->getArguments ();
633- // URL should be exactly the original service URL including its query string
634- $ this ->assertEquals ($ serviceWithQuery , $ arguments [0 ]);
635- // No additional parameters (e.g., no ticket) should be appended by CAS
636- $ this ->assertSame ([], $ arguments [1 ] ?? []);
637- }
638567}
0 commit comments