Fix tenant SSO status mode and refactor status UI tiles

This commit is contained in:
2026-03-12 10:01:24 +01:00
parent e9f73bc96a
commit 24a6b79f7a
8 changed files with 190 additions and 37 deletions

View File

@@ -31,6 +31,7 @@ return [
'css/components/app-empty-state.css',
'css/components/app-details.css',
'css/components/app-details-card.css',
'css/components/app-form-info-tile.css',
'css/components/app-list-table.css',
'css/components/app-list-tabs.css',
'css/components/app-tabs.css',

View File

@@ -856,6 +856,7 @@
"Password login is disabled for this tenant. Please use Microsoft login.": "Passwort-Login ist für diesen Mandanten deaktiviert. Bitte nutze Microsoft-Login.",
"Password login is disabled for all assigned tenants": "Passwort-Login ist für alle zugewiesenen Mandanten deaktiviert.",
"Password login mode": "Passwort-Login-Modus",
"Local password only": "Nur lokales Passwort",
"Local password + Microsoft": "Lokales Passwort + Microsoft",
"Credentials source": "Quelle der Zugangsdaten",
"Shared app": "Geteilte App",

View File

@@ -856,6 +856,7 @@
"Password login is disabled for this tenant. Please use Microsoft login.": "Password login is disabled for this tenant. Please use Microsoft login.",
"Password login is disabled for all assigned tenants": "Password login is disabled for all assigned tenants",
"Password login mode": "Password login mode",
"Local password only": "Local password only",
"Local password + Microsoft": "Local password + Microsoft",
"Credentials source": "Credentials source",
"Shared app": "Shared app",

View File

@@ -110,9 +110,9 @@ class TenantSsoService
'config_complete' => !$state['enabled'] || $configState['complete'],
'config_error_code' => (string) ($configState['error_code'] ?? ''),
'config_error_label' => $this->microsoftConfigErrorLabel((string) ($configState['error_code'] ?? '')),
'password_mode' => $state['enabled'] && $state['enforce_microsoft_login']
? 'microsoft_only'
: 'local_and_microsoft',
'password_mode' => !$state['enabled']
? 'local_only'
: ($state['enforce_microsoft_login'] ? 'microsoft_only' : 'local_and_microsoft'),
'credential_source' => $state['use_shared_app'] ? 'shared' : 'override',
'sync_needs_graph' => $state['sync_profile_on_login']
&& $this->profileSyncFieldsRequireGraph($state['sync_profile_fields']),

View File

@@ -69,9 +69,41 @@ $ssoUiState = is_array($ssoUiState ?? null)
: $tenantSsoService->buildMicrosoftUiState($tenantId, $values);
$ssoConfigComplete = !empty($ssoUiState['config_complete']);
$ssoConfigErrorLabel = trim((string) ($ssoUiState['config_error_label'] ?? ''));
$ssoPasswordMode = (string) ($ssoUiState['password_mode'] ?? ($microsoftEnforce ? 'microsoft_only' : 'local_and_microsoft'));
$ssoPasswordMode = (string) ($ssoUiState['password_mode'] ?? ($microsoftEnforce ? 'microsoft_only' : ($microsoftEnabled ? 'local_and_microsoft' : 'local_only')));
$ssoCredentialSource = (string) ($ssoUiState['credential_source'] ?? ($useSharedApp ? 'shared' : 'override'));
$ssoSyncNeedsGraph = !empty($ssoUiState['sync_needs_graph']);
$ssoPasswordModeLabel = match ($ssoPasswordMode) {
'microsoft_only' => t('Microsoft only'),
'local_and_microsoft' => t('Local password + Microsoft'),
default => t('Local password only'),
};
$ssoCredentialSourceLabel = $ssoCredentialSource === 'shared' ? t('Shared app') : t('Tenant override');
$ssoStatusTiles = [
[
'label' => t('Microsoft login'),
'value' => $microsoftEnabled ? t('Active') : t('Inactive'),
'tone' => $microsoftEnabled ? 'success' : 'neutral',
],
[
'label' => t('Configuration complete'),
'value' => $ssoConfigComplete ? t('Yes') : t('No'),
'tone' => $ssoConfigComplete ? 'success' : 'warning',
],
[
'label' => t('Password login mode'),
'value' => $ssoPasswordModeLabel,
'tone' => match ($ssoPasswordMode) {
'microsoft_only' => 'warning',
'local_and_microsoft' => 'info',
default => 'neutral',
},
],
[
'label' => t('Credentials source'),
'value' => $ssoCredentialSourceLabel,
'tone' => $ssoCredentialSource === 'shared' ? 'info' : 'neutral',
],
];
?>
<form id="<?php e($formId); ?>" method="post" data-standard-detail-form="1">
@@ -407,39 +439,20 @@ $ssoSyncNeedsGraph = !empty($ssoUiState['sync_needs_graph']);
<?php if ($showSsoTab): ?>
<div data-tab-panel="sso" data-tenant-sso-root>
<fieldset>
<legend><small><?php e(t('SSO configuration status')); ?></small></legend>
<div class="grid grid-1-1">
<div>
<small><?php e(t('Microsoft login')); ?></small>
<div><strong><?php e($microsoftEnabled ? t('Active') : t('Inactive')); ?></strong></div>
</div>
<div>
<small><?php e(t('Configuration complete')); ?></small>
<div><strong><?php e($ssoConfigComplete ? t('Yes') : t('No')); ?></strong></div>
</div>
<div>
<small><?php e(t('Password login mode')); ?></small>
<div>
<strong>
<?php e($ssoPasswordMode === 'microsoft_only' ? t('Microsoft only') : t('Local password + Microsoft')); ?>
</strong>
</div>
</div>
<div>
<small><?php e(t('Credentials source')); ?></small>
<div>
<strong>
<?php e($ssoCredentialSource === 'shared' ? t('Shared app') : t('Tenant override')); ?>
</strong>
</div>
</div>
<small class="app-form-info-tiles-title"><?php e(t('SSO configuration status')); ?></small>
<div class="app-form-info-tiles">
<?php foreach ($ssoStatusTiles as $ssoStatusTile): ?>
<?php
$infoTileLabel = (string) ($ssoStatusTile['label'] ?? '');
$infoTileValue = (string) ($ssoStatusTile['value'] ?? '');
$infoTileTone = (string) ($ssoStatusTile['tone'] ?? 'neutral');
require templatePath('partials/app-form-info-tile.phtml');
?>
<?php endforeach; ?>
</div>
<?php if ($microsoftEnabled && !$ssoConfigComplete && $ssoConfigErrorLabel !== ''): ?>
<hr>
<small class="muted"><?php e($ssoConfigErrorLabel); ?></small>
<?php endif; ?>
</fieldset>
<fieldset>
<legend><small><?php e(t('Required Microsoft configuration')); ?></small></legend>

View File

@@ -0,0 +1,24 @@
<?php
/**
* @var string $infoTileLabel
* @var string $infoTileValue
* @var string $infoTileTone
*/
$infoTileLabel = trim((string) ($infoTileLabel ?? ''));
$infoTileValue = trim((string) ($infoTileValue ?? ''));
$infoTileTone = strtolower(trim((string) ($infoTileTone ?? 'neutral')));
if (!in_array($infoTileTone, ['neutral', 'info', 'success', 'warning'], true)) {
$infoTileTone = 'neutral';
}
?>
<article class="app-form-info-tile app-form-info-tile--<?php e($infoTileTone); ?>">
<?php if ($infoTileLabel !== ''): ?>
<small class="app-form-info-tile-label"><?php e($infoTileLabel); ?></small>
<?php endif; ?>
<?php if ($infoTileValue !== ''): ?>
<strong class="app-form-info-tile-value"><?php e($infoTileValue); ?></strong>
<?php endif; ?>
</article>

View File

@@ -159,6 +159,48 @@ class TenantSsoServiceTest extends TestCase
$this->assertSame('sso_disabled', $result['microsoft_reason']);
}
public function testBuildMicrosoftUiStateReturnsLocalOnlyPasswordModeWhenSsoDisabled(): void
{
$repository = $this->createMock(TenantMicrosoftAuthRepositoryInterface::class);
$repository->method('findByTenantId')->willReturn([
'enabled' => 0,
'enforce_microsoft_login' => 1,
]);
$service = $this->newService($repository);
$result = $service->buildMicrosoftUiState(5);
$this->assertSame('local_only', $result['password_mode']);
}
public function testBuildMicrosoftUiStateReturnsLocalAndMicrosoftPasswordModeWhenSsoEnabledWithoutEnforcement(): void
{
$repository = $this->createMock(TenantMicrosoftAuthRepositoryInterface::class);
$repository->method('findByTenantId')->willReturn([
'enabled' => 1,
'enforce_microsoft_login' => 0,
]);
$service = $this->newService($repository);
$result = $service->buildMicrosoftUiState(5);
$this->assertSame('local_and_microsoft', $result['password_mode']);
}
public function testBuildMicrosoftUiStateReturnsMicrosoftOnlyPasswordModeWhenEnforced(): void
{
$repository = $this->createMock(TenantMicrosoftAuthRepositoryInterface::class);
$repository->method('findByTenantId')->willReturn([
'enabled' => 1,
'enforce_microsoft_login' => 1,
]);
$service = $this->newService($repository);
$result = $service->buildMicrosoftUiState(5);
$this->assertSame('microsoft_only', $result['password_mode']);
}
public function testResolveTenantLoginMethodsReturnsMicrosoftOnlyWhenEnforcedAndConfigured(): void
{
$tenantGateway = $this->createMock(AuthTenantGateway::class);

View File

@@ -0,0 +1,71 @@
@layer components {
.app-form-info-tiles-title {
display: block;
margin-bottom: calc(var(--app-spacing) * 0.55);
color: var(--app-muted-color);
}
.app-form-info-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: calc(var(--app-spacing) * 0.75);
margin-bottom: calc(var(--app-spacing) * 0.75);
}
.app-form-info-tile {
border: 1px solid var(--badge-neutral-border);
border-radius: var(--app-border-radius);
background: var(--badge-neutral-bg);
padding: calc(var(--app-spacing) * 0.75);
display: flex;
flex-direction: column;
gap: calc(var(--app-spacing) * 0.35);
margin: 0;
box-shadow: none;
}
.app-form-info-tile-label {
display: block;
color: var(--app-muted-color);
line-height: 1.3;
}
.app-form-info-tile-value {
display: block;
color: var(--app-contrast);
line-height: 1.35;
}
.app-form-info-tile--success {
border-color: var(--badge-success-border);
background: var(--badge-success-bg);
}
.app-form-info-tile--success .app-form-info-tile-value {
color: var(--badge-success-color);
}
.app-form-info-tile--info {
border-color: var(--badge-info-border);
background: var(--badge-info-bg);
}
.app-form-info-tile--info .app-form-info-tile-value {
color: var(--badge-info-color);
}
.app-form-info-tile--warning {
border-color: var(--badge-warn-border);
background: var(--badge-warn-bg);
}
.app-form-info-tile--warning .app-form-info-tile-value {
color: var(--badge-warn-color);
}
@media (max-width: 640px) {
.app-form-info-tiles {
grid-template-columns: 1fr;
}
}
}