From dc173330fc5b4e0b7c765c2b497bc17c85eae93e Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Sep 2026 11:31:28 +0200 Subject: [PATCH] feat(support): Add section to system report Signed-off-by: Carl Schwan --- lib/AppInfo/Application.php | 9 ++ lib/Support/SystemReportSection.php | 91 ++++++++++++++++ tests/unit/.phpunit.result.cache | 1 + .../unit/Support/SystemReportSectionTest.php | 102 ++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 lib/Support/SystemReportSection.php create mode 100644 tests/unit/.phpunit.result.cache create mode 100644 tests/unit/Support/SystemReportSectionTest.php 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..a6e638109 --- /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((int)$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/tests/unit/.phpunit.result.cache b/tests/unit/.phpunit.result.cache new file mode 100644 index 000000000..85b875ae6 --- /dev/null +++ b/tests/unit/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":2,"defects":{"OCA\\User_SAML\\Tests\\GroupManagerTest::testUpdateUserGroups":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testUnassignUserFromGroups":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testUnassignUserFromGroupsWithKeepEmpytGroups":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testAssignUserToGroups":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testAssignUserToNonExistingGroups":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testAssignUserToGroupsWithCollision":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testHasGroupForeignMembersWithOnlySamlUsers":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testHasGroupForeignMembersWithForeignMember":8,"OCA\\User_SAML\\Tests\\GroupManagerTest::testHasGroupForeignMembersWithNoMembers":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetIdAndTitle":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetDetailsReturnsNothingWithoutConfiguredIdps":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetDetailsNeverIncludesThePrivateKey":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetDetailsOnlyReportsCertificatePresence":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetDetailsUsesFallbackTitleWhenDisplayNameIsEmpty":8,"OCA\\User_SAML\\Tests\\Support\\SystemReportSectionTest::testGetDetailsUsesPreformattedFormat":8},"times":[]} \ No newline at end of file diff --git a/tests/unit/Support/SystemReportSectionTest.php b/tests/unit/Support/SystemReportSectionTest.php new file mode 100644 index 000000000..f1f6aa343 --- /dev/null +++ b/tests/unit/Support/SystemReportSectionTest.php @@ -0,0 +1,102 @@ +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()); + } +}