diff --git a/README.md b/README.md index 58d2499c9da45778265e51ce7222b333c6320fe6..a81343a34af04d66507f9243da5a84ce13c3a386 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,34 @@ - This plugin calls the postDelete.php script in the /e/ docker-welcome container - The e_welcome_secret is loaded in nextcloud's config file during ecloud-selfhosting installation. +## Fix Missing Email Addresses + +- This app provides a command to fix missing email addresses by querying LDAP and setting them in NextCloud + +### Usage + +```bash +# Fix all users without email addresses (dry run first) +occ ecloud-accounts:fix-missing-emails --dry-run + +# Fix all users without email addresses +occ ecloud-accounts:fix-missing-emails + +# Fix specific users (multiple --users arguments) +occ ecloud-accounts:fix-missing-emails --users=user1 --users=user2 --users=user3 + +# Fix specific users with dry run +occ ecloud-accounts:fix-missing-emails --users=user1 --users=user2 --dry-run +``` + +### What it does + +1. Queries the database to find users without email addresses set in NextCloud +2. For each user, checks if they are on the LDAP backend +3. Queries LDAP to get the `mailAddress` attribute +4. Sets only the email address in NextCloud (quota remains unchanged) +5. Provides detailed output of the process and any errors encountered + ## Support Please open issues here : https://gitlab.e.foundation/e/backlog/issues diff --git a/appinfo/info.xml b/appinfo/info.xml index 3dbec5c8418390b93c4aed605cef6683d40959ce..2e1d4393ddef691e6aee396b51fef3683b1ff32b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -28,5 +28,6 @@ OCA\EcloudAccounts\Command\Migrate2FASecrets OCA\EcloudAccounts\Command\MigrateWebmailAddressbooks OCA\EcloudAccounts\Command\MapActiveAttributetoLDAP + OCA\EcloudAccounts\Command\FixMissingEmails diff --git a/lib/Command/FixMissingEmails.php b/lib/Command/FixMissingEmails.php new file mode 100644 index 0000000000000000000000000000000000000000..98734c4b48f13a8e2e955627536c56c3e4234093 --- /dev/null +++ b/lib/Command/FixMissingEmails.php @@ -0,0 +1,168 @@ +ldapConnectionService = $ldapConnectionService; + $this->userService = $userService; + $this->dbConnection = $dbConnection; + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName(Application::APP_ID . ':fix-missing-emails') + ->setDescription('Fixes missing email addresses by querying LDAP and setting them in NextCloud') + ->addOption( + 'users', + null, + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Users to fix (can be specified multiple times, e.g., --users=user1 --users=user2)', + [] + ) + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Show what would be done without making changes' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + try { + $this->commandOutput = $output; + $isDryRun = $input->getOption('dry-run'); + $usernames = $input->getOption('users'); + + if ($isDryRun) { + $this->commandOutput->writeln('DRY RUN MODE - No changes will be made'); + } + + // Get users without email + $usersWithoutEmail = $this->getUsersWithoutEmail($usernames); + + if (empty($usersWithoutEmail)) { + $this->commandOutput->writeln('No users found without email addresses.'); + return 0; + } + + $this->commandOutput->writeln(sprintf('Found %d users without email addresses.', count($usersWithoutEmail))); + + $successCount = 0; + $errorCount = 0; + + foreach ($usersWithoutEmail as $username) { + try { + $this->fixUserEmail($username, $isDryRun); + $successCount++; + } catch (Exception $e) { + $this->commandOutput->writeln(sprintf('Error fixing email for user %s: %s', $username, $e->getMessage())); + $errorCount++; + } + } + + $this->commandOutput->writeln(sprintf('Processed %d users successfully, %d errors.', $successCount, $errorCount)); + return 0; + } catch (Exception $e) { + $this->commandOutput->writeln(sprintf('Error: %s', $e->getMessage())); + return 1; + } + } + + /** + * Get users without email addresses + * + * @param array $usernames Array of usernames to check (if empty, all users without email will be processed) + * @return array Array of usernames without email + */ + private function getUsersWithoutEmail(array $usernames = []): array { + $query = $this->dbConnection->getQueryBuilder(); + $query->select('a.uid') + ->from('accounts', 'a') + ->leftJoin('a', 'preferences', 'p', $query->expr()->andX( + $query->expr()->eq('a.uid', 'p.userid'), + $query->expr()->eq('p.appid', $query->createNamedParameter('settings')), + $query->expr()->eq('p.configkey', $query->createNamedParameter('email')) + )) + ->where($query->expr()->isNull('p.userid')); + + if (!empty($usernames)) { + $placeholders = []; + foreach ($usernames as $username) { + $placeholders[] = $query->createNamedParameter($username); + } + $query->andWhere($query->expr()->in('a.uid', $placeholders)); + } + + $result = $query->execute(); + $users = []; + while ($row = $result->fetch()) { + $users[] = $row['uid']; + } + $result->closeCursor(); + + return $users; + } + + /** + * Fix email address for a specific user + * + * @param string $username The username to fix + * @param bool $isDryRun Whether this is a dry run + * @return void + */ + private function fixUserEmail(string $username, bool $isDryRun = false): void { + $this->commandOutput->writeln(sprintf('Processing user: %s', $username)); + + // Check if user exists and is on LDAP backend + $user = $this->userManager->get($username); + if (!$user) { + throw new Exception("User not found in NextCloud"); + } + + if (!$this->ldapConnectionService->isUserOnLDAPBackend($user)) { + throw new Exception("User is not on LDAP backend"); + } + + $mailAddress = $this->ldapConnectionService->getUserMailAddress($username); + + if (empty($mailAddress)) { + throw new Exception("No mailAddress found in LDAP for user"); + } + + $this->commandOutput->writeln(sprintf(' Found email in LDAP: %s', $mailAddress)); + + if (!$isDryRun) { + $this->userService->setUserEmail($username, $mailAddress); + $this->commandOutput->writeln(sprintf(' Email set successfully for user: %s', $username)); + } else { + $this->commandOutput->writeln(sprintf(' Would set email to: %s', $mailAddress)); + } + } +} diff --git a/lib/Service/LDAPConnectionService.php b/lib/Service/LDAPConnectionService.php index f7609941af21cc77e50b6ec2de74a61732f0fa0f..b63fb339fa4cf322e9d1063560ab874d833ef4ed 100644 --- a/lib/Service/LDAPConnectionService.php +++ b/lib/Service/LDAPConnectionService.php @@ -122,4 +122,43 @@ class LDAPConnectionService { $this->closeLDAPConnection($conn); } + + /** + * Get mailAddress for a user directly from LDAP + * + * @param string $username The username to search for + * @return string|null The mailAddress or null if not found + * @throws Exception If LDAP is not enabled or user not found + */ + public function getUserMailAddress(string $username): ?string { + if (!$this->isLDAPEnabled()) { + throw new Exception('LDAP backend is not enabled'); + } + + $conn = $this->getLDAPConnection(); + $userDn = $this->username2dn($username); + + if ($userDn === false) { + throw new Exception('Could not find DN for username: ' . $username); + } + + $result = ldap_read($conn, $userDn, '(objectClass=*)', ['mailAddress']); + + if (!$result) { + $this->closeLDAPConnection($conn); + throw new Exception('Could not read user entry from LDAP for username: ' . $username); + } + + $entries = ldap_get_entries($conn, $result); + $this->closeLDAPConnection($conn); + + if ($entries['count'] === 0) { + throw new Exception('User not found in LDAP: ' . $username); + } + + $entry = $entries[0]; + $mailAddress = $entry['mailaddress'][0] ?? null; + + return $mailAddress; + } } diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index 7b8098a9527225e69459a873388129c1ccf22b0d..1cd8fece1ec226d3a3c020c7650493d7b9104683 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -386,6 +386,22 @@ class UserService { $user->setQuota($quota); } + /** + * Set only the email address for a user without modifying quota. + * + * @param string $uid The unique identifier of the user. + * @param string $mailAddress The email address to set for the user. + * + * @return void + */ + public function setUserEmail(string $uid, string $mailAddress): void { + $user = $this->getUser($uid); + if (is_null($user)) { + throw new Exception("User with username '$uid' not found."); + } + $user->setEMailAddress($mailAddress); + } + public function isUsernameTaken(string $username) : bool { $commonServicesURL = $this->apiConfig['commonServicesURL']; $commonApiVersion = $this->apiConfig['commonApiVersion'];