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",