diff --git a/lib/Command/SyncMissingUsersToCommon.php b/lib/Command/SyncMissingUsersToCommon.php index c4828dba780854654784081ea4bbd5411e0ab928..b506b67c969ea15bad912162345e0e31bf508429 100644 --- a/lib/Command/SyncMissingUsersToCommon.php +++ b/lib/Command/SyncMissingUsersToCommon.php @@ -93,6 +93,8 @@ class SyncMissingUsersToCommon extends Command { 'processed' => 0, 'success' => 0, 'errors' => 0, + 'skipped' => 0, + 'skip_reasons' => [], 'failed_reasons' => [] ]; @@ -130,14 +132,35 @@ class SyncMissingUsersToCommon extends Command { * Process all users from NextCloud */ private function processAllUsers(string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void { - $this->userManager->callForAllUsers(function (IUser $user) use ($ipAddress, $isDryRun, $output, &$stats) { + $userCount = 0; + $ldapUsers = 0; + $nonLdapUsers = 0; + + $output->writeln('Starting user enumeration...'); + + $this->userManager->callForAllUsers(function (IUser $user) use ($ipAddress, $isDryRun, $output, &$stats, &$userCount, &$ldapUsers, &$nonLdapUsers) { + $userCount++; + $backend = $user->getBackend(); + $backendName = $backend->getBackendName(); + + if ($backendName === 'LDAP') { + $ldapUsers++; + } else { + $nonLdapUsers++; + } + + // Show initial count after first user + if ($userCount === 1) { + $output->writeln(sprintf('Found %d+ users in NextCloud (counting during processing)', $userCount)); + } + try { $this->processSingleUser($user->getUID(), $ipAddress, $isDryRun, $output, $stats); // Reduce output frequency for better performance if ($stats['processed'] > 0 && $stats['processed'] % 1000 === 0) { - $output->writeln(sprintf('Progress: %d processed, %d success, %d errors', - $stats['processed'], $stats['success'], $stats['errors'])); + $output->writeln(sprintf('Progress: %d processed, %d success, %d errors (Total: %d, LDAP: %d, Non-LDAP: %d)', + $stats['processed'], $stats['success'], $stats['errors'], $userCount, $ldapUsers, $nonLdapUsers)); } } catch (Exception $e) { $errorMessage = sprintf('Unexpected error processing user %s: %s', $user->getUID(), $e->getMessage()); @@ -149,6 +172,7 @@ class SyncMissingUsersToCommon extends Command { // Set total after processing for all users $stats['total'] = $stats['processed']; + $output->writeln(sprintf('Completed processing %d total users (LDAP: %d, Non-LDAP: %d)', $stats['total'], $ldapUsers, $nonLdapUsers)); } /** @@ -171,10 +195,33 @@ class SyncMissingUsersToCommon extends Command { $usernameWithoutDomain = $this->userService->stripLegacyDomainFromUsername($username); if ($this->userService->isUsernameTaken($usernameWithoutDomain)) { // This is not an error, just a skip - user already exists + $skipReason = sprintf('User already exists in common database: %s', $usernameWithoutDomain); + $stats['skipped']++; + $stats['skip_reasons'][] = $skipReason; + if ($isDryRun) { + $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason)); + } return; } - $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output); + // Check if user is on LDAP backend + $backend = $user->getBackend(); + $backendName = $backend->getBackendName(); + + if ($backendName === 'LDAP') { + // Full sync with LDAP metadata + $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output, $stats); + } else { + // Skip non-LDAP users + $skipReason = sprintf('Non-LDAP user: %s', $username); + $stats['skipped']++; + $stats['skip_reasons'][] = $skipReason; + if ($isDryRun) { + $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason)); + } + return; + } + $stats['success']++; } catch (Exception $e) { $errorMessage = sprintf('Error processing user %s: %s', $username, $e->getMessage()); @@ -193,8 +240,27 @@ class SyncMissingUsersToCommon extends Command { $output->writeln(sprintf('Total users to process: %d', $stats['total'])); $output->writeln(sprintf('Users processed: %d', $stats['processed'])); $output->writeln(sprintf('Successfully synced: %d', $stats['success'])); + $output->writeln(sprintf('Users skipped: %d', $stats['skipped'])); $output->writeln(sprintf('Errors encountered: %d', $stats['errors'])); + if (!empty($stats['skip_reasons'])) { + $output->writeln(''); + $output->writeln('=== SKIP REASONS ==='); + + // Limit the number of reasons shown for performance + $maxReasonsToShow = 50; + $reasonsToShow = array_slice($stats['skip_reasons'], 0, $maxReasonsToShow); + + foreach ($reasonsToShow as $reason) { + $output->writeln(sprintf('• %s', $reason)); + } + + if (count($stats['skip_reasons']) > $maxReasonsToShow) { + $remaining = count($stats['skip_reasons']) - $maxReasonsToShow; + $output->writeln(sprintf('... and %d more skips.', $remaining)); + } + } + if (!empty($stats['failed_reasons'])) { $output->writeln(''); $output->writeln('=== FAILURE REASONS ==='); @@ -225,9 +291,10 @@ class SyncMissingUsersToCommon extends Command { * @param string $ipAddress The IP address to use * @param bool $isDryRun Whether this is a dry run * @param OutputInterface $output The output interface for writing messages + * @param array $stats The stats array to update * @return void */ - private function syncUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output): void { + private function syncUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void { // Only show processing message in dry run mode for performance if ($isDryRun) { $output->writeln(sprintf('Processing user: %s', $username)); @@ -240,8 +307,11 @@ class SyncMissingUsersToCommon extends Command { // Check if user is on LDAP backend - skip if not if (!$this->ldapConnectionService->isUserOnLDAPBackend($user)) { // Skip users that are not on LDAP backend + $skipReason = sprintf('User not on LDAP backend: %s', $username); + $stats['skipped']++; + $stats['skip_reasons'][] = $skipReason; if ($isDryRun) { - $output->writeln(sprintf(' Skipping user %s: not on LDAP backend', $username)); + $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason)); } return; // Skip this user gracefully } @@ -288,4 +358,43 @@ class SyncMissingUsersToCommon extends Command { throw new Exception(sprintf("Failed to sync user %s: %s", $username, $e->getMessage()), 0, $e); } } + + /** + * Sync a non-LDAP user to the common database (just username) + * + * @param string $username The username to sync + * @param string $ipAddress The IP address to use + * @param bool $isDryRun Whether this is a dry run + * @param OutputInterface $output The output interface for writing messages + * @param array $stats The stats array to update + * @return void + */ + private function syncNonLdapUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void { + // Only show processing message in dry run mode for performance + if ($isDryRun) { + $output->writeln(sprintf('Processing non-LDAP user: %s', $username)); + } + + try { + // Strip legacy domain from username + $usernameWithoutDomain = $this->userService->stripLegacyDomainFromUsername($username); + + if (!$isDryRun) { + // Add user to common database with minimal data + $this->userService->addUsernameToCommonDataStore( + $usernameWithoutDomain, + $ipAddress, + '', // No recovery email for non-LDAP users + date('Y-m-d H:i:s') // Use current time formatted as YYYY-MM-DD HH:MM:SS + ); + // Show success message for actual syncs + $output->writeln(sprintf(' ✓ Non-LDAP user synced successfully to common database: %s', $usernameWithoutDomain)); + } else { + $output->writeln(sprintf(' Would sync non-LDAP user to common database: %s', $usernameWithoutDomain)); + } + } catch (Exception $e) { + // Re-throw the exception to be caught by the caller + throw new Exception(sprintf("Failed to sync non-LDAP user %s: %s", $username, $e->getMessage()), 0, $e); + } + } }