diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 2d0530549..0d798bb8b 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -24,6 +24,7 @@ use OCA\User_SAML\Middleware\OnlyLoggedInMiddleware; use OCA\User_SAML\SAMLSettings; use OCA\User_SAML\Service\SessionService; +use OCA\User_SAML\Support\SystemReportSection; use OCA\User_SAML\UserBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -77,6 +78,14 @@ public function register(IRegistrationContext $context): void { */ $context->registerAlternativeLoginProvider(AlternativeLoginProvider::class); } + + if (method_exists($context, 'registerSystemReportSection')) { + /** + * @psalm-suppress UndefinedInterfaceMethod + * @psalm-suppress MissingDependency + */ + $context->registerSystemReportSection(SystemReportSection::class); + } } #[\Override] diff --git a/lib/Support/SystemReportSection.php b/lib/Support/SystemReportSection.php new file mode 100644 index 000000000..728c12d6c --- /dev/null +++ b/lib/Support/SystemReportSection.php @@ -0,0 +1,91 @@ +l10n->t('SAML'); + } + + #[\Override] + public function getDetails(): array { + $details = []; + + foreach ($this->samlSettings->getListOfIdps() as $id => $displayName) { + $title = $displayName !== '' ? $displayName : $this->l10n->t('SAML provider #%s', [(string)$id]); + $details[] = new SystemReportDetail( + $title, + $this->renderConfig($this->samlSettings->get($id)), + SystemReportDetailFormat::Preformatted, + ); + } + + return $details; + } + + private function renderConfig(array $config): string { + $lines = []; + foreach (SAMLSettings::IDP_CONFIG_KEYS as $key) { + if (in_array($key, self::SECRET_KEYS, true)) { + continue; + } + + $value = $config[$key] ?? ''; + if (in_array($key, self::PRESENCE_ONLY_KEYS, true)) { + $value = $value !== '' ? 'configured' : 'not configured'; + } elseif (is_array($value)) { + $value = implode(';', $value); + } + + $lines[] = $key . ': ' . $value; + } + + return implode("\n", $lines); + } +} diff --git a/psalm.xml b/psalm.xml index 584dc9cc2..2487d8bf1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -24,6 +24,9 @@ + + + diff --git a/tests/stubs/ocp_systemreport_isystemreportsection.php b/tests/stubs/ocp_systemreport_isystemreportsection.php new file mode 100644 index 000000000..ecca4a330 --- /dev/null +++ b/tests/stubs/ocp_systemreport_isystemreportsection.php @@ -0,0 +1,41 @@ +title; + } + + /** @since 36.0.0 */ + public function getContent(): string { + return $this->content; + } + + /** @since 36.0.0 */ + public function getFormat(): SystemReportDetailFormat { + return $this->format; + } +} diff --git a/tests/stubs/ocp_systemreport_systemreportdetailformat.php b/tests/stubs/ocp_systemreport_systemreportdetailformat.php new file mode 100644 index 000000000..263330bd8 --- /dev/null +++ b/tests/stubs/ocp_systemreport_systemreportdetailformat.php @@ -0,0 +1,24 @@ +markTestSkipped('System report sections require Nextcloud 36 or later'); + } + + $this->samlSettings = $this->createMock(SAMLSettings::class); + $this->l10n = $this->createMock(IL10N::class); + $this->l10n->method('t') + ->willReturnCallback(fn (string $text, array $parameters = []) => vsprintf($text, $parameters)); + + $this->section = new SystemReportSection( + $this->samlSettings, + $this->l10n, + ); + } + + public function testGetIdAndTitle(): void { + $this->assertSame('saml', $this->section->getId()); + $this->assertSame('SAML', $this->section->getTitle()); + } + + public function testGetDetailsReturnsNothingWithoutConfiguredIdps(): void { + $this->samlSettings->method('getListOfIdps') + ->willReturn([]); + + $this->assertSame([], $this->section->getDetails()); + } + + public function testGetDetailsNeverIncludesThePrivateKey(): void { + $this->samlSettings->method('getListOfIdps') + ->willReturn([1 => 'My IdP']); + $this->samlSettings->method('get') + ->with(1) + ->willReturn([ + 'idp-entityId' => 'https://idp.example.com', + 'sp-privateKey' => 'super-secret-private-key', + ]); + + $details = $this->section->getDetails(); + $this->assertCount(1, $details); + $this->assertStringNotContainsString('super-secret-private-key', $details[0]->getContent()); + $this->assertStringContainsString('idp-entityId: https://idp.example.com', $details[0]->getContent()); + } + + public function testGetDetailsOnlyReportsCertificatePresence(): void { + $this->samlSettings->method('getListOfIdps') + ->willReturn([1 => 'My IdP']); + $this->samlSettings->method('get') + ->with(1) + ->willReturn([ + 'idp-x509cert' => '-----BEGIN CERTIFICATE-----MIIB...-----END CERTIFICATE-----', + 'sp-x509cert' => '', + ]); + + $content = $this->section->getDetails()[0]->getContent(); + $this->assertStringNotContainsString('BEGIN CERTIFICATE', $content); + $this->assertStringContainsString('idp-x509cert: configured', $content); + $this->assertStringContainsString('sp-x509cert: not configured', $content); + } + + public function testGetDetailsUsesFallbackTitleWhenDisplayNameIsEmpty(): void { + $this->samlSettings->method('getListOfIdps') + ->willReturn([3 => '']); + $this->samlSettings->method('get') + ->with(3) + ->willReturn([]); + + $this->assertSame('SAML provider #3', $this->section->getDetails()[0]->getTitle()); + } + + public function testGetDetailsUsesPreformattedFormat(): void { + $this->samlSettings->method('getListOfIdps') + ->willReturn([1 => 'My IdP']); + $this->samlSettings->method('get') + ->with(1) + ->willReturn([]); + + $this->assertSame(SystemReportDetailFormat::Preformatted, $this->section->getDetails()[0]->getFormat()); + } +} diff --git a/vendor-bin/psalm/composer.lock b/vendor-bin/psalm/composer.lock index b199f35c9..b72d26891 100644 --- a/vendor-bin/psalm/composer.lock +++ b/vendor-bin/psalm/composer.lock @@ -1639,12 +1639,12 @@ "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "1516e40cbb672420958dd7600da564d04bc86645" + "reference": "0a5c79ce655c957d0887842b48803f085f49aa70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/1516e40cbb672420958dd7600da564d04bc86645", - "reference": "1516e40cbb672420958dd7600da564d04bc86645", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/0a5c79ce655c957d0887842b48803f085f49aa70", + "reference": "0a5c79ce655c957d0887842b48803f085f49aa70", "shasum": "" }, "require": { @@ -1679,7 +1679,7 @@ "issues": "https://github.com/nextcloud-deps/ocp/issues", "source": "https://github.com/nextcloud-deps/ocp/tree/stable32" }, - "time": "2026-04-14T01:25:19+00:00" + "time": "2026-09-02T01:51:48+00:00" }, { "name": "nikic/php-parser",