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'];