|
18 | 18 | #[CoversClass(Utils\HTTP::class)] |
19 | 19 | class HTTPTest extends ClearStateTestCase |
20 | 20 | { |
| 21 | + /** |
| 22 | + * Set up the test. |
| 23 | + */ |
| 24 | + protected function setUp(): void |
| 25 | + { |
| 26 | + parent::setUp(); |
| 27 | + if (!defined('SIMPLESAMLPHP_TEST_NOEXIT')) { |
| 28 | + define('SIMPLESAMLPHP_TEST_NOEXIT', true); |
| 29 | + } |
| 30 | + } |
| 31 | + |
21 | 32 | /** |
22 | 33 | * Set up the environment ($_SERVER) populating the typical variables from a given URL. |
23 | 34 | * |
@@ -584,7 +595,7 @@ public function testDetectSameSiteNoneBehavior(?string $userAgent, bool $support |
584 | 595 |
|
585 | 596 | public static function detectSameSiteProvider(): array |
586 | 597 | { |
587 | | - // @codingStandardsIgnoreStart |
| 598 | + // phpcs:disable Generic.Files.LineLength |
588 | 599 | return [ |
589 | 600 | [null, true], |
590 | 601 | ['some-new-browser', true], |
@@ -618,6 +629,124 @@ public static function detectSameSiteProvider(): array |
618 | 629 | // old embedded browser |
619 | 630 | ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko)', false], |
620 | 631 | ]; |
621 | | - // @codingStandardsIgnoreEnd |
| 632 | + // phpcs:enable Generic.Files.LineLength |
| 633 | + } |
| 634 | + |
| 635 | + /** |
| 636 | + * submitPOSTData() should throw Error\Exception for an invalid destination URL. |
| 637 | + */ |
| 638 | + public function testSubmitPOSTDataThrowsOnInvalidURL(): void |
| 639 | + { |
| 640 | + $httpUtils = new Utils\HTTP(); |
| 641 | + |
| 642 | + // Minimal configuration to satisfy internals; won’t be used since we fail early. |
| 643 | + Configuration::loadFromArray([ |
| 644 | + 'baseurlpath' => 'https://example.com/simplesaml/', |
| 645 | + ], '[ARRAY]', 'simplesaml'); |
| 646 | + |
| 647 | + $this->expectException(Error\Exception::class); |
| 648 | + $this->expectExceptionMessage('Invalid destination URL: not-a-url'); |
| 649 | + |
| 650 | + $httpUtils->submitPOSTData('not-a-url', ['a' => 'b']); |
| 651 | + } |
| 652 | + |
| 653 | + |
| 654 | + /** |
| 655 | + * When enable.http_post = true, destination is http:// and current request is HTTPS, we must redirect. |
| 656 | + * We assert a Location header is sent pointing to a postredirect URL. |
| 657 | + */ |
| 658 | + #[Depends('testXdebugMode')] |
| 659 | + #[RunInSeparateProcess] |
| 660 | + public function testSubmitPOSTDataRedirectsFromHttpsToHttp(): void |
| 661 | + { |
| 662 | + $httpUtils = new Utils\HTTP(); |
| 663 | + |
| 664 | + // Configure base URL and allow http post. |
| 665 | + Configuration::loadFromArray([ |
| 666 | + 'baseurlpath' => 'https://idp.example.org/simplesaml/', |
| 667 | + 'enable.http_post' => true, |
| 668 | + 'secretsalt' => 'abc', |
| 669 | + ], '[ARRAY]', 'simplesaml'); |
| 670 | + |
| 671 | + // Simulate the current request being HTTPS |
| 672 | + $this->setupEnvFromURL('https://idp.example.org/simplesaml/module.php/core/someaction?x=1'); |
| 673 | + |
| 674 | + // Destination is explicitly http:// |
| 675 | + $destination = 'http://sp.example.com/acs'; |
| 676 | + $post = ['SAMLResponse' => 'abc', 'RelayState' => 'xyz']; |
| 677 | + |
| 678 | + try { |
| 679 | + $httpUtils->submitPOSTData($destination, $post); |
| 680 | + } catch (\Throwable $e) { |
| 681 | + } |
| 682 | + |
| 683 | + $headers = function_exists('xdebug_get_headers') ? xdebug_get_headers() : []; |
| 684 | + |
| 685 | + // Find the Location header |
| 686 | + $locationHeader = null; |
| 687 | + foreach ($headers as $h) { |
| 688 | + if (stripos($h, 'Location: ') === 0) { |
| 689 | + $locationHeader = substr($h, 10); |
| 690 | + break; |
| 691 | + } |
| 692 | + } |
| 693 | + |
| 694 | + $this->assertNotNull($locationHeader, 'Expected a Location header to be sent'); |
| 695 | + $this->assertStringStartsWith('http://', $locationHeader, 'Location should be http://'); |
| 696 | + $this->assertStringContainsString('/core/postredirect', $locationHeader); |
| 697 | + $this->assertTrue( |
| 698 | + (str_contains($locationHeader, 'RedirInfo=')), |
| 699 | + 'Location should contain RedirInfo parameter', |
| 700 | + ); |
| 701 | + } |
| 702 | + |
| 703 | + |
| 704 | + /** |
| 705 | + * submitPOSTData() should pass slow_post_delay_ms to the template: |
| 706 | + * - default 30000 when config key missing |
| 707 | + * - default 30000 when config value < 0 |
| 708 | + * - exact value when config value is 10000 |
| 709 | + */ |
| 710 | + #[DataProvider('slowPostDelayProvider')] |
| 711 | + #[RunInSeparateProcess] |
| 712 | + public function testSubmitPOSTDataSlowPostDelay(?int $configured, int $expected): void |
| 713 | + { |
| 714 | + $httpUtils = new Utils\HTTP(); |
| 715 | + |
| 716 | + // Base config |
| 717 | + $config = [ |
| 718 | + 'baseurlpath' => 'https://idp.example.org/simplesaml/', |
| 719 | + 'enable.http_post' => false, |
| 720 | + ]; |
| 721 | + if ($configured !== null) { |
| 722 | + $config['slow_post_delay_ms'] = $configured; |
| 723 | + } |
| 724 | + Configuration::loadFromArray($config, '[ARRAY]', 'simplesaml'); |
| 725 | + |
| 726 | + // Use https destination to bypass the http-redirect branch entirely |
| 727 | + $destination = 'https://sp.example.com/acs'; |
| 728 | + $post = ['k' => 'v']; |
| 729 | + |
| 730 | + // Capture output |
| 731 | + ob_start(); |
| 732 | + try { |
| 733 | + $httpUtils->submitPOSTData($destination, $post); |
| 734 | + } catch (\Throwable $e) { |
| 735 | + } |
| 736 | + $html = ob_get_clean(); |
| 737 | + |
| 738 | + // The template writes the delay into data-slow-post-delay attribute |
| 739 | + $needle = 'data-slow-post-delay="' . $expected . '"'; |
| 740 | + $this->assertStringContainsString($needle, $html); |
| 741 | + } |
| 742 | + |
| 743 | + public static function slowPostDelayProvider(): array |
| 744 | + { |
| 745 | + return [ |
| 746 | + // [configured, expected] |
| 747 | + 'missing config => default' => [null, 30000], |
| 748 | + 'negative config => default' => [-5, 30000], |
| 749 | + 'positive config 10000 => 10000' => [10000, 10000], |
| 750 | + ]; |
622 | 751 | } |
623 | 752 | } |
0 commit comments