The database schema for {user} does not require that email addresses are unique, and user_save() does not prevent saving multiple users with the same address. However, when user with a non-unique address submits the account form on user/123/edit, the form validation fails with the following error message:

The e-mail address c960657@example.com is already taken.

The error occurs, even if the registered mail address has not been changed, i.e. this prevents the user from changing other parts of the profile.

I know that other parts of Drupal does not properly handle duplicate email addresses, e.g. the lost password feature, but we should either fix those parts or enforce the uniqueness in the database schema and/or in user_save().

Comments

c960657’s picture

Status: Active » Needs review
StatusFileSize
new4.21 KB

This patch adds a hidden setting. I think it's reasonable to make it hidden, because once you allow duplicate email addresses, you cannot just disallow them again without manually removing the duplicates.

The patch also modifies the lost password feature in a crude way, so that multiple mails are sent. This is not that user friendly, but I would like to hear some opinion on the general approach in this issue before I dig into the details.

Also, this needs tests.

Georg’s picture

My personal take on this would be to only allow unique eMailadresses. One account per eMailadress.

The only people I could think of that need more than one account, would be admins. Those people I expect to have more than one eMailadress by hand anyway.

So I don't see a reason to allow multiaccounts per eMailadress.

But if it is easier to change everything to support multiple accounts per eMailadress, then I'm fine with that.

c960657’s picture

It's not unlikely that two people (e.g. married couples) share one email address.

In some setups, the user list in Drupal is mirrored from some other system, e.g. a single sign-on system. Any constraint added by Drupal that does not exist in the source system makes the mirroring harder. This is also a problem when mirroring users from two or more separate systems and you don't want to combine the profile fields of two different users even though they share an email address.

I'm not sure what the intention behind the current database scheme is, i.e. whether it's a feature or an oversight.

sivaji_ganesh_jojodae’s picture

Status: Needs review » Closed (won't fix)

I don't think this feature will made into core. You could use contrib modules like Shared email http://drupal.org/project/sharedemail to achieve it.

Edit: ..and this is not a bug perhaps feature request.

drewish’s picture

Version: 7.x-dev » 8.x-dev
Status: Closed (won't fix) » Needs work

This is definitely wonky. We should either allow duplicate emails or not. We've got the worst of both worlds right now.

c960657’s picture

Status: Needs work » Needs review
StatusFileSize
new5.65 KB

Rerolled patch with tests added.

Summary:
The patch introduces a hidden setting that allows e-mails to be non-unique. The "reset password" feature is rudimentarily extended so that specifying an email address will send one mail per account with that email.

c960657’s picture

StatusFileSize
new5.62 KB

Reroll.

aaronbauman’s picture

Thanks for your work on the patches above, Christian.
I think you've demonstrated the tip of the iceberg in terms of how major a change supporting duplicate emails would be.
Further, offering configurable uniqueness of email will be even more work.

I agree with drewish - we're currently in the worst of both worlds.
I will cast my vote for disallowing duplicate emails explicitly, and enforcing uniqueness of email in the database.
Here's how I usually hack core on my own sites:

diff --git a/core/modules/user/user.install b/core/modules/user/user.install
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -237,6 +237,7 @@
     ),
     'unique keys' => array(
       'name' => array('name'),
+      'mail' => array('mail'),
     ),
     'primary key' => array('uid'),
     'foreign keys' => array(

This patch is 100% sufficient for new sites, but of course any installs with existing dupes will fail updates.
Failing updates is a big deal, and we've painted ourselves into a corner by being wishy-washy about email for the past 10 years.
But better late than never.

From a UX perspective, I can give you an example of how terrible a system is that allows duplicate emails.
You may not be familiar with this amazon.com "feature", but their user system validates uniqueness on a dual key: email+password.
The other day I logged into my amazon account with my email address and one of my throwaway common passwords, only to find that I had no order history even though I had recently placed a new order.
Further, if I had placed orders under both accounts, there is no way to merge the two accounts.
I'd have to explicitly request one account to be deleted, and I'd lose all my order history.
Awful awful UX.

If Drupal were to fully support non-unique email, we'd have to take a similar approach, which implies some pretty fundamental shifts to any subsystem that deals with users.

c960657’s picture

It may be tricky to enforce uniqueness if the users log have their profile data updated from some external source (e.g. a single sign-on system). Even if the external system enforces uniqueness, there may be duplicates in Drupal unless all changes in the external system are synced to Drupal immediately.

If we are to allow users to use their email address as identifier instead of a username, we should definitely require uniqueness (the Amazon approach sounds wacky).

aaronbauman’s picture

It's not tricky to enforce uniqueness: just add a unique key to {users} and throw an exception when someone breaks it (even an external source).

Right now, core assumes and requires uniqueness of email without actually enforcing it.
This is broken.
And it leads to inconsistencies in the database, and a completely broken UX in which a user cannot update their own user record.

In my opinion, accommodating a non-unique external identifier is not something core should be concerned with.
In general, I think scenarios like the one you're describing need to be handled in contrib.

bleen’s picture

Title: Cannot save user profile form when non-unique email address is used » Enforce unique email addresses for users

I have to agree with aaronbauman. We already require a unique email address - if you go to /admin/people/create and use the GUI to try creating a user with a taken email and you get the message "The email address %mail is already taken".

Let's start enforcing...

c960657’s picture

We already require a unique email address

Why do we do that? It seems we do not use this uniqueness for anything, so why have that restriction? If there was a phone number user profile field in core by default, should we require that to be unique too? :-)

It's not tricky to enforce uniqueness:

Sorry, I didn't make myself clear. I did write that it is hard to enforce uniqueness, but what I meant ( :-) ) was that it is hard to sync users from some external user database into Drupal when there is uniqueness on not only 1 but 2 columns. This is also the case if both email and username are unique in the external user database if the changes are not synced immediately (or at least in the same order) but e.g. are synced when the user logs in.

Assume 1) user A changes his email from foo@example.org to bar@example.org, and then 2) user B changes his email from baz@example.org to foo@example.org, then change #1 must be synced before #2, otherwise the uniqueness constraint will be violated. This problem may arise even if all users are synced in one batch during a feed.

In my opinion, accommodating a non-unique external identifier is not something core should be concerned with.
In general, I think scenarios like the one you're describing need to be handled in contrib.

I agree that the actual login/mapping etc. should happen in a contrib module. But it is hardly kosher for a contrib module to alter the database schema of {users} by removing a unique key.

Drupal are used more and more on sites owned by large organisations. Often the Drupal site is not the only web site or user database in the organisation, and it is generally preferably to support some kind of single sign-on system across all systems (external web sites would have a regular web-based log-in system, and internal web sites would interface with e.g. an Active Directory), so interfacing against various external user databases with various uniquness constraints is an important feature for a system targeting enterprise users.

bleen’s picture

Dont many (most?) external login contrib modules simply create a ghost user in the Drupal users table with a username of some-module-user1 and some throw-away email address? This is not to say that all modules do this, but certainly some do (IIRC fb.module does this for example) so there is a solution to this for contrib.

And cant I make the same argument you are making about making life easier on contrib by pointing to the several modules out there that allow people to log in using his/her email?

c960657’s picture

Using dummy usernames is not a problem, because using hook_username_alter() contrib modules can ensure that they are never shown to the end-user. Using a throwaway email address will break modules that send email to the user.

And cant I make the same argument you are making about making life easier on contrib by pointing to the several modules out there that allow people to log in using his/her email?

I'm not sure I understand you correctly. It is easy to prevent duplicate emails even if the database table allows duplicates (core does this already). But if the database does not allow duplicates, a contrib module cannot allow them without changing the database scheme or saving the user's email address somewhere else than in {users}.mail.

We could also optionally add the unique key depending on the setting I suggested in my patch. This would support both use-cases equally good, though it would be a rather unusual approach.

c960657’s picture

StatusFileSize
new5.53 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch, user-mail-unique-4.patch, failed testing.

c960657’s picture

Status: Needs work » Needs review

#15: user-mail-unique-4.patch queued for re-testing.

aaronbauman’s picture

We already require a unique email address

Why do we do that?

This is a fundamental question, but it's outside the scope of this issue.
If you want to open a discussion about whether we should change the existing assumption, that belongs in a new issue.
In the meantime, we can fix the buggy behavior by enforcing this assumption.

jbrown’s picture

#15: user-mail-unique-4.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, user-mail-unique-4.patch, failed testing.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dpi’s picture

Issue summary: View changes
Status: Needs work » Closed (outdated)

The user entity includes a validator on the mail field for uniqueness. See \Drupal\user\Plugin\Validation\Constraint\UserMailUnique.

dpi’s picture

The argument for non-unique email addresses could be made in #286401: Make email not required for a Drupal site account

avpaderno’s picture

Version: 8.2.x-dev » 9.3.x-dev
Status: Closed (outdated) » Needs work
Issue tags: +Needs reroll

This issue is about adding a unique key for the email database field.

Drupal sets many database fields as part of a unique key, even if form validation handlers verify the entered value isn't already used. One thing doesn't exclude the other one.
The other issue is about not setting as required the email field. It simply means that an empty value for the email address would be acceptable, but this doesn't exclude the validation code may reject a value that is already present in the database. It just means the validation code needs to first check the entered value isn't empty; if it's empty, it doesn't proceed with the validation.

The code to change is in UserStorageSchema::getEntitySchema(), the method that alters the database schema for the User entity, which now contains the following code.

protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
  $schema = parent::getEntitySchema($entity_type, $reset);
  if ($data_table = $this->storage->getDataTable()) {
    $schema[$data_table]['unique keys'] += [
      'user__name' => [
        'name',
        'langcode',
      ],
    ];
  }
  return $schema;
}
avpaderno’s picture

Title: Enforce unique email addresses for users » Change the database schema for the User entity and add a unique key for the email
Category: Bug report » Task
cilefen’s picture

What will occur if there are duplicates when the schema change is implemented?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

karishmaamin’s picture

StatusFileSize
new2.59 KB

Tried re-rolling patch against 9.4.x

avpaderno’s picture

Status: Needs work » Needs review
ranjith_kumar_k_u’s picture

StatusFileSize
new2.59 KB
new640 bytes

Fixed cSpell error.

Status: Needs review » Needs work

The last submitted patch, 31: 771002-31.patch, failed testing. View results

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

shubham chandra’s picture

StatusFileSize
new2.6 KB

Re-rolled patch against #31 in Drupal 9.5.x

avpaderno’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 34: 771002-34.patch, failed testing. View results

sandeepsingh199’s picture

StatusFileSize
new2.68 KB

fixed #34 drupalPost() error

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Ankit.Gupta’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new2.7 KB

Reroll the patch #37 with Drupal 10.1.x

avpaderno’s picture

Status: Needs review » Needs work
+    $edit['pass[pass1]'] = $new_pass = $this->randomName();
+    $edit['pass[pass2]'] = $new_pass;

randomName() isn't defined in the test class or its parent classes.

+    // Allow multiple accounts to use the same e-mail address.
+    variable_set('user_mail_unique', FALSE);

variable_set() is a Drupal 7 function.

nikhil_110’s picture

StatusFileSize
new2.91 KB
new3.57 KB

Try to fixed #39 Cc Failed

nikhil_110’s picture

StatusFileSize
new603 bytes
new3.56 KB

Fixed #41 Cc Failed.

avpaderno’s picture

+    $random_generator = new Random();
 
     $edit = [];

The UserRegistrationTest class has the randomMachineName() and the randomString() methods. Using the Random class should not be necessary.

dpi’s picture

I still stand by my comment from #23.

I dont think we should be enforcing database level uniqueness, defer to validators and contrib to solve these issues.

Shall we get a framework maintainer opinion on this? (currently: Moshe)

poker10’s picture

I think there are still some unresolved questions, for example the one from #27:

What will occur if there are duplicates when the schema change is implemented?

It is also important to think about case-sensitivity. In MySQL unique columns are case insensitive by default, so you cannot have test@test.com and TEST@test.com. On the other side, in PostgreSQL, these are case sensitive and by default it will allow test@test.com and TEST@test.com in the unique columns. This can cause another confusion if not handled properly.

In my opinion, the uniqueness validation could remain only in code. There are lot of contrib modules creating weird email addresses just to workaround the need of the email address, or even saving empty email addresses and this change could potentially break all these.

aaronbauman’s picture

Should we get rid of other database constraints as well then?
How do we justify having unique constraints for UUID or username, but not email?

Seems like maybe we need to define a decision making criteria for when database constraints are warranted.

moshe weitzman’s picture

Status: Needs work » Closed (won't fix)

Hi all - user module maintainer here.

If I were designing Drupal from scratch, I would certainly put a uniqueness constraint in the DB. But we are now 22 years into Drupal's life, and I think that ship has sailed. We dont want to disrupt existing sites so much. Folks who want to add this constraint to their DB are free to do so via schema alter. See UserStorageSchema::getEntitySchema().

Lets focus improving our add/edit validators as needed. And lets do that in other issues.

I respectfully close this long-standing issue.

daffie’s picture

I think this functionality would be great for a contrib module.