From fde6263e6178e68579a216dbe8ab382037eb25db Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 6 Jan 2026 13:11:31 +0530 Subject: [PATCH 1/5] Updates Keycloak's internal enabled flag via Admin REST API --- lib/Listeners/UserChangedListener copy.php | 135 +++++++++++++++++++++ lib/Listeners/UserChangedListener.php | 38 +++++- lib/Service/SSOService.php | 32 +++++ 3 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 lib/Listeners/UserChangedListener copy.php diff --git a/lib/Listeners/UserChangedListener copy.php b/lib/Listeners/UserChangedListener copy.php new file mode 100644 index 00000000..22b99212 --- /dev/null +++ b/lib/Listeners/UserChangedListener copy.php @@ -0,0 +1,135 @@ +util = $util; + $this->mailboxMapper = $mailboxMapper; + $this->logger = $logger; + $this->userService = $userService; + $this->LDAPConnectionService = $LDAPConnectionService; + } + + public function handle(Event $event): void { + if (!($event instanceof UserChangedEvent)) { + return; + } + + $feature = $event->getFeature(); + $user = $event->getUser(); + $username = $user->getUID(); + $newValue = $event->getValue(); + $backend = $user->getBackend()->getBackendName(); + + $this->logger->error("UserChangedEvent received for user: $username, feature: $feature, backend: $backend, value type: " . gettype($newValue) . ", value: " . var_export($newValue, true)); + + if ($feature === self::QUOTA_FEATURE) { + $updatedQuota = $event->getValue(); + $quotaInBytes = (int) $this->util->computerFileSize($updatedQuota); + $this->logger->error("Processing quota update for user: $username, quota: $updatedQuota, bytes: $quotaInBytes"); + + $this->updateQuota($username, $backend, $quotaInBytes); + } + + if ($feature === self::ENABLED_FEATURE) { + $this->logger->error("Processing enabled feature change for user: $username, backend: $backend"); + + // Only update LDAP for LDAP backend users + if ($backend === 'LDAP') { + $this->logger->error("User $username is on LDAP backend, proceeding with LDAP update"); + try { + // Get user's current enabled state for comparison + $userCurrentEnabled = $user->isEnabled(); + $this->logger->error("User $username current enabled state from user->isEnabled(): " . ($userCurrentEnabled ? 'true' : 'false')); + + // Convert $newValue to boolean - handle various types that might be returned + // $event->getValue() might return: bool true/false, string "1"/"0", int 1/0, or null + $isEnabled = false; + $conversionMethod = 'unknown'; + + if ($newValue !== null) { + if (is_bool($newValue)) { + $isEnabled = $newValue; + $conversionMethod = 'direct_bool'; + } elseif (is_string($newValue)) { + // Handle string "1", "0", "true", "false", etc. + $isEnabled = filter_var($newValue, FILTER_VALIDATE_BOOLEAN); + $conversionMethod = 'filter_var_string'; + $this->logger->error("Converted string value '$newValue' to boolean: " . ($isEnabled ? 'true' : 'false')); + } elseif (is_numeric($newValue)) { + // Handle int 1/0 + $isEnabled = (bool) $newValue; + $conversionMethod = 'cast_numeric'; + $this->logger->error("Converted numeric value $newValue to boolean: " . ($isEnabled ? 'true' : 'false')); + } else { + // Fallback to user's actual enabled state if value is unexpected + $isEnabled = $user->isEnabled(); + $conversionMethod = 'fallback_user_state'; + $this->logger->error("Unexpected value type (" . gettype($newValue) . ") for enabled feature for user: $username, using user->isEnabled() instead. Value was: " . var_export($newValue, true)); + } + } else { + // If value is null, use user's actual enabled state + $isEnabled = $user->isEnabled(); + $conversionMethod = 'null_fallback_user_state'; + $this->logger->error("Event value is null for user: $username, using user->isEnabled() instead"); + } + + $this->logger->error("Updating LDAP active attributes for user: $username, final enabled value: " . ($isEnabled ? 'true' : 'false') . ", conversion method: $conversionMethod, event value: " . var_export($newValue, true) . ", user->isEnabled(): " . ($userCurrentEnabled ? 'true' : 'false')); + + $this->userService->mapActiveAttributesInLDAP($username, $isEnabled); + + $this->logger->error("Successfully called mapActiveAttributesInLDAP for user: $username with enabled: " . ($isEnabled ? 'true' : 'false')); + } catch (Exception $e) { + $this->logger->error('Failed to update LDAP attributes for user: ' . $username, [ + 'exception' => $e, + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + } + } else { + $this->logger->error("User $username is not on LDAP backend (backend: $backend), skipping LDAP update"); + } + } + } + + private function updateQuota(string $username, string $backend, int $quotaInBytes) { + try { + if ($backend === 'SQL raw') { + $this->mailboxMapper->updateMailboxQuota($username, $quotaInBytes); + } + if ($backend === 'LDAP') { + $quotaAttribute = [ + 'quota' => $quotaInBytes + ]; + $this->LDAPConnectionService->updateAttributesInLDAP($username, $quotaAttribute); + } + } catch (Exception $e) { + $this->logger->error("Error setting quota for user $username " . $e->getMessage()); + } + } +} diff --git a/lib/Listeners/UserChangedListener.php b/lib/Listeners/UserChangedListener.php index f720dc38..2e429a27 100644 --- a/lib/Listeners/UserChangedListener.php +++ b/lib/Listeners/UserChangedListener.php @@ -7,6 +7,7 @@ namespace OCA\EcloudAccounts\Listeners; use Exception; use OCA\EcloudAccounts\Db\MailboxMapper; use OCA\EcloudAccounts\Service\LDAPConnectionService; +use OCA\EcloudAccounts\Service\SSOService; use OCA\EcloudAccounts\Service\UserService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -26,13 +27,15 @@ class UserChangedListener implements IEventListener { private $userService; private $LDAPConnectionService; + private $ssoService; - public function __construct(Util $util, LoggerInterface $logger, MailboxMapper $mailboxMapper, UserService $userService, LDAPConnectionService $LDAPConnectionService) { + public function __construct(Util $util, LoggerInterface $logger, MailboxMapper $mailboxMapper, UserService $userService, LDAPConnectionService $LDAPConnectionService, SSOService $ssoService) { $this->util = $util; $this->mailboxMapper = $mailboxMapper; $this->logger = $logger; $this->userService = $userService; $this->LDAPConnectionService = $LDAPConnectionService; + $this->ssoService = $ssoService; } public function handle(Event $event): void { @@ -54,10 +57,39 @@ class UserChangedListener implements IEventListener { } if ($feature === self::ENABLED_FEATURE) { + // Convert $newValue to boolean - handle various types that might be returned + $isEnabled = false; + if ($newValue !== null) { + if (is_bool($newValue)) { + $isEnabled = $newValue; + } elseif (is_string($newValue)) { + $isEnabled = filter_var($newValue, FILTER_VALIDATE_BOOLEAN); + } elseif (is_numeric($newValue)) { + $isEnabled = (bool) $newValue; + } else { + // Fallback to user's actual enabled state + $isEnabled = $user->isEnabled(); + } + } else { + // If value is null, use user's actual enabled state + $isEnabled = $user->isEnabled(); + } + + // Step 1: Update LDAP attributes (active, mailActive) + try { + $this->userService->mapActiveAttributesInLDAP($username, $isEnabled); + $this->logger->error("[UserChangedListener] Successfully updated LDAP attributes for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); + } catch (Exception $e) { + $this->logger->error('[UserChangedListener] Failed to update LDAP attributes for user: ' . $username, ['exception' => $e]); + } + + // Step 2: Update Keycloak Admin UI enabled status (this is the ONLY way to update Keycloak's internal enabled flag) try { - $this->userService->mapActiveAttributesInLDAP($username, $newValue); + $this->ssoService->updateUserEnabledStatus($username, $isEnabled); + $this->logger->error("[UserChangedListener] Successfully updated Keycloak enabled status for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); } catch (Exception $e) { - $this->logger->error('Failed to update LDAP attributes for user: ' . $username, ['exception' => $e]); + $this->logger->error('[UserChangedListener] Failed to update Keycloak enabled status for user: ' . $username, ['exception' => $e]); + // Don't throw - LDAP update is more critical, Keycloak update failure shouldn't break the flow } } } diff --git a/lib/Service/SSOService.php b/lib/Service/SSOService.php index 89d49067..c6b9b5ca 100644 --- a/lib/Service/SSOService.php +++ b/lib/Service/SSOService.php @@ -253,6 +253,38 @@ class SSOService { $this->migrateCredential($username, $secret); } + /** + * Update user's enabled status in Keycloak Admin UI + * This is the ONLY way to update Keycloak's internal "enabled" flag + * + * @param string $username The Nextcloud username + * @param bool $enabled Whether the user should be enabled (true) or disabled (false) + * @throws SSOAdminAPIException If the API call fails + */ + public function updateUserEnabledStatus(string $username, bool $enabled): void { + try { + if ($this->isNotCurrentUser($username)) { + $this->setupUserId($username); + } + + $url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '/' . $this->currentUserId; + $data = [ + 'enabled' => $enabled + ]; + + $this->logger->info("[SSOService] Updating Keycloak enabled status for user: $username, enabled: " . ($enabled ? 'true' : 'false') . ", Keycloak User ID: " . $this->currentUserId); + $this->callSSOAPI($url, 'PUT', $data, 204); + $this->logger->info("[SSOService] Successfully updated Keycloak enabled status for user: $username"); + } catch (\Exception $e) { + $this->logger->error("[SSOService] Failed to update Keycloak enabled status for user: $username", [ + 'exception' => $e, + 'message' => $e->getMessage() + ]); + // Don't throw - we don't want to break the user enable/disable flow if Keycloak update fails + // The LDAP update is more critical + } + } + private function getCredentialIds() : array { $url = $this->ssoConfig['admin_rest_api_url'] . self::CREDENTIALS_ENDPOINT; $url = str_replace('{USER_ID}', $this->currentUserId, $url); -- GitLab From 49148e632ff4bbaf865efc1d5b823e8bb3a9b5b8 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 6 Jan 2026 13:12:14 +0530 Subject: [PATCH 2/5] delete backuped file --- lib/Listeners/UserChangedListener copy.php | 135 --------------------- 1 file changed, 135 deletions(-) delete mode 100644 lib/Listeners/UserChangedListener copy.php diff --git a/lib/Listeners/UserChangedListener copy.php b/lib/Listeners/UserChangedListener copy.php deleted file mode 100644 index 22b99212..00000000 --- a/lib/Listeners/UserChangedListener copy.php +++ /dev/null @@ -1,135 +0,0 @@ -util = $util; - $this->mailboxMapper = $mailboxMapper; - $this->logger = $logger; - $this->userService = $userService; - $this->LDAPConnectionService = $LDAPConnectionService; - } - - public function handle(Event $event): void { - if (!($event instanceof UserChangedEvent)) { - return; - } - - $feature = $event->getFeature(); - $user = $event->getUser(); - $username = $user->getUID(); - $newValue = $event->getValue(); - $backend = $user->getBackend()->getBackendName(); - - $this->logger->error("UserChangedEvent received for user: $username, feature: $feature, backend: $backend, value type: " . gettype($newValue) . ", value: " . var_export($newValue, true)); - - if ($feature === self::QUOTA_FEATURE) { - $updatedQuota = $event->getValue(); - $quotaInBytes = (int) $this->util->computerFileSize($updatedQuota); - $this->logger->error("Processing quota update for user: $username, quota: $updatedQuota, bytes: $quotaInBytes"); - - $this->updateQuota($username, $backend, $quotaInBytes); - } - - if ($feature === self::ENABLED_FEATURE) { - $this->logger->error("Processing enabled feature change for user: $username, backend: $backend"); - - // Only update LDAP for LDAP backend users - if ($backend === 'LDAP') { - $this->logger->error("User $username is on LDAP backend, proceeding with LDAP update"); - try { - // Get user's current enabled state for comparison - $userCurrentEnabled = $user->isEnabled(); - $this->logger->error("User $username current enabled state from user->isEnabled(): " . ($userCurrentEnabled ? 'true' : 'false')); - - // Convert $newValue to boolean - handle various types that might be returned - // $event->getValue() might return: bool true/false, string "1"/"0", int 1/0, or null - $isEnabled = false; - $conversionMethod = 'unknown'; - - if ($newValue !== null) { - if (is_bool($newValue)) { - $isEnabled = $newValue; - $conversionMethod = 'direct_bool'; - } elseif (is_string($newValue)) { - // Handle string "1", "0", "true", "false", etc. - $isEnabled = filter_var($newValue, FILTER_VALIDATE_BOOLEAN); - $conversionMethod = 'filter_var_string'; - $this->logger->error("Converted string value '$newValue' to boolean: " . ($isEnabled ? 'true' : 'false')); - } elseif (is_numeric($newValue)) { - // Handle int 1/0 - $isEnabled = (bool) $newValue; - $conversionMethod = 'cast_numeric'; - $this->logger->error("Converted numeric value $newValue to boolean: " . ($isEnabled ? 'true' : 'false')); - } else { - // Fallback to user's actual enabled state if value is unexpected - $isEnabled = $user->isEnabled(); - $conversionMethod = 'fallback_user_state'; - $this->logger->error("Unexpected value type (" . gettype($newValue) . ") for enabled feature for user: $username, using user->isEnabled() instead. Value was: " . var_export($newValue, true)); - } - } else { - // If value is null, use user's actual enabled state - $isEnabled = $user->isEnabled(); - $conversionMethod = 'null_fallback_user_state'; - $this->logger->error("Event value is null for user: $username, using user->isEnabled() instead"); - } - - $this->logger->error("Updating LDAP active attributes for user: $username, final enabled value: " . ($isEnabled ? 'true' : 'false') . ", conversion method: $conversionMethod, event value: " . var_export($newValue, true) . ", user->isEnabled(): " . ($userCurrentEnabled ? 'true' : 'false')); - - $this->userService->mapActiveAttributesInLDAP($username, $isEnabled); - - $this->logger->error("Successfully called mapActiveAttributesInLDAP for user: $username with enabled: " . ($isEnabled ? 'true' : 'false')); - } catch (Exception $e) { - $this->logger->error('Failed to update LDAP attributes for user: ' . $username, [ - 'exception' => $e, - 'message' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); - } - } else { - $this->logger->error("User $username is not on LDAP backend (backend: $backend), skipping LDAP update"); - } - } - } - - private function updateQuota(string $username, string $backend, int $quotaInBytes) { - try { - if ($backend === 'SQL raw') { - $this->mailboxMapper->updateMailboxQuota($username, $quotaInBytes); - } - if ($backend === 'LDAP') { - $quotaAttribute = [ - 'quota' => $quotaInBytes - ]; - $this->LDAPConnectionService->updateAttributesInLDAP($username, $quotaAttribute); - } - } catch (Exception $e) { - $this->logger->error("Error setting quota for user $username " . $e->getMessage()); - } - } -} -- GitLab From 31b33c28f53e271e414d6bd3a8d990dd6e65009f Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 6 Jan 2026 13:38:37 +0530 Subject: [PATCH 3/5] changes in condition --- lib/Listeners/UserChangedListener.php | 2 +- lib/Service/SSOService.php | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/Listeners/UserChangedListener.php b/lib/Listeners/UserChangedListener.php index 2e429a27..545f92b8 100644 --- a/lib/Listeners/UserChangedListener.php +++ b/lib/Listeners/UserChangedListener.php @@ -84,9 +84,9 @@ class UserChangedListener implements IEventListener { } // Step 2: Update Keycloak Admin UI enabled status (this is the ONLY way to update Keycloak's internal enabled flag) + // Note: updateUserEnabledStatus logs success/failure internally, so we don't duplicate logs here try { $this->ssoService->updateUserEnabledStatus($username, $isEnabled); - $this->logger->error("[UserChangedListener] Successfully updated Keycloak enabled status for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); } catch (Exception $e) { $this->logger->error('[UserChangedListener] Failed to update Keycloak enabled status for user: ' . $username, ['exception' => $e]); // Don't throw - LDAP update is more critical, Keycloak update failure shouldn't break the flow diff --git a/lib/Service/SSOService.php b/lib/Service/SSOService.php index c6b9b5ca..5d19a9f6 100644 --- a/lib/Service/SSOService.php +++ b/lib/Service/SSOService.php @@ -456,7 +456,31 @@ class SSOService { $statusCode = $this->curl->getLastStatusCode(); if ($statusCode !== $expectedStatusCode) { - throw new SSOAdminAPIException('Error calling SSO API with url ' . $url . ' status code: ' . $statusCode); + // Try to parse error response from Keycloak + $errorMessage = 'Error calling SSO API with url ' . $url . ' status code: ' . $statusCode; + if (!empty($answer)) { + $errorResponse = json_decode($answer, true); + if (is_array($errorResponse)) { + if (isset($errorResponse['errorMessage'])) { + $errorMessage .= ' - ' . $errorResponse['errorMessage']; + } elseif (isset($errorResponse['error'])) { + $errorMessage .= ' - ' . $errorResponse['error']; + } elseif (isset($errorResponse['message'])) { + $errorMessage .= ' - ' . $errorResponse['message']; + } + // Log full response for debugging + $this->logger->error("[SSOService] Keycloak API error response: " . json_encode($errorResponse)); + } else { + // Log raw response if not JSON + $this->logger->error("[SSOService] Keycloak API error response (raw): " . substr($answer, 0, 500)); + } + } + throw new SSOAdminAPIException($errorMessage); + } + + // For 204 No Content responses, return null instead of trying to decode JSON + if ($statusCode === 204 || empty($answer)) { + return null; } $answer = json_decode($answer, true); -- GitLab From ab177608a950d1439c2e3bd21e752310195a2c8b Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 6 Jan 2026 13:46:24 +0530 Subject: [PATCH 4/5] more debugger --- lib/Listeners/UserChangedListener.php | 41 ++++++++++++++++++++++---- lib/Service/SSOService.php | 42 ++++++++++++++++++--------- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/lib/Listeners/UserChangedListener.php b/lib/Listeners/UserChangedListener.php index 545f92b8..e64d1237 100644 --- a/lib/Listeners/UserChangedListener.php +++ b/lib/Listeners/UserChangedListener.php @@ -47,66 +47,95 @@ class UserChangedListener implements IEventListener { $user = $event->getUser(); $username = $user->getUID(); $newValue = $event->getValue(); + $backend = $user->getBackend()->getBackendName(); + + $this->logger->error("[UserChangedListener] Event received - User: $username, Feature: $feature, Backend: $backend, Value type: " . gettype($newValue) . ", Value: " . var_export($newValue, true)); if ($feature === self::QUOTA_FEATURE) { + $this->logger->error("[UserChangedListener] Processing QUOTA_FEATURE for user: $username"); $updatedQuota = $event->getValue(); $quotaInBytes = (int) $this->util->computerFileSize($updatedQuota); - $backend = $user->getBackend()->getBackendName(); + $this->logger->error("[UserChangedListener] Quota update - User: $username, Quota: $updatedQuota, Bytes: $quotaInBytes, Backend: $backend"); $this->updateQuota($username, $backend, $quotaInBytes); } if ($feature === self::ENABLED_FEATURE) { + $this->logger->error("[UserChangedListener] Processing ENABLED_FEATURE for user: $username"); + $this->logger->error("[UserChangedListener] Event value - Type: " . gettype($newValue) . ", Value: " . var_export($newValue, true)); + // Convert $newValue to boolean - handle various types that might be returned $isEnabled = false; + $conversionMethod = 'unknown'; + if ($newValue !== null) { if (is_bool($newValue)) { $isEnabled = $newValue; + $conversionMethod = 'direct_bool'; } elseif (is_string($newValue)) { $isEnabled = filter_var($newValue, FILTER_VALIDATE_BOOLEAN); + $conversionMethod = 'filter_var_string'; } elseif (is_numeric($newValue)) { $isEnabled = (bool) $newValue; + $conversionMethod = 'cast_numeric'; } else { // Fallback to user's actual enabled state $isEnabled = $user->isEnabled(); + $conversionMethod = 'fallback_user_state'; + $this->logger->error("[UserChangedListener] Unexpected value type (" . gettype($newValue) . ") for user: $username, using user->isEnabled() instead"); } } else { // If value is null, use user's actual enabled state $isEnabled = $user->isEnabled(); + $conversionMethod = 'null_fallback_user_state'; + $this->logger->error("[UserChangedListener] Event value is null for user: $username, using user->isEnabled() instead"); } + + // Get user's current enabled state for comparison + $userCurrentEnabled = $user->isEnabled(); + $this->logger->error("[UserChangedListener] Conversion result - Method: $conversionMethod, Final enabled value: " . ($isEnabled ? 'true' : 'false') . ", user->isEnabled(): " . ($userCurrentEnabled ? 'true' : 'false')); // Step 1: Update LDAP attributes (active, mailActive) + $this->logger->error("[UserChangedListener] Step 1: Updating LDAP attributes for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); try { $this->userService->mapActiveAttributesInLDAP($username, $isEnabled); - $this->logger->error("[UserChangedListener] Successfully updated LDAP attributes for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); + $this->logger->error("[UserChangedListener] Step 1: Successfully updated LDAP attributes for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); } catch (Exception $e) { - $this->logger->error('[UserChangedListener] Failed to update LDAP attributes for user: ' . $username, ['exception' => $e]); + $this->logger->error("[UserChangedListener] Step 1: Failed to update LDAP attributes for user: $username", ['exception' => $e]); } // Step 2: Update Keycloak Admin UI enabled status (this is the ONLY way to update Keycloak's internal enabled flag) - // Note: updateUserEnabledStatus logs success/failure internally, so we don't duplicate logs here + $this->logger->error("[UserChangedListener] Step 2: Updating Keycloak enabled status for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); try { $this->ssoService->updateUserEnabledStatus($username, $isEnabled); + $this->logger->error("[UserChangedListener] Step 2: Successfully called updateUserEnabledStatus for user: $username"); } catch (Exception $e) { - $this->logger->error('[UserChangedListener] Failed to update Keycloak enabled status for user: ' . $username, ['exception' => $e]); + $this->logger->error("[UserChangedListener] Step 2: Failed to update Keycloak enabled status for user: $username", ['exception' => $e]); // Don't throw - LDAP update is more critical, Keycloak update failure shouldn't break the flow } + + $this->logger->error("[UserChangedListener] Completed ENABLED_FEATURE processing for user: $username"); } } private function updateQuota(string $username, string $backend, int $quotaInBytes) { + $this->logger->error("[UserChangedListener] updateQuota called - User: $username, Backend: $backend, Quota bytes: $quotaInBytes"); try { if ($backend === 'SQL raw') { + $this->logger->error("[UserChangedListener] Updating quota in SQL raw backend for user: $username"); $this->mailboxMapper->updateMailboxQuota($username, $quotaInBytes); + $this->logger->error("[UserChangedListener] Successfully updated quota in SQL raw for user: $username"); } if ($backend === 'LDAP') { + $this->logger->error("[UserChangedListener] Updating quota in LDAP backend for user: $username"); $quotaAttribute = [ 'quota' => $quotaInBytes ]; $this->LDAPConnectionService->updateAttributesInLDAP($username, $quotaAttribute); + $this->logger->error("[UserChangedListener] Successfully updated quota in LDAP for user: $username"); } } catch (Exception $e) { - $this->logger->error("Error setting quota for user $username " . $e->getMessage()); + $this->logger->error("[UserChangedListener] Error setting quota for user $username: " . $e->getMessage(), ['exception' => $e]); } } } diff --git a/lib/Service/SSOService.php b/lib/Service/SSOService.php index 5d19a9f6..c5bba775 100644 --- a/lib/Service/SSOService.php +++ b/lib/Service/SSOService.php @@ -85,7 +85,7 @@ class SSOService { 'credentials' => [$credentialEntry] ]; - $this->logger->debug('migrateCredential calling SSO API with url: '. $url . ' and data: ' . print_r($data, true)); + $this->logger->error('migrateCredential calling SSO API with url: '. $url . ' and data: ' . print_r($data, true)); $this->callSSOAPI($url, 'PUT', $data, 204); } @@ -100,7 +100,7 @@ class SSOService { $url = $this->ssoConfig['admin_rest_api_url'] . self::CREDENTIALS_ENDPOINT; $url = str_replace('{USER_ID}', $this->currentUserId, $url); $url .= '/' . $credentialId; - $this->logger->debug('deleteCredentials calling SSO API with url: '. $url); + $this->logger->error('deleteCredentials calling SSO API with url: '. $url); $this->callSSOAPI($url, 'DELETE', [], 204); } } @@ -113,7 +113,7 @@ class SSOService { $url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '/' . $this->currentUserId . '/logout'; - $this->logger->debug('logout calling SSO API with url: '. $url); + $this->logger->error('logout calling SSO API with url: '. $url); $this->callSSOAPI($url, 'POST', [], 204); // Revoke offline sessions for all clients @@ -159,7 +159,7 @@ class SSOService { $clientsCache = $this->cacheFactory->createDistributed('ecloud_accounts_realm_clients'); $cachedClients = $clientsCache->get($cacheKey); if ($cachedClients !== null) { - $this->logger->debug('Returning cached clients from distributed cache'); + $this->logger->error('Returning cached clients from distributed cache'); return $cachedClients; } @@ -193,7 +193,7 @@ class SSOService { // Get offline sessions for this client and user $offlineSessions = $this->getOfflineSessionsForClient($clientUUID); if (empty($offlineSessions)) { - $this->logger->debug('No offline sessions found for client: ' . $clientUUID); + $this->logger->error('No offline sessions found for client: ' . $clientUUID); return; } @@ -257,6 +257,11 @@ class SSOService { * Update user's enabled status in Keycloak Admin UI * This is the ONLY way to update Keycloak's internal "enabled" flag * + * Keycloak requires the full user object for PUT requests, so we: + * 1. Fetch the current user object + * 2. Update the enabled field + * 3. PUT the full user object back + * * @param string $username The Nextcloud username * @param bool $enabled Whether the user should be enabled (true) or disabled (false) * @throws SSOAdminAPIException If the API call fails @@ -268,13 +273,22 @@ class SSOService { } $url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '/' . $this->currentUserId; - $data = [ - 'enabled' => $enabled - ]; + + // Step 1: Fetch the current user object from Keycloak + $this->logger->error("[SSOService] Fetching Keycloak user object for: $username, Keycloak User ID: " . $this->currentUserId); + $userData = $this->callSSOAPI($url, 'GET', [], 200); + + if (empty($userData) || !is_array($userData)) { + throw new SSOAdminAPIException('Failed to fetch user data from Keycloak for user: ' . $username); + } + + // Step 2: Update the enabled field + $userData['enabled'] = $enabled; - $this->logger->info("[SSOService] Updating Keycloak enabled status for user: $username, enabled: " . ($enabled ? 'true' : 'false') . ", Keycloak User ID: " . $this->currentUserId); - $this->callSSOAPI($url, 'PUT', $data, 204); - $this->logger->info("[SSOService] Successfully updated Keycloak enabled status for user: $username"); + // Step 3: PUT the updated user object back + $this->logger->error("[SSOService] Updating Keycloak enabled status for user: $username, enabled: " . ($enabled ? 'true' : 'false') . ", Keycloak User ID: " . $this->currentUserId); + $this->callSSOAPI($url, 'PUT', $userData, 204); + $this->logger->error("[SSOService] Successfully updated Keycloak enabled status for user: $username"); } catch (\Exception $e) { $this->logger->error("[SSOService] Failed to update Keycloak enabled status for user: $username", [ 'exception' => $e, @@ -288,7 +302,7 @@ class SSOService { private function getCredentialIds() : array { $url = $this->ssoConfig['admin_rest_api_url'] . self::CREDENTIALS_ENDPOINT; $url = str_replace('{USER_ID}', $this->currentUserId, $url); - $this->logger->debug('getCredentialIds calling SSO API with url: '. $url); + $this->logger->error('getCredentialIds calling SSO API with url: '. $url); $credentials = $this->callSSOAPI($url, 'GET'); @@ -358,7 +372,7 @@ class SSOService { } $url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '?exact=true&email=' . $email; - $this->logger->debug('getUserId calling SSO API with url: '. $url); + $this->logger->error('getUserId calling SSO API with url: '. $url); $users = $this->callSSOAPI($url, 'GET'); if (empty($users) || !is_array($users) || !isset($users[0])) { throw new SSOAdminAPIException('Error: no user found for search with url: ' . $url); @@ -410,7 +424,7 @@ class SSOService { $headers = [ 'Content-Type: application/x-www-form-urlencoded' ]; - $this->logger->debug('getAdminAccessToken calling SSO API with url: '. $adminAccessTokenRoute . ' and headers: ' . print_r($headers, true) . ' and body: ' . print_r($requestBody, true)); + $this->logger->error('getAdminAccessToken calling SSO API with url: '. $adminAccessTokenRoute . ' and headers: ' . print_r($headers, true) . ' and body: ' . print_r($requestBody, true)); $response = $this->curl->post($adminAccessTokenRoute, $requestBody, $headers); if ($this->curl->getLastStatusCode() !== 200) { -- GitLab From 93fd6004a31ef29b70117704243b55ebe3fea706 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 6 Jan 2026 14:26:07 +0530 Subject: [PATCH 5/5] change in sso logic --- lib/Listeners/UserChangedListener.php | 10 +++++--- lib/Service/SSOService.php | 37 +++++++++------------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/lib/Listeners/UserChangedListener.php b/lib/Listeners/UserChangedListener.php index e64d1237..5d68bf0d 100644 --- a/lib/Listeners/UserChangedListener.php +++ b/lib/Listeners/UserChangedListener.php @@ -107,10 +107,14 @@ class UserChangedListener implements IEventListener { // Step 2: Update Keycloak Admin UI enabled status (this is the ONLY way to update Keycloak's internal enabled flag) $this->logger->error("[UserChangedListener] Step 2: Updating Keycloak enabled status for user: $username, enabled: " . ($isEnabled ? 'true' : 'false')); try { - $this->ssoService->updateUserEnabledStatus($username, $isEnabled); - $this->logger->error("[UserChangedListener] Step 2: Successfully called updateUserEnabledStatus for user: $username"); + $keycloakUpdateSuccess = $this->ssoService->updateUserEnabledStatus($username, $isEnabled); + if ($keycloakUpdateSuccess) { + $this->logger->error("[UserChangedListener] Step 2: Successfully updated Keycloak enabled status for user: $username"); + } else { + $this->logger->error("[UserChangedListener] Step 2: Keycloak update failed for user: $username (check SSOService logs for details)"); + } } catch (Exception $e) { - $this->logger->error("[UserChangedListener] Step 2: Failed to update Keycloak enabled status for user: $username", ['exception' => $e]); + $this->logger->error("[UserChangedListener] Step 2: Exception caught while updating Keycloak enabled status for user: $username", ['exception' => $e]); // Don't throw - LDAP update is more critical, Keycloak update failure shouldn't break the flow } diff --git a/lib/Service/SSOService.php b/lib/Service/SSOService.php index c5bba775..05193439 100644 --- a/lib/Service/SSOService.php +++ b/lib/Service/SSOService.php @@ -257,45 +257,32 @@ class SSOService { * Update user's enabled status in Keycloak Admin UI * This is the ONLY way to update Keycloak's internal "enabled" flag * - * Keycloak requires the full user object for PUT requests, so we: - * 1. Fetch the current user object - * 2. Update the enabled field - * 3. PUT the full user object back - * * @param string $username The Nextcloud username * @param bool $enabled Whether the user should be enabled (true) or disabled (false) - * @throws SSOAdminAPIException If the API call fails + * @return bool True if successful, false otherwise */ - public function updateUserEnabledStatus(string $username, bool $enabled): void { + public function updateUserEnabledStatus(string $username, bool $enabled): bool { try { if ($this->isNotCurrentUser($username)) { $this->setupUserId($username); } $url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '/' . $this->currentUserId; - - // Step 1: Fetch the current user object from Keycloak - $this->logger->error("[SSOService] Fetching Keycloak user object for: $username, Keycloak User ID: " . $this->currentUserId); - $userData = $this->callSSOAPI($url, 'GET', [], 200); - - if (empty($userData) || !is_array($userData)) { - throw new SSOAdminAPIException('Failed to fetch user data from Keycloak for user: ' . $username); - } - // Step 2: Update the enabled field - $userData['enabled'] = $enabled; - - // Step 3: PUT the updated user object back + // Only send what we need $this->logger->error("[SSOService] Updating Keycloak enabled status for user: $username, enabled: " . ($enabled ? 'true' : 'false') . ", Keycloak User ID: " . $this->currentUserId); - $this->callSSOAPI($url, 'PUT', $userData, 204); - $this->logger->error("[SSOService] Successfully updated Keycloak enabled status for user: $username"); + $this->callSSOAPI($url, 'PUT', [ + 'enabled' => $enabled, + ], 204); + + $this->logger->error("[SSOService] Keycloak user {$username} enabled=" . ($enabled ? 'true' : 'false')); + return true; + } catch (\Exception $e) { $this->logger->error("[SSOService] Failed to update Keycloak enabled status for user: $username", [ - 'exception' => $e, - 'message' => $e->getMessage() + 'exception' => $e ]); - // Don't throw - we don't want to break the user enable/disable flow if Keycloak update fails - // The LDAP update is more critical + return false; } } -- GitLab