Problem

User entity validation misses some of the logic from AccountForm::validate(), and the logic is duplicated there. This is critical as the validation logic will be missed from RESTful validation. (see #1696648: [META] Untie content entity validation from form validation)

The missing validation is logic is related only to automatic validation done by FAPI else, i.e. allowed values validation from signature formats or timezones. In alternative to fixing validation only, the completion of the following issues would (most likely) address this issue also:

#2227381: Apply formatters and widgets to User base fields 'name' and 'email'
#1548204: Remove user signature and move it to contrib

Proposed resolution

Convert the logic to entity validation and leverage entity validation in form validation.

Remaining tasks

Implement proposed resolution.

User interface changes

-

API changes

-

Comments

fago’s picture

Title: User entity validation misses the logic from » User entity validation misses form validation logic
larowlan’s picture

Issue tags: +Critical Office Hours

I think I could talk someone with some plugins experience through this

berdir’s picture

Assigned: Unassigned » fago

Actually, I think we have all of this covered?

User name: Check
User unique: Check
Mail unique: Check
Signature: Check (this is using the field definition max_length, so...)

We also have tests for this in UserValidationTest

However, there are some parts here that better hidden and are not so easy to do. Specifically, that is the part about having to provide the existing password when you want to change the password, username or e-mail. That's dynamic, optional validation depending on the fact whether another field changes or not. I don't think we can deal with that at the moment, and we can also not map that concept to REST at all as far as I can see?

I saw an issue open about this, but this example might be even complexer than that? Assigning to @fago for feedback.

berdir’s picture

See also how https://www.drupal.org/node/2227381 removes most of the validation, not yet the signature, but I assume that can be removed as well.

jibran’s picture

Can we ping @fago about it?

mikey_p’s picture

Status: Active » Needs review
StatusFileSize
new10.5 KB

Initial patch that adds a validation for signature length and modifies AccountForm::validate() to use the entity validation instead of it's own. I don't think this should be blocked by #2227381: Apply formatters and widgets to User base fields 'name' and 'email' since when that goes through the validation will still be needed and we can just remove the extra lines from AccountForm::validate() that call the entity validations.

Status: Needs review » Needs work

The last submitted patch, 6: 2405943-user-entity-validation-6.patch, failed testing.

berdir’s picture

+++ b/core/modules/user/src/Entity/User.php
@@ -496,7 +512,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['signature'] = BaseFieldDefinition::create('string')
       ->setLabel(t('Signature'))
       ->setDescription(t('The signature of this user.'))
-      ->setTranslatable(TRUE);
+      ->setTranslatable(TRUE)
+      ->setConstraints(array('UserSignature' => array()));

Why add a custom constraint that is doing exactly the same as the one we already have by default? You even updated the test to assert for both?

mikey_p’s picture

That makes a lot of sense, I was just trying to keep the error messages consistent with the existing messages. I suppose that would be easy to do with a constraint that uses validatedBy()?

mikey_p’s picture

Status: Needs work » Needs review
StatusFileSize
new7.99 KB
new13.12 KB

Removed the custom validator for now, I wish it was easier to customize those error messages, but this should work for now.

Not sure what's up with the UserRegistrationTest, I updated the strings, but it's still failing for me.

mikey_p’s picture

StatusFileSize
new5.81 KB

Bah, forgot to rebase the old branch. Here's the correct interdiff.

mikey_p’s picture

Status: Needs review » Needs work

The last submitted patch, 10: 2405943-user-entity-validation-10.patch, failed testing.

mikey_p’s picture

Status: Needs work » Needs review
StatusFileSize
new7.99 KB
new1.54 KB

Not sure why I didn't see that earlier.

berdir’s picture

+++ b/core/modules/user/src/Tests/UserRegistrationTest.php
@@ -138,13 +138,13 @@ function testRegistrationEmailDuplicates() {
     $this->drupalPostForm('user/register', $edit, t('Create new account'));
-    $this->assertText(t('The email address @email is already registered.', array('@email' => $duplicate_user->getEmail())), 'Supplying an exact duplicate email address displays an error message');
+    $this->assertText(t('The email address @email is already taken.', array('@email' => $duplicate_user->getEmail())), 'Supplying an exact duplicate email address displays an error message');
 
     // Attempt to bypass duplicate email registration validation by adding spaces.
     $edit['mail'] = '   ' . $duplicate_user->getEmail() . '   ';
 
     $this->drupalPostForm('user/register', $edit, t('Create new account'));
-    $this->assertText(t('The email address @email is already registered.', array('@email' => $duplicate_user->getEmail())), 'Supplying a duplicate email address with added whitespace displays an error message');
+    $this->assertText(t('The email address @email is already taken.', array('@email' => $duplicate_user->getEmail())), 'Supplying a duplicate email address with added whitespace displays an error message');

Interesting, I looked at my patch in the widget issue and I did the same. The problem is that registration and existing users use a different validation message in HEAD. I'm not sure if taken or registered is better, registered kind of feels better to me?

berdir’s picture

Also, looks like we have no validation on the signature length as no fixes were necessary for that, maybe we should add a simple check for that to one of the tests, just to make sure that everything is wired together correctly?

mikey_p’s picture

re #15: Yeah the only place the old string was used in core was in AccountForm.php.

re #16: We already have the test in UserValidationTest. Did you want to add something to UserEditTest?

berdir’s picture

I think it would be good to have a web test for that as well yes, UserEditTest sounds fine. UserValidationTest does not and can not test that we are calling the validation of the signature in the form and we're calling it on the right values (e.g., making sure that we assign the values first, as we currently have to do that manually).

dawehner’s picture

I agree with berdir that a dedicated UI test is helpful.

  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -368,64 +376,23 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    +    foreach ($violations as $violation) {
    +      $form_state->setErrorByName('name', $violation->getMessage());
         }
    ...
    +    $violations = $account->mail->validate();
    +    foreach ($violations as $violation) {
    +      $form_state->setErrorByName('mail', $violation->getMessage());
         }
    ...
    +    $violations = $account->signature->validate();
    +    foreach ($violations as $violation) {
    +      $form_state->setErrorByName('signature', $violation->getMessage());
         }
    

    I love how nice this looks like!

  2. +++ b/core/modules/user/src/Entity/User.php
    @@ -291,6 +291,14 @@ public function getSignature() {
    +  public function setSignature($signature) {
    +    $this->get('signature')->value = $signature;
    +    return $this;
    
    @@ -298,6 +306,14 @@ public function getSignatureFormat() {
    +  public function setSignatureFormat($signature_format) {
    +    $this->get('signature_format')->value = $signature_format;
    +    return $this;
    

    It would be nice to open up a follow up to convert signature into a single TextItem.

mikey_p’s picture

dawehner’s picture

@mikey_p
Cool, just added that as a related issue

mikey_p’s picture

Test that the form validation calls out to entity validation.

dashaforbes’s picture

StatusFileSize
new75.58 KB

Tested Maximum length of user signature:

  1. Enabled Signature field for user profile.
  2. Created a new user
  3. Edited the user's profile and added signature with the length greater than signature maximum length(255) .
fago’s picture

Issue summary: View changes

Patch looks quite good already. @berdir: Yep, that validation logic is mostly already there, just not added in. Afaics only signature format allowed value validation is missing. Corrected the summary.

Looking closer at allowed values - stuff that I see missing else:
- Timezone allowed values validation
- User status allowed values validation (that's covered, as it's a boolean)
- Language allowed values validation (applies to all content entities :/)
- Preferred language, preferred_admin_langcode allowed values validation

For fixing the allowed values validation #2329937: Allow definition objects to provide options would be good to have, but I think we should be able to go with the AllowedValues constraint and a respective callback also meanwhile. That said, we might have missed allowed values validation for other entities so far probably also - I'll check them.

Also, it would make sense to validate we've got a filter format when a signature is set, but as filter formats appear to be required only when filter module is enabled it's probably ok to not validate that, i.e. we'd generally make the filter format optional. But then, it's only rendered with formats by comment module - so that whole area is a bit strange.

    // The mail field is NOT required if account originally had no mail set
    // and the user performing the edit has 'administer users' permission.
    // This allows users without email address to be edited and deleted.

Interesting special casing here. Looking up the mail base field, it's not required - making that an issue also as it allows removing mail addresses what should not be the case :/ I'm not sure why we allow accounts without mail address anyway? :/

However, there are some parts here that better hidden and are not so easy to do. Specifically, that is the part about having to provide the existing password when you want to change the password, username or e-mail.
That's dynamic, optional validation depending on the fact whether another field changes or not. I don't think we can deal with that at the moment, and we can also not map that concept to REST at all as far as I can see?

hm, that's indeed interesting as it means it has interesting security implications if you enable REST updates for user accounts. It's tricky, but given the security implications I think that needs to be addressed?
I think it could be solved on REST level only as you need to know the user input in order to be able to validate it. So this sounds like some extra user validation that REST could add in for security?

fago’s picture

StatusFileSize
new10.9 KB
new1.93 KB

oh, here a quick re-roll fixing some comments and demonstrating missing allowed values for signature formats -> it should fail on that.

Status: Needs review » Needs work

The last submitted patch, 25: d8_validate_user.patch, failed testing.

fago’s picture

StatusFileSize
new19.72 KB
new11.5 KB

First patch attached. This should cover everything except
- required mail
- tightened validation for name, mail or password changes
validation. It generally adds in validation for language references, what might cause some tests to fail which go with invalid values. Let's see.

fago’s picture

Assigned: fago » Unassigned
fago’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 27: d8_validate_user.patch, failed testing.

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new21.18 KB
new2.59 KB

ok, addressed the patch fails.

I had some fun with the default value of 'preferred_admin_langcode', which was '' - but that's not a valid language nor NULL. As I explained in #2318605-54: uuid, created, changed, language default value implementations need to be updated a default value of NULL does not work either, so I had to add a work-a-round with a @todo to fix it there instead. Finally I noticed that the form ignores the default value anyway, and does something else: Noted that at #2227381: Apply formatters and widgets to User base fields 'name' and 'email' which should fix that I think. At least, the test respects the default value and should be green now.

fago’s picture

StatusFileSize
new6.67 KB
new26.79 KB

Also added a constraint for the e-mail validation. Given that, this should be complete except for #2418119: REST user updates bypass tightened user account change validation.

As it's not clear how #2418119: REST user updates bypass tightened user account change validation should be solved, I filed it as a separate issue so we can move on here. Please provide your comments regarding this issue there.

Updated patch attached.

klausi’s picture

Status: Needs review » Needs work

Patch looks good, test cases seem sufficient. I agree that we should split off the security issue regarding password changes to #2418119: REST user updates bypass tightened user account change validation. Only some minor nitpicks:

  1. +++ b/core/modules/system/src/Tests/Entity/EntityValidationTest.php
    @@ -113,7 +113,8 @@ protected function checkValidation($entity_type) {
    -    $this->assertEqual($violations->count(), 1, 'Validation failed.');
    +    // This should fail on AllowedValues and Length constraints.
    +    $this->assertEqual($violations->count(), 2, 'Validation failed.');
         $this->assertEqual($violations[0]->getMessage(), t('This value is too long. It should have %limit characters or less.', array('%limit' => '12')));
    

    2 violations here, so we should also assert the second message here.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -359,7 +360,21 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    +    /** @var \Drupal\user\UserInterface $account */
    +    $account = parent::buildEntity($form, $form_state);
    +
    +    $signature = $form_state->getValue('signature');
    +    $account->setSignature($signature['value']);
    +    $account->setSignatureFormat($signature['format']);
    

    what's up with the signature here, why do we have to populate it manually? Please add a comment.

  3. +++ b/core/modules/user/src/AccountForm.php
    @@ -368,63 +383,24 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    +    // Customly trigger validation of manually added fields and add in
    +    // violations.
    +    $field_names = array(
    

    why do we have to trigger the validation manually here? Why does parent::validate() fail us here? Please add a comment.

  4. +++ b/core/modules/user/src/Entity/User.php
    @@ -553,4 +594,35 @@ protected function getRoleStorage() {
    +   * @return string[]
    

    Description is missing, like "The machine names of allowed filter formats."

  5. +++ b/core/modules/user/src/Entity/User.php
    @@ -553,4 +594,35 @@ protected function getRoleStorage() {
    +   * @return string[]
    

    Same here.

  6. +++ b/core/modules/user/src/Entity/User.php
    @@ -553,4 +594,35 @@ protected function getRoleStorage() {
    +   * @return string[]
    

    And here.

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new27.26 KB
new3.19 KB

Thanks for the review.

4,5,6: Right, I felt like repeating this method description here for internal helpers isn't worth it, but yeah it's coding standard, thus added it.

Addressed 1,2,3 also.

mikey_p’s picture

+++ b/core/modules/user/src/AccountForm.php
@@ -368,63 +385,25 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
+      'name',
+      'mail',
+      'signature',
+      'signature_format',
+      'timezone',
+      'langcode',
+      'preferred_langcode',
+      'preferred_admin_langcode'
+    );

Should we add a corresponding section in UserEditTest for each of these validations?

fago’s picture

I don't think it's an issue as all of those follow the same code-flow + it's there only temporarily until #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay fixes it in general.

klausi’s picture

Status: Needs review » Needs work

Great, almost ready. I missed a couple of nitpicks last time:

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
    @@ -51,6 +57,15 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
       /**
    +   * Defines allowed language codes for the field's AllowedValues constraint.
    +   *
    +   * @return string[]
    +   */
    +  public static function getAllowedLanguageCodes() {
    

    @return description missing.

  2. +++ b/core/modules/user/src/Entity/User.php
    @@ -553,4 +594,38 @@ protected function getRoleStorage() {
    +   *   The allowed values.
    

    Not really creative, but OK :-P

  3. +++ b/core/modules/user/src/Tests/UserEditTest.php
    @@ -16,12 +16,43 @@
    +  public static $modules = array('filter');
    +
    +  protected function setUp() {
    

    doc blocks are missing.

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new27.38 KB
new1.19 KB

ok, here you go.

klausi’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, assuming the testbot comes back green.

effulgentsia’s picture

Issue tags: +Triaged D8 critical

Yay to see this RTBC! Also, confirmed the criticality of this with the branch maintainers.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 17c2500 and pushed to 8.0.x. Thanks!

+++ b/core/modules/user/src/AccountForm.php
@@ -368,63 +385,25 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
+    // Customly trigger validation of manually added fields and add in
+    // violations. This is necessary as entity form displays only invoke entity
+    // validation for fields contained in the display.
+    $field_names = array(
+      'name',
+      'mail',
+      'signature',
+      'signature_format',
+      'timezone',
+      'langcode',
+      'preferred_langcode',
+      'preferred_admin_langcode'
+    );

There is an issue to issue this right? #2002180: Entity forms skip validation of fields that are edited without widgets?

  • alexpott committed 17c2500 on 8.0.x
    Issue #2405943 by fago, mikey_p, dashaforbes: User entity validation...
fago’s picture

Thanks! Yep, correct - we could have pointed to that issue here. Anyway, we'll have to work over it there.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.