diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 272b2d65194..75cecbc45e1 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -523,6 +523,16 @@ field.value.string_long: type: text label: 'Value' +# Schema for the configuration of the Password field type. + +field.storage_settings.password: + type: field.storage_settings.string + label: 'Password settings' + +field.field_settings.password: + type: mapping + label: 'Password settings' + # Schema for the configuration of the URI field type. field.storage_settings.uri: diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php index f86eac7e518..e6afc01620a 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php @@ -46,6 +46,11 @@ class PasswordItem extends StringItem { // Reset the pre_hashed value since it has now been used. $this->pre_hashed = FALSE; } + elseif (!$entity->isNew() && empty($this->value)) { + // If the password is empty, that means it was not changed, so use the + // original password. + $this->value = $entity->original->{$this->getFieldDefinition()->getName()}->value; + } elseif ($entity->isNew() || (strlen(trim($this->value)) > 0 && $this->value != $entity->original->{$this->getFieldDefinition()->getName()}->value)) { // Allow alternate password hashing schemes. $this->value = \Drupal::service('password')->hash(trim($this->value)); @@ -55,13 +60,6 @@ class PasswordItem extends StringItem { } } - if (!$entity->isNew()) { - // If the password is empty, that means it was not changed, so use the - // original password. - if (empty($this->value)) { - $this->value = $entity->original->{$this->getFieldDefinition()->getName()}->value; - } - } // Ensure that the existing password is unset to minimise risks of it // getting serialized and stored somewhere. $this->existing = NULL; diff --git a/core/tests/Drupal/KernelTests/Core/Field/FieldType/PasswordItemTest.php b/core/tests/Drupal/KernelTests/Core/Field/FieldType/PasswordItemTest.php new file mode 100644 index 00000000000..9b496beed61 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Field/FieldType/PasswordItemTest.php @@ -0,0 +1,189 @@ +fieldStorage = FieldStorageConfig::create([ + 'field_name' => 'test_field', + 'entity_type' => 'entity_test', + 'type' => 'password', + ]); + $this->fieldStorage->save(); + + $this->field = FieldConfig::create([ + 'field_storage' => $this->fieldStorage, + 'bundle' => 'entity_test', + 'required' => TRUE, + ]); + $this->field->save(); + } + + /** + * @covers ::preSave + */ + public function testPreSavePreHashed() { + $entity = EntityTest::create([ + 'name' => $this->randomString(), + ]); + $entity->test_field = 'this_is_not_a_real_hash'; + $entity->test_field->pre_hashed = TRUE; + + $entity->save(); + $this->assertSame('this_is_not_a_real_hash', $entity->test_field->value); + $this->assertFalse($entity->test_field->pre_hashed); + } + + /** + * @covers ::preSave + */ + public function testPreSaveNewNull() { + $entity = EntityTest::create([ + 'name' => $this->randomString(), + ]); + $entity->test_field = NULL; + + $entity->save(); + $this->assertNull($entity->test_field->value); + } + + /** + * @covers ::preSave + */ + public function testPreSaveNewEmptyString() { + $entity = EntityTest::create([ + 'name' => $this->randomString(), + ]); + $entity->test_field = ''; + + $entity->save(); + + // The string starts with the portable password string and is a hash of an + // empty string. + $this->assertStringStartsWith('$S$', $entity->test_field->value); + $this->assertTrue($this->container->get('password')->check('', $entity->test_field->value)); + } + + /** + * @covers ::preSave + */ + public function testPreSaveNewMultipleSpacesString() { + $entity = EntityTest::create([ + 'name' => $this->randomString(), + ]); + $entity->test_field = ' '; + + $entity->save(); + + // The string starts with the portable password string and is a hash of an + // empty string. + $this->assertStringStartsWith('$S$', $entity->test_field->value); + $this->assertTrue($this->container->get('password')->check('', $entity->test_field->value)); + } + + /** + * @covers ::preSave + */ + public function testPreSaveExistingNull() { + $entity = EntityTest::create(); + $entity->test_field = $this->randomString(); + $entity->save(); + + $this->assertNotNull($entity->test_field->value); + + $entity->test_field = NULL; + $entity->save(); + + $this->assertNull($entity->test_field->value); + } + + /** + * @covers ::preSave + */ + public function testPreSaveExistingEmptyString() { + $entity = EntityTest::create(); + $entity->test_field = $this->randomString(); + $entity->save(); + + $hashed_password = $entity->test_field->value; + + $entity->test_field = ''; + $entity->save(); + + $this->assertSame($hashed_password, $entity->test_field->value); + } + + /** + * @covers ::preSave + */ + public function testPreSaveExistingMultipleSpacesString() { + $entity = EntityTest::create(); + $entity->test_field = $this->randomString(); + $entity->save(); + + $entity->test_field = ' '; + $entity->save(); + + // @todo Fix this bug in https://www.drupal.org/project/i/3238399. + $this->assertSame(' ', $entity->test_field->value); + } + + /** + * @covers ::preSave + */ + public function testPreSaveExceptionNew() { + $entity = EntityTest::create(); + $entity->test_field = str_repeat('a', PasswordInterface::PASSWORD_MAX_LENGTH + 1); + $this->expectException(EntityStorageException::class); + $this->expectExceptionMessage('The entity does not have a password'); + $entity->save(); + } + + /** + * @covers ::preSave + */ + public function testPreSaveExceptionExisting() { + $entity = EntityTest::create(); + $entity->test_field = 'will_be_hashed'; + $entity->save(); + + $this->assertNotEquals('will_be_hashed', $entity->test_field->value); + + $this->expectException(EntityStorageException::class); + $this->expectExceptionMessage('The entity does not have a password'); + $entity->test_field = str_repeat('a', PasswordInterface::PASSWORD_MAX_LENGTH + 1); + $entity->save(); + } + +}