Commit 354a2618 authored by xjm's avatar xjm

Merged 9.0.1.

parents 12fb3d86 32d737c5
This diff is collapsed.
...@@ -382,11 +382,17 @@ function drupal_valid_test_ua($new_prefix = NULL) { ...@@ -382,11 +382,17 @@ function drupal_valid_test_ua($new_prefix = NULL) {
// Ensure that no information leaks on production sites. // Ensure that no information leaks on production sites.
$test_db = new TestDatabase($prefix); $test_db = new TestDatabase($prefix);
$key_file = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/.htkey'; $key_file = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/.htkey';
if (!is_readable($key_file)) { if (!is_readable($key_file) || is_dir($key_file)) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit; exit;
} }
$private_key = file_get_contents($key_file); $private_key = file_get_contents($key_file);
// The string from drupal_generate_test_ua() is 74 bytes long. If we don't
// have it, tests cannot be allowed.
if (empty($private_key) || strlen($private_key) < 74) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
// The file properties add more entropy not easily accessible to others. // The file properties add more entropy not easily accessible to others.
$key = $private_key . filectime(__FILE__) . fileinode(__FILE__); $key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
$time_diff = REQUEST_TIME - $time; $time_diff = REQUEST_TIME - $time;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\FileBag; use Symfony\Component\HttpFoundation\FileBag;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
...@@ -957,8 +958,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state ...@@ -957,8 +958,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
// This value is checked in self::handleInputElement(). // This value is checked in self::handleInputElement().
$form_state->setInvalidToken(TRUE); $form_state->setInvalidToken(TRUE);
// Ignore all submitted values.
$form_state->setUserInput([]);
$request = $this->requestStack->getCurrentRequest();
// Do not trust any POST data.
$request->request = new ParameterBag();
// Make sure file uploads do not get processed. // Make sure file uploads do not get processed.
$this->requestStack->getCurrentRequest()->files = new FileBag(); $request->files = new FileBag();
// Ensure PHP globals reflect these changes.
$request->overrideGlobals();
} }
} }
} }
......
...@@ -124,10 +124,8 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state) ...@@ -124,10 +124,8 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state)
* {@inheritdoc} * {@inheritdoc}
*/ */
public function setInvalidTokenError(FormStateInterface $form_state) { public function setInvalidTokenError(FormStateInterface $form_state) {
$url = $this->requestStack->getCurrentRequest()->getRequestUri();
// Setting this error will cause the form to fail validation. // Setting this error will cause the form to fail validation.
$form_state->setErrorByName('form_token', $this->t('The form has become outdated. Copy any unsaved work in the form below and then <a href=":link">reload this page</a>.', [':link' => $url])); $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.'));
} }
/** /**
......
...@@ -50,7 +50,7 @@ public function testManagedFile() { ...@@ -50,7 +50,7 @@ public function testManagedFile() {
$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()), $file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
]; ];
$this->drupalPostForm(NULL, $edit, t('Save')); $this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('The form has become outdated. Copy any unsaved work in the form below'); $this->assertText('The form has become outdated.');
$last_fid = $this->getLastFileId(); $last_fid = $this->getLastFileId();
$this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.'); $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
......
...@@ -322,7 +322,7 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en ...@@ -322,7 +322,7 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en
)); ));
} }
$data += ['attributes' => [], 'relationships' => []]; $data += ['attributes' => [], 'relationships' => []];
$field_names = array_merge(array_keys($data['attributes']), array_keys($data['relationships'])); $field_names = array_map([$resource_type, 'getInternalName'], array_merge(array_keys($data['attributes']), array_keys($data['relationships'])));
// User resource objects contain a read-only attribute that is not a real // User resource objects contain a read-only attribute that is not a real
// field on the user entity type. // field on the user entity type.
......
...@@ -112,6 +112,7 @@ public function __construct(AccountInterface $current_user, EntityFieldManagerIn ...@@ -112,6 +112,7 @@ public function __construct(AccountInterface $current_user, EntityFieldManagerIn
* created file entity. * created file entity.
*/ */
public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) { public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) {
$file_field_name = $resource_type->getInternalName($file_field_name);
$field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name);
static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity); static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity);
...@@ -138,7 +139,7 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy ...@@ -138,7 +139,7 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy
$entity->save(); $entity->save();
$route_parameters = ['entity' => $entity->uuid()]; $route_parameters = ['entity' => $entity->uuid()];
$route_name = sprintf('jsonapi.%s.%s.related', $resource_type->getTypeName(), $file_field_name); $route_name = sprintf('jsonapi.%s.%s.related', $resource_type->getTypeName(), $resource_type->getPublicName($file_field_name));
$related_url = Url::fromRoute($route_name, $route_parameters)->toString(TRUE); $related_url = Url::fromRoute($route_name, $route_parameters)->toString(TRUE);
$request = Request::create($related_url->getGeneratedUrl(), 'GET', [], $request->cookies->all(), [], $request->server->all()); $request = Request::create($related_url->getGeneratedUrl(), 'GET', [], $request->cookies->all(), [], $request->server->all());
return $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST); return $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST);
...@@ -161,6 +162,7 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy ...@@ -161,6 +162,7 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy
* Thrown when there are validation errors. * Thrown when there are validation errors.
*/ */
public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) { public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) {
$file_field_name = $resource_type->getInternalName($file_field_name);
$field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name);
static::ensureFileUploadAccess($this->currentUser, $field_definition); static::ensureFileUploadAccess($this->currentUser, $field_definition);
...@@ -182,7 +184,7 @@ public function handleFileUploadForNewResource(Request $request, ResourceType $r ...@@ -182,7 +184,7 @@ public function handleFileUploadForNewResource(Request $request, ResourceType $r
/* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */ /* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */
$links = new LinkCollection(['self' => $self_link]); $links = new LinkCollection(['self' => $self_link]);
$relatable_resource_types = $resource_type->getRelatableResourceTypesByField($file_field_name); $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($resource_type->getPublicName($file_field_name));
$file_resource_type = reset($relatable_resource_types); $file_resource_type = reset($relatable_resource_types);
$resource_object = ResourceObject::createFromEntity($file_resource_type, $file); $resource_object = ResourceObject::createFromEntity($file_resource_type, $file);
return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object], 1), new NullIncludedData(), $links), 201, []); return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object], 1), new NullIncludedData(), $links), 201, []);
......
...@@ -251,21 +251,27 @@ public function testInputWithInvalidToken() { ...@@ -251,21 +251,27 @@ public function testInputWithInvalidToken() {
$this->assertSession() $this->assertSession()
->elementExists('css', 'input[name="form_token"]') ->elementExists('css', 'input[name="form_token"]')
->setValue('invalid token'); ->setValue('invalid token');
$random_string = $this->randomString();
$edit = [ $edit = [
'textfield' => $this->randomString(), 'textfield' => $random_string,
'checkboxes[bar]' => TRUE, 'checkboxes[bar]' => TRUE,
'select' => 'bar', 'select' => 'bar',
'radios' => 'foo', 'radios' => 'foo',
]; ];
$this->drupalPostForm(NULL, $edit, 'Submit'); $this->drupalPostForm(NULL, $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.'); $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
// Verify that input elements retained the posted values. $assert = $this->assertSession();
$this->assertFieldByName('textfield', $edit['textfield']); $element = $assert->fieldExists('textfield');
$this->assertEmpty($element->getValue());
$assert->responseNotContains($random_string);
$this->assertText('The form has become outdated.');
// Ensure that we don't use the posted values.
$this->assertFieldByName('textfield', '');
$this->assertNoFieldChecked('edit-checkboxes-foo'); $this->assertNoFieldChecked('edit-checkboxes-foo');
$this->assertFieldChecked('edit-checkboxes-bar'); $this->assertNoFieldChecked('edit-checkboxes-bar');
$this->assertOptionSelected('edit-select', 'bar'); $this->assertOptionSelected('edit-select', '');
$this->assertFieldChecked('edit-radios-foo'); $this->assertNoFieldChecked('edit-radios-foo');
// Check another form that has a textarea input. // Check another form that has a textarea input.
$this->drupalGet(Url::fromRoute('form_test.required')); $this->drupalGet(Url::fromRoute('form_test.required'));
...@@ -278,9 +284,9 @@ public function testInputWithInvalidToken() { ...@@ -278,9 +284,9 @@ public function testInputWithInvalidToken() {
]; ];
$this->drupalPostForm(NULL, $edit, 'Submit'); $this->drupalPostForm(NULL, $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.'); $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below'); $this->assertText('The form has become outdated.');
$this->assertFieldByName('textfield', $edit['textfield']); $this->assertFieldByName('textfield', '');
$this->assertFieldByName('textarea', $edit['textarea']); $this->assertFieldByName('textarea', '');
// Check another form that has a number input. // Check another form that has a number input.
$this->drupalGet(Url::fromRoute('form_test.number')); $this->drupalGet(Url::fromRoute('form_test.number'));
...@@ -288,12 +294,14 @@ public function testInputWithInvalidToken() { ...@@ -288,12 +294,14 @@ public function testInputWithInvalidToken() {
->elementExists('css', 'input[name="form_token"]') ->elementExists('css', 'input[name="form_token"]')
->setValue('invalid token'); ->setValue('invalid token');
$edit = [ $edit = [
'integer_step' => mt_rand(1, 100), // We choose a random value which is higher than the default value,
// so we don't accidentally generate the default value.
'integer_step' => mt_rand(6, 100),
]; ];
$this->drupalPostForm(NULL, $edit, 'Submit'); $this->drupalPostForm(NULL, $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.'); $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below'); $this->assertText('The form has become outdated.');
$this->assertFieldByName('integer_step', $edit['integer_step']); $this->assertFieldByName('integer_step', 5);
// Check a form with a Url field // Check a form with a Url field
$this->drupalGet(Url::fromRoute('form_test.url')); $this->drupalGet(Url::fromRoute('form_test.url'));
...@@ -305,8 +313,8 @@ public function testInputWithInvalidToken() { ...@@ -305,8 +313,8 @@ public function testInputWithInvalidToken() {
]; ];
$this->drupalPostForm(NULL, $edit, 'Submit'); $this->drupalPostForm(NULL, $edit, 'Submit');
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.'); $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below'); $this->assertText('The form has become outdated.');
$this->assertFieldByName('url', $edit['url']); $this->assertFieldByName('url', '');
} }
/** /**
......
...@@ -74,7 +74,7 @@ public function testValidate() { ...@@ -74,7 +74,7 @@ public function testValidate() {
$this->drupalPostForm(NULL, ['name' => 'validate'], 'Save'); $this->drupalPostForm(NULL, ['name' => 'validate'], 'Save');
$this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.'); $this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
$this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.'); $this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.');
$this->assertText('The form has become outdated. Copy any unsaved work in the form below'); $this->assertText('The form has become outdated.');
} }
/** /**
......
...@@ -832,12 +832,30 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated ...@@ -832,12 +832,30 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated
$expected_form = $form_id(); $expected_form = $form_id();
$form_arg = $this->getMockForm($form_id, $expected_form); $form_arg = $this->getMockForm($form_id, $expected_form);
// Set up some request data so we can be sure it is removed when a token is
// invalid.
$this->request->request->set('foo', 'bar');
$_POST['foo'] = 'bar';
$form_state = new FormState(); $form_state = new FormState();
$input['form_id'] = $form_id; $input['form_id'] = $form_id;
$input['form_token'] = $form_token; $input['form_token'] = $form_token;
$input['test'] = 'example-value';
$form_state->setUserInput($input); $form_state->setUserInput($input);
$this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE); $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
$this->assertSame($expected, $form_state->hasInvalidToken()); $this->assertSame($expected, $form_state->hasInvalidToken());
if ($expected) {
$this->assertEmpty($form['test']['#value']);
$this->assertEmpty($form_state->getValue('test'));
$this->assertEmpty($_POST);
$this->assertEmpty(iterator_to_array($this->request->request->getIterator()));
}
else {
$this->assertEquals('example-value', $form['test']['#value']);
$this->assertEquals('example-value', $form_state->getValue('test'));
$this->assertEquals('bar', $_POST['foo']);
$this->assertEquals('bar', $this->request->request->get('foo'));
}
} }
public function providerTestInvalidToken() { public function providerTestInvalidToken() {
......
...@@ -173,7 +173,7 @@ protected function setUp() { ...@@ -173,7 +173,7 @@ protected function setUp() {
->getMock(); ->getMock();
$this->account = $this->createMock('Drupal\Core\Session\AccountInterface'); $this->account = $this->createMock('Drupal\Core\Session\AccountInterface');
$this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface'); $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface');
$this->request = new Request(); $this->request = Request::createFromGlobals();
$this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->requestStack = new RequestStack(); $this->requestStack = new RequestStack();
$this->requestStack->push($this->request); $this->requestStack->push($this->request);
......
...@@ -131,7 +131,7 @@ public function testValidateInvalidFormToken() { ...@@ -131,7 +131,7 @@ public function testValidateInvalidFormToken() {
->getMock(); ->getMock();
$form_state->expects($this->once()) $form_state->expects($this->once())
->method('setErrorByName') ->method('setErrorByName')
->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.'); ->with('form_token', 'The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.');
$form_state->setValue('form_token', 'some_random_token'); $form_state->setValue('form_token', 'some_random_token');
$form_validator->validateForm('test_form_id', $form, $form_state); $form_validator->validateForm('test_form_id', $form, $form_state);
$this->assertTrue($form_state->isValidationComplete()); $this->assertTrue($form_state->isValidationComplete());
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment