From 493fab870276829e0b2564ef6de7e0a4cdf45fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20D=C3=B6rfler?= Date: Wed, 16 Sep 2026 00:55:16 +0200 Subject: [PATCH 1/3] fix: account for webroot when validating SAML responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit php-saml derives the current SAML endpoint URL from PHP server variables. When Nextcloud is exposed below a path prefix behind a reverse proxy, the external webroot may not be present in those variables. This causes the calculated URL to differ from the Destination in the SAML response and can make response validation fail. Configure php-saml with Nextcloud's webroot before initializing the SAML authentication handler so URL validation includes the external path prefix. Signed-off-by: Robert Dörfler --- lib/Controller/SAMLController.php | 3 ++ tests/unit/Controller/SAMLControllerTest.php | 46 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/Controller/SAMLController.php b/lib/Controller/SAMLController.php index 81b3a0083..0c84405f9 100644 --- a/lib/Controller/SAMLController.php +++ b/lib/Controller/SAMLController.php @@ -41,6 +41,7 @@ use OneLogin\Saml2\Auth; use OneLogin\Saml2\Error; use OneLogin\Saml2\Settings; +use OneLogin\Saml2\Utils; use OneLogin\Saml2\ValidationError; use Psr\Log\LoggerInterface; @@ -359,6 +360,8 @@ public function assertionConsumerService(): Http\RedirectResponse { return new Http\RedirectResponse($this->urlGenerator->getAbsoluteURL('/')); } + Utils::setBaseURLPath(\OC::$WEBROOT); + $auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp)); // validator (called with processResponse()) needs an XML entity loader $this->callWithXmlEntityLoader(function () use ($auth, $AuthNRequestID): void { diff --git a/tests/unit/Controller/SAMLControllerTest.php b/tests/unit/Controller/SAMLControllerTest.php index 8a40440cb..30fa9b299 100644 --- a/tests/unit/Controller/SAMLControllerTest.php +++ b/tests/unit/Controller/SAMLControllerTest.php @@ -29,6 +29,7 @@ use OCP\IUserSession; use OCP\Security\ICrypto; use OCP\Security\ITrustedDomainHelper; +use OneLogin\Saml2\Utils; use Override; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; @@ -440,4 +441,49 @@ public function testLoginWithUnconfiguredIdpRedirectsToGenericError(): void { $this->assertInstanceOf(RedirectResponse::class, $result); $this->assertEquals($errorUrl, $result->getRedirectURL()); } + + public function testAssertionConsumerServiceSetsSamlBaseUrlPath(): void { + $previousWebRoot = \OC::$WEBROOT; + + try { + \OC::$WEBROOT = '/nextcloud'; + Utils::setBaseURLPath(null); + + $data = [ + 'AuthNRequestID' => 'request-id', + 'Idp' => 1, + 'OriginalUrl' => '', + ]; + + $this->request + ->expects($this->once()) + ->method('getCookie') + ->with('saml_data') + ->willReturn(base64_encode('encrypted-cookie')); + + $this->crypto + ->expects($this->once()) + ->method('decrypt') + ->with('encrypted-cookie') + ->willReturn(json_encode($data, JSON_THROW_ON_ERROR)); + + $this->samlSettings + ->expects($this->once()) + ->method('getOneLoginSettingsArray') + ->with(1) + ->willReturn([]); + + try { + $this->samlController->assertionConsumerService(); + } catch (\Throwable) { + // Auth initialization is expected to fail with the minimal + // SAML configuration used by this test. + } + + $this->assertSame('/nextcloud/', Utils::getBaseURLPath()); + } finally { + \OC::$WEBROOT = $previousWebRoot; + Utils::setBaseURLPath(null); + } + } } From 6ccc688ef2b2b7b290838bb596cedecb12217698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20D=C3=B6rfler?= Date: Mon, 21 Sep 2026 19:12:15 +0200 Subject: [PATCH 2/3] fix: use URL generator for SAML webroot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Dörfler --- lib/Controller/SAMLController.php | 2 +- tests/unit/Controller/SAMLControllerTest.php | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Controller/SAMLController.php b/lib/Controller/SAMLController.php index 0c84405f9..0cabef22d 100644 --- a/lib/Controller/SAMLController.php +++ b/lib/Controller/SAMLController.php @@ -360,7 +360,7 @@ public function assertionConsumerService(): Http\RedirectResponse { return new Http\RedirectResponse($this->urlGenerator->getAbsoluteURL('/')); } - Utils::setBaseURLPath(\OC::$WEBROOT); + Utils::setBaseURLPath($this->urlGenerator->getWebroot()); $auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp)); // validator (called with processResponse()) needs an XML entity loader diff --git a/tests/unit/Controller/SAMLControllerTest.php b/tests/unit/Controller/SAMLControllerTest.php index 30fa9b299..3a41f1d43 100644 --- a/tests/unit/Controller/SAMLControllerTest.php +++ b/tests/unit/Controller/SAMLControllerTest.php @@ -443,12 +443,14 @@ public function testLoginWithUnconfiguredIdpRedirectsToGenericError(): void { } public function testAssertionConsumerServiceSetsSamlBaseUrlPath(): void { - $previousWebRoot = \OC::$WEBROOT; - try { - \OC::$WEBROOT = '/nextcloud'; Utils::setBaseURLPath(null); + $this->urlGenerator + ->expects($this->once()) + ->method('getWebroot') + ->willReturn('/nextcloud'); + $data = [ 'AuthNRequestID' => 'request-id', 'Idp' => 1, @@ -482,7 +484,6 @@ public function testAssertionConsumerServiceSetsSamlBaseUrlPath(): void { $this->assertSame('/nextcloud/', Utils::getBaseURLPath()); } finally { - \OC::$WEBROOT = $previousWebRoot; Utils::setBaseURLPath(null); } } From 0007fb81fb97ac6b74a8466da124a4f2dba58a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20D=C3=B6rfler?= Date: Mon, 21 Sep 2026 19:18:36 +0200 Subject: [PATCH 3/3] fix: use URL generator for SAML webroot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Dörfler --- tests/unit/Controller/SAMLControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Controller/SAMLControllerTest.php b/tests/unit/Controller/SAMLControllerTest.php index 3a41f1d43..744dcbf3e 100644 --- a/tests/unit/Controller/SAMLControllerTest.php +++ b/tests/unit/Controller/SAMLControllerTest.php @@ -444,7 +444,7 @@ public function testLoginWithUnconfiguredIdpRedirectsToGenericError(): void { public function testAssertionConsumerServiceSetsSamlBaseUrlPath(): void { try { - Utils::setBaseURLPath(null); + Utils::setBaseURLPath(''); $this->urlGenerator ->expects($this->once()) @@ -484,7 +484,7 @@ public function testAssertionConsumerServiceSetsSamlBaseUrlPath(): void { $this->assertSame('/nextcloud/', Utils::getBaseURLPath()); } finally { - Utils::setBaseURLPath(null); + Utils::setBaseURLPath(''); } } }