diff --git a/config/assets.php b/config/assets.php index b5269d5..a3a0d10 100644 --- a/config/assets.php +++ b/config/assets.php @@ -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', diff --git a/i18n/default_de.json b/i18n/default_de.json index 39f222f..570d97d 100644 --- a/i18n/default_de.json +++ b/i18n/default_de.json @@ -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", diff --git a/i18n/default_en.json b/i18n/default_en.json index f333565..5ba185b 100644 --- a/i18n/default_en.json +++ b/i18n/default_en.json @@ -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", diff --git a/lib/Service/Auth/TenantSsoService.php b/lib/Service/Auth/TenantSsoService.php index e7f8f47..a4b0d9f 100644 --- a/lib/Service/Auth/TenantSsoService.php +++ b/lib/Service/Auth/TenantSsoService.php @@ -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']), diff --git a/pages/admin/tenants/_form.phtml b/pages/admin/tenants/_form.phtml index 0a566bf..b664608 100644 --- a/pages/admin/tenants/_form.phtml +++ b/pages/admin/tenants/_form.phtml @@ -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', + ], +]; ?>
@@ -407,39 +439,20 @@ $ssoSyncNeedsGraph = !empty($ssoUiState['sync_needs_graph']);
-
- -
-
- -
-
-
- -
-
-
- -
- - - -
-
-
- -
- - - -
-
-
- -
- - -
+ +
+ + + +
+ + +
diff --git a/templates/partials/app-form-info-tile.phtml b/templates/partials/app-form-info-tile.phtml new file mode 100644 index 0000000..ab0dac9 --- /dev/null +++ b/templates/partials/app-form-info-tile.phtml @@ -0,0 +1,24 @@ + +
+ + + + + + +
diff --git a/tests/Service/Auth/TenantSsoServiceTest.php b/tests/Service/Auth/TenantSsoServiceTest.php index a842f8b..c28f9db 100644 --- a/tests/Service/Auth/TenantSsoServiceTest.php +++ b/tests/Service/Auth/TenantSsoServiceTest.php @@ -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); diff --git a/web/css/components/app-form-info-tile.css b/web/css/components/app-form-info-tile.css new file mode 100644 index 0000000..1bb014f --- /dev/null +++ b/web/css/components/app-form-info-tile.css @@ -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; + } + } +}