Loading lib/Command/SyncMissingUsersToCommon.php +115 −6 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ class SyncMissingUsersToCommon extends Command { 'processed' => 0, 'success' => 0, 'errors' => 0, 'skipped' => 0, 'skip_reasons' => [], 'failed_reasons' => [] ]; Loading Loading @@ -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('<info>Starting user enumeration...</info>'); $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('<info>Found %d+ users in NextCloud (counting during processing)</info>', $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('<info>Progress: %d processed, %d success, %d errors</info>', $stats['processed'], $stats['success'], $stats['errors'])); $output->writeln(sprintf('<info>Progress: %d processed, %d success, %d errors (Total: %d, LDAP: %d, Non-LDAP: %d)</info>', $stats['processed'], $stats['success'], $stats['errors'], $userCount, $ldapUsers, $nonLdapUsers)); } } catch (Exception $e) { $errorMessage = sprintf('Unexpected error processing user %s: %s', $user->getUID(), $e->getMessage()); Loading @@ -149,6 +172,7 @@ class SyncMissingUsersToCommon extends Command { // Set total after processing for all users $stats['total'] = $stats['processed']; $output->writeln(sprintf('<info>Completed processing %d total users (LDAP: %d, Non-LDAP: %d)</info>', $stats['total'], $ldapUsers, $nonLdapUsers)); } /** Loading @@ -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(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; } // 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(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; } $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output); $stats['success']++; } catch (Exception $e) { $errorMessage = sprintf('Error processing user %s: %s', $username, $e->getMessage()); Loading @@ -193,8 +240,27 @@ class SyncMissingUsersToCommon extends Command { $output->writeln(sprintf('<info>Total users to process: %d</info>', $stats['total'])); $output->writeln(sprintf('<info>Users processed: %d</info>', $stats['processed'])); $output->writeln(sprintf('<info>Successfully synced: %d</info>', $stats['success'])); $output->writeln(sprintf('<info>Users skipped: %d</info>', $stats['skipped'])); $output->writeln(sprintf('<info>Errors encountered: %d</info>', $stats['errors'])); if (!empty($stats['skip_reasons'])) { $output->writeln(''); $output->writeln('<comment>=== SKIP REASONS ===</comment>'); // 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('<comment>• %s</comment>', $reason)); } if (count($stats['skip_reasons']) > $maxReasonsToShow) { $remaining = count($stats['skip_reasons']) - $maxReasonsToShow; $output->writeln(sprintf('<comment>... and %d more skips.</comment>', $remaining)); } } if (!empty($stats['failed_reasons'])) { $output->writeln(''); $output->writeln('<error>=== FAILURE REASONS ===</error>'); Loading Loading @@ -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)); Loading @@ -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(' <comment>Skipping user %s: not on LDAP backend</comment>', $username)); $output->writeln(sprintf(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; // Skip this user gracefully } Loading Loading @@ -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(' <info>✓ Non-LDAP user synced successfully to common database: %s</info>', $usernameWithoutDomain)); } else { $output->writeln(sprintf(' <comment>Would sync non-LDAP user to common database: %s</comment>', $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); } } } Loading
lib/Command/SyncMissingUsersToCommon.php +115 −6 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ class SyncMissingUsersToCommon extends Command { 'processed' => 0, 'success' => 0, 'errors' => 0, 'skipped' => 0, 'skip_reasons' => [], 'failed_reasons' => [] ]; Loading Loading @@ -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('<info>Starting user enumeration...</info>'); $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('<info>Found %d+ users in NextCloud (counting during processing)</info>', $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('<info>Progress: %d processed, %d success, %d errors</info>', $stats['processed'], $stats['success'], $stats['errors'])); $output->writeln(sprintf('<info>Progress: %d processed, %d success, %d errors (Total: %d, LDAP: %d, Non-LDAP: %d)</info>', $stats['processed'], $stats['success'], $stats['errors'], $userCount, $ldapUsers, $nonLdapUsers)); } } catch (Exception $e) { $errorMessage = sprintf('Unexpected error processing user %s: %s', $user->getUID(), $e->getMessage()); Loading @@ -149,6 +172,7 @@ class SyncMissingUsersToCommon extends Command { // Set total after processing for all users $stats['total'] = $stats['processed']; $output->writeln(sprintf('<info>Completed processing %d total users (LDAP: %d, Non-LDAP: %d)</info>', $stats['total'], $ldapUsers, $nonLdapUsers)); } /** Loading @@ -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(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; } // 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(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; } $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output); $stats['success']++; } catch (Exception $e) { $errorMessage = sprintf('Error processing user %s: %s', $username, $e->getMessage()); Loading @@ -193,8 +240,27 @@ class SyncMissingUsersToCommon extends Command { $output->writeln(sprintf('<info>Total users to process: %d</info>', $stats['total'])); $output->writeln(sprintf('<info>Users processed: %d</info>', $stats['processed'])); $output->writeln(sprintf('<info>Successfully synced: %d</info>', $stats['success'])); $output->writeln(sprintf('<info>Users skipped: %d</info>', $stats['skipped'])); $output->writeln(sprintf('<info>Errors encountered: %d</info>', $stats['errors'])); if (!empty($stats['skip_reasons'])) { $output->writeln(''); $output->writeln('<comment>=== SKIP REASONS ===</comment>'); // 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('<comment>• %s</comment>', $reason)); } if (count($stats['skip_reasons']) > $maxReasonsToShow) { $remaining = count($stats['skip_reasons']) - $maxReasonsToShow; $output->writeln(sprintf('<comment>... and %d more skips.</comment>', $remaining)); } } if (!empty($stats['failed_reasons'])) { $output->writeln(''); $output->writeln('<error>=== FAILURE REASONS ===</error>'); Loading Loading @@ -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)); Loading @@ -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(' <comment>Skipping user %s: not on LDAP backend</comment>', $username)); $output->writeln(sprintf(' <comment>Skipping user %s: %s</comment>', $username, $skipReason)); } return; // Skip this user gracefully } Loading Loading @@ -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(' <info>✓ Non-LDAP user synced successfully to common database: %s</info>', $usernameWithoutDomain)); } else { $output->writeln(sprintf(' <comment>Would sync non-LDAP user to common database: %s</comment>', $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); } } }