Skip to content

Commit 3a806f8

Browse files
authored
Merge pull request #1091 from nextcloud/fix/avoid-crash-with-incomplete-configuration
fix: Avoid redirecting to login with an incomplete configuration
2 parents 7992c15 + 3c2857e commit 3a806f8

3 files changed

Lines changed: 141 additions & 2 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,13 @@ public function boot(IBootContext $context): void {
167167
}
168168

169169
$multipleUserBackEnds = $samlSettings->allowMultipleUserBackEnds();
170-
$configuredIdps = $samlSettings->getListOfIdps();
170+
$configuredIdps = $samlSettings->getListOfIdps(true);
171+
// If no IdP has the minimum required config (entityId + SSO URL), fall through to normal login
172+
// only for regular SAML mode. Environment-variable mode can still redirect to SAMLController::login()
173+
// without requiring configured IdP metadata.
174+
if ($type === 'saml' && empty($configuredIdps)) {
175+
return;
176+
}
171177
$showLoginOptions = $type !== 'environment-variable' && ($multipleUserBackEnds || count($configuredIdps) > 1);
172178

173179
if ($redirectSituation === true && $showLoginOptions) {

lib/SAMLSettings.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,17 @@ public function __construct(
9090
* @return array<int, string>
9191
* @throws Exception
9292
*/
93-
public function getListOfIdps(): array {
93+
public function getListOfIdps(bool $onlyComplete = false): array {
9494
$this->ensureConfigurationsLoaded();
9595

9696
$result = [];
9797
foreach ($this->configurations as $configID => $config) {
98+
if ($onlyComplete
99+
&& (trim(($config['idp-entityId'] ?? '')) === ''
100+
|| trim(($config['idp-singleSignOnService.url'] ?? '')) === '')
101+
) {
102+
continue;
103+
}
98104
// no fancy array_* method, because there might be thousands
99105
$result[$configID] = $config['general-idp0_display_name'] ?? '';
100106
}

tests/unit/SAMLSettingsTest.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\User_SAML\Tests;
11+
12+
use OCA\User_SAML\Db\ConfigurationsMapper;
13+
use OCA\User_SAML\SAMLSettings;
14+
use OCP\AppFramework\Services\IAppConfig;
15+
use OCP\IConfig;
16+
use OCP\ISession;
17+
use OCP\IURLGenerator;
18+
use PHPUnit\Framework\Attributes\DataProvider;
19+
use PHPUnit\Framework\MockObject\MockObject;
20+
use Test\TestCase;
21+
22+
class SAMLSettingsTest extends TestCase {
23+
private IURLGenerator&MockObject $urlGenerator;
24+
private IConfig&MockObject $config;
25+
private IAppConfig&MockObject $appConfig;
26+
private ISession&MockObject $session;
27+
private ConfigurationsMapper&MockObject $mapper;
28+
private SAMLSettings $samlSettings;
29+
30+
#[\Override]
31+
protected function setUp(): void {
32+
parent::setUp();
33+
34+
$this->urlGenerator = $this->createMock(IURLGenerator::class);
35+
$this->config = $this->createMock(IConfig::class);
36+
$this->appConfig = $this->createMock(IAppConfig::class);
37+
$this->session = $this->createMock(ISession::class);
38+
$this->mapper = $this->createMock(ConfigurationsMapper::class);
39+
40+
$this->samlSettings = new SAMLSettings(
41+
$this->urlGenerator,
42+
$this->config,
43+
$this->appConfig,
44+
$this->session,
45+
$this->mapper,
46+
);
47+
}
48+
49+
private static function dataGetListOfIdps(): array {
50+
return [
51+
'empty-all' => [
52+
false,
53+
[],
54+
[],
55+
],
56+
'empty-complete' => [
57+
true,
58+
[],
59+
[],
60+
],
61+
'entityId-and-ssoUrl-missing' => [
62+
true,
63+
[
64+
1 => [
65+
'general-idp0_display_name' => 'My IdP',
66+
// no idp-entityId, no idp-singleSignOnService.url
67+
],
68+
],
69+
[],
70+
],
71+
'only-whitespace' => [
72+
true,
73+
[
74+
1 => [
75+
'general-idp0_display_name' => 'My IdP',
76+
'idp-entityId' => ' ',
77+
'idp-singleSignOnService.url' => "\t",
78+
],
79+
],
80+
[],
81+
],
82+
'configured' => [
83+
true,
84+
[
85+
1 => [
86+
'general-idp0_display_name' => 'My IdP',
87+
'idp-entityId' => 'https://idp.example.com',
88+
'idp-singleSignOnService.url' => 'https://idp.example.com/sso',
89+
],
90+
],
91+
[1 => 'My IdP'],
92+
],
93+
'partially-configured' => [
94+
true,
95+
[
96+
1 => [
97+
'general-idp0_display_name' => 'Configured IdP',
98+
'idp-entityId' => 'https://idp.example.com',
99+
'idp-singleSignOnService.url' => 'https://idp.example.com/sso',
100+
],
101+
2 => [
102+
'general-idp0_display_name' => 'Missing SSO URL',
103+
'idp-entityId' => 'https://idp2.example.com',
104+
// missing idp-singleSignOnService.url
105+
],
106+
3 => [
107+
'general-idp0_display_name' => 'Missing Entity ID',
108+
// missing idp-entityId
109+
'idp-singleSignOnService.url' => 'https://idp3.example.com/sso',
110+
],
111+
],
112+
[1 => 'Configured IdP'],
113+
],
114+
];
115+
}
116+
117+
#[DataProvider('dataGetListOfIdps')]
118+
public function testGetListOfIdps(bool $onlyComplete, array $configs, array $expected): void {
119+
$this->mapper->expects($this->once())
120+
->method('getAll')
121+
->willReturn($configs);
122+
123+
$result = $this->samlSettings->getListOfIdps($onlyComplete);
124+
125+
$this->assertSame($expected, $result);
126+
}
127+
}

0 commit comments

Comments
 (0)