Broken in Drupal 10.2.0+

Created on 7 May 2024, 5 months ago
Updated 1 July 2024, 3 months ago

Problem/Motivation

If you have used this module to store passwords prior to 10.2.0 and use the passwords in anyway, e.g. by comparing with a value submitted in a form, or passing the unencrypted value to an API then a change introduced here https://git.drupalcode.org/project/drupal/-/commit/be46dafb302ad16eeab76... will break your code.
This is due to an additional parameter being added to the list being unset that causes the password to now be encrypted three times while all existing passwords saved before 10.2.0 have only been encrypted twice.

This does highlight the fact that I think the code in \Drupal\password_field\Plugin\Field\FieldType\PasswordField::__unset is not the correct way this code should be working. Setting the value using the unset magic method cannot possibly the correct way a module should be integrating with the Drupal code? The password should only be being encrypted once but because the field type is using the __unset method it is accidentally encrypting it twice prior to 10.2 and now three times in 10.2+.

Steps to reproduce

Install this module on any Drupal pre 10.2.0 and create lots of entities with a password. Add a custom module with a password field in a form that validates against the password stored in the entities (or integrate with a third part API to validate the password against). As long as you decrypt the value provided in the entity using password_field_encrypt_decrypt TWICE then the password will match.
Update Drupal to 10.2.0 and update an existing entity with a new password and your existing code will no longer work because the password has now accidentally been encrypted three times due to this core code in \Drupal\Core\Field\WidgetBase::extractFormValues trying to unset the _actions parameter as well:

      // Put delta mapping in $form_state, so that flagErrors() can use it.
      $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
      foreach ($items as $delta => $item) {
        $field_state['original_deltas'][$delta] = $item->_original_delta ?? $delta;
        unset($item->_original_delta, $item->_weight, $item->_actions);
      }

See https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Co...

Proposed resolution

Instead of using the __unset() magic method, use the preSave() method as the core \Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem does.

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    if (!empty($this->value)) {
      $this->value = password_field_encrypt_decrypt('encrypt', $this->value);
    }
  }

However to maintain backwards compatibility the password_field_encrypt_decrypt method must now double encrypt itself. I have checked an old D9.5.x site and that double encrypts but I don't know how far that behaviour goes back in time. I'm assuming anyone who then used this module has already written code to double decrypt.

Remaining tasks

The password field could be rewritten to extend the core Password field - just overriding the preSave method?

๐Ÿ› Bug report
Status

Needs review

Version

2.0

Component

Code

Created by

๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom altcom_neil

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Production build 0.71.5 2024