Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -5,5 +5,5 @@ core: 8.x
package: Testing
version: VERSION
dependencies:
- filter
- ckeditor
- drupal:filter
- drupal:ckeditor

View file

@ -0,0 +1,237 @@
<?php
namespace Drupal\Tests\editor\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\filter\Entity\FilterFormat;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
/**
* Tests administration of text editors.
*
* @group editor
*/
class EditorAdminTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['filter', 'editor'];
/**
* A user with the 'administer filters' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Add text format.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => [],
]);
$filtered_html_format->save();
// Create admin user.
$this->adminUser = $this->drupalCreateUser(['administer filters']);
}
/**
* Tests an existing format without any editors available.
*/
public function testNoEditorAvailable() {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
// Ensure the form field order is correct.
$raw_content = $this->getSession()->getPage()->getContent();
$roles_pos = strpos($raw_content, 'Roles');
$editor_pos = strpos($raw_content, 'Text editor');
$filters_pos = strpos($raw_content, 'Enabled filters');
$this->assertTrue($roles_pos < $editor_pos && $editor_pos < $filters_pos, '"Text Editor" select appears in the correct location of the text format configuration UI.');
// Verify the <select>.
$select = $this->xpath('//select[@name="editor[editor]"]');
$select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 1, 'The Text Editor select is disabled.');
$this->assertTrue(count($options) === 1, 'The Text Editor select has only one option.');
$this->assertTrue(($options[0]->getText()) === 'None', 'Option 1 in the Text Editor select is "None".');
$this->assertRaw('This option is disabled because no modules that provide a text editor are currently enabled.', 'Description for select present that tells users to install a text editor module.');
}
/**
* Tests adding a text editor to an existing text format.
*/
public function testAddEditorToExistingFormat() {
$this->enableUnicornEditor();
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
$edit = $this->selectUnicornEditor();
// Configure Unicorn Editor's setting to another value.
$edit['editor[settings][ponies_too]'] = FALSE;
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->verifyUnicornEditorConfiguration('filtered_html', FALSE);
// Switch back to 'None' and check the Unicorn Editor's settings are gone.
$edit = [
'editor[editor]' => '',
];
$this->drupalPostForm(NULL, $edit, 'Configure');
$unicorn_setting = $this->xpath('//input[@name="editor[settings][ponies_too]" and @type="checkbox" and @checked]');
$this->assertTrue(count($unicorn_setting) === 0, "Unicorn Editor's settings form is no longer present.");
}
/**
* Tests adding a text editor to a new text format.
*/
public function testAddEditorToNewFormat() {
$this->addEditorToNewFormat('monocerus', 'Monocerus');
$this->verifyUnicornEditorConfiguration('monocerus');
}
/**
* Tests format disabling.
*/
public function testDisableFormatWithEditor() {
$formats = ['monocerus' => 'Monocerus', 'tattoo' => 'Tattoo'];
// Install the node module.
$this->container->get('module_installer')->install(['node']);
$this->resetAll();
// Create a new node type and attach the 'body' field to it.
$node_type = NodeType::create(['type' => mb_strtolower($this->randomMachineName())]);
$node_type->save();
node_add_body_field($node_type, $this->randomString());
$permissions = ['administer filters', "edit any {$node_type->id()} content"];
foreach ($formats as $format => $name) {
// Create a format and add an editor to this format.
$this->addEditorToNewFormat($format, $name);
// Add permission for this format.
$permissions[] = "use text format $format";
}
// Create a node having the body format value 'moncerus'.
$node = Node::create([
'type' => $node_type->id(),
'title' => $this->randomString(),
]);
$node->body->value = $this->randomString(100);
$node->body->format = 'monocerus';
$node->save();
// Log in as an user able to use both formats and edit nodes of created type.
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// The node edit page header.
$text = (string) new FormattableMarkup('<em>Edit @type</em> @title', ['@type' => $node_type->label(), '@title' => $node->label()]);
// Go to node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertRaw($text);
// Disable the format assigned to the 'body' field of the node.
FilterFormat::load('monocerus')->disable()->save();
// Edit again the node.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertRaw($text);
}
/**
* Adds an editor to a new format using the UI.
*
* @param string $format_id
* The format id.
* @param string $format_name
* The format name.
*/
protected function addEditorToNewFormat($format_id, $format_name) {
$this->enableUnicornEditor();
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/content/formats/add');
// Configure the text format name.
$edit = [
'name' => $format_name,
'format' => $format_id,
];
$edit += $this->selectUnicornEditor();
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
}
/**
* Enables the unicorn editor.
*/
protected function enableUnicornEditor() {
if (!$this->container->get('module_handler')->moduleExists('editor_test')) {
$this->container->get('module_installer')->install(['editor_test']);
}
}
/**
* Tests and selects the unicorn editor.
*
* @return array
* Returns an edit array containing the values to be posted.
*/
protected function selectUnicornEditor() {
// Verify the <select> when a text editor is available.
$select = $this->xpath('//select[@name="editor[editor]"]');
$select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue(($options[0]->getText()) === 'None', 'Option 1 in the Text Editor select is "None".');
$this->assertTrue(($options[1]->getText()) === 'Unicorn Editor', 'Option 2 in the Text Editor select is "Unicorn Editor".');
$this->assertTrue($options[0]->hasAttribute('selected'), 'Option 1 ("None") is selected.');
// Ensure the none option is selected.
$this->assertNoRaw('This option is disabled because no modules that provide a text editor are currently enabled.', 'Description for select absent that tells users to install a text editor module.');
// Select the "Unicorn Editor" editor and click the "Configure" button.
$edit = [
'editor[editor]' => 'unicorn',
];
$this->drupalPostForm(NULL, $edit, 'Configure');
$unicorn_setting = $this->xpath('//input[@name="editor[settings][ponies_too]" and @type="checkbox" and @checked]');
$this->assertTrue(count($unicorn_setting), "Unicorn Editor's settings form is present.");
return $edit;
}
/**
* Verifies unicorn editor configuration.
*
* @param string $format_id
* The format machine name.
* @param bool $ponies_too
* The expected value of the ponies_too setting.
*/
protected function verifyUnicornEditorConfiguration($format_id, $ponies_too = TRUE) {
$editor = editor_load($format_id);
$settings = $editor->getSettings();
$this->assertIdentical($editor->getEditor(), 'unicorn', 'The text editor is configured correctly.');
$this->assertIdentical($settings['ponies_too'], $ponies_too, 'The text editor settings are stored correctly.');
$this->drupalGet('admin/config/content/formats/manage/' . $format_id);
$select = $this->xpath('//select[@name="editor[editor]"]');
$select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue($options[1]->hasAttribute('selected'), 'Option 2 ("Unicorn Editor") is selected.');
}
}

View file

@ -0,0 +1,293 @@
<?php
namespace Drupal\Tests\editor\Functional;
use Drupal\editor\Entity\Editor;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests loading of text editors.
*
* @group editor
*/
class EditorLoadingTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['filter', 'editor', 'editor_test', 'node'];
/**
* An untrusted user, with access to the 'plain_text' format.
*
* @var \Drupal\user\UserInterface
*/
protected $untrustedUser;
/**
* A normal user with additional access to the 'filtered_html' format.
*
* @var \Drupal\user\UserInterface
*/
protected $normalUser;
/**
* A privileged user with additional access to the 'full_html' format.
*
* @var \Drupal\user\UserInterface
*/
protected $privilegedUser;
protected function setUp() {
parent::setUp();
// Let there be T-rex.
\Drupal::state()->set('editor_test_give_me_a_trex_thanks', TRUE);
\Drupal::service('plugin.manager.editor')->clearCachedDefinitions();
// Add text formats.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => [],
]);
$filtered_html_format->save();
$full_html_format = FilterFormat::create([
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => [],
]);
$full_html_format->save();
// Create article node type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
// Create page node type, but remove the body.
$this->drupalCreateContentType([
'type' => 'page',
'name' => 'Page',
]);
$body = FieldConfig::loadByName('node', 'page', 'body');
$body->delete();
// Create a formatted text field, which uses an <input type="text">.
FieldStorageConfig::create([
'field_name' => 'field_text',
'entity_type' => 'node',
'type' => 'text',
])->save();
FieldConfig::create([
'field_name' => 'field_text',
'entity_type' => 'node',
'label' => 'Textfield',
'bundle' => 'page',
])->save();
entity_get_form_display('node', 'page', 'default')
->setComponent('field_text')
->save();
// Create 3 users, each with access to different text formats.
$this->untrustedUser = $this->drupalCreateUser(['create article content', 'edit any article content']);
$this->normalUser = $this->drupalCreateUser(['create article content', 'edit any article content', 'use text format filtered_html']);
$this->privilegedUser = $this->drupalCreateUser(['create article content', 'edit any article content', 'create page content', 'edit any page content', 'use text format filtered_html', 'use text format full_html']);
}
/**
* Tests loading of text editors.
*/
public function testLoading() {
// Only associate a text editor with the "Full HTML" text format.
$editor = Editor::create([
'format' => 'full_html',
'editor' => 'unicorn',
'image_upload' => [
'status' => FALSE,
'scheme' => file_default_scheme(),
'directory' => 'inline-images',
'max_size' => '',
'max_dimensions' => ['width' => '', 'height' => ''],
],
]);
$editor->save();
// The normal user:
// - has access to 2 text formats;
// - doesn't have access to the full_html text format, so: no text editor.
$this->drupalLogin($this->normalUser);
$this->drupalGet('node/add/article');
list(, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck('body');
$this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
$this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page because the user only has access to a single format.');
$this->drupalLogout($this->normalUser);
// The privileged user:
// - has access to 2 text formats (and the fallback format);
// - does have access to the full_html text format, so: Unicorn text editor.
$this->drupalLogin($this->privilegedUser);
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck('body');
$expected = [
'formats' => [
'full_html' => [
'format' => 'full_html',
'editor' => 'unicorn',
'editorSettings' => ['ponyModeEnabled' => TRUE],
'editorSupportsContentFiltering' => TRUE,
'isXssSafe' => FALSE,
],
],
];
$this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
$this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
$this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
$specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and @data-editor-for="edit-body-0-value"]');
$this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has a "data-editor-for" attribute with the correct value.');
// Load the editor image dialog form and make sure it does not fatal.
$this->drupalGet('editor/dialog/image/full_html');
$this->assertResponse(200);
$this->drupalLogout($this->privilegedUser);
// Also associate a text editor with the "Plain Text" text format.
$editor = Editor::create([
'format' => 'plain_text',
'editor' => 'unicorn',
]);
$editor->save();
// The untrusted user:
// - has access to 1 text format (plain_text);
// - has access to the plain_text text format, so: Unicorn text editor.
$this->drupalLogin($this->untrustedUser);
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck('body');
$expected = [
'formats' => [
'plain_text' => [
'format' => 'plain_text',
'editor' => 'unicorn',
'editorSettings' => ['ponyModeEnabled' => TRUE],
'editorSupportsContentFiltering' => TRUE,
'isXssSafe' => FALSE,
],
],
];
$this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
$this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
$this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
$hidden_input = $this->xpath('//input[@type="hidden" and @value="plain_text" and @data-editor-for="edit-body-0-value"]');
$this->assertTrue(count($hidden_input) === 1, 'A single text format hidden input exists on the page and has a "data-editor-for" attribute with the correct value.');
// Create an "article" node that uses the full_html text format, then try
// to let the untrusted user edit it.
$this->drupalCreateNode([
'type' => 'article',
'body' => [
['value' => $this->randomMachineName(32), 'format' => 'full_html'],
],
]);
// The untrusted user tries to edit content that is written in a text format
// that (s)he is not allowed to use. The editor is still loaded. CKEditor,
// for example, supports being loaded in a disabled state.
$this->drupalGet('node/1/edit');
list(, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck('body');
$this->assertTrue($editor_settings_present, 'Text Editor module settings.');
$this->assertTrue($editor_js_present, 'Text Editor JavaScript.');
$this->assertTrue(count($body) === 1, 'A body field exists.');
$this->assertFieldByXPath('//textarea[@id="edit-body-0-value" and @disabled="disabled"]', t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Text format access denied message found.');
$this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
$hidden_input = $this->xpath('//input[@type="hidden" and contains(@class, "editor")]');
$this->assertTrue(count($hidden_input) === 0, 'A single text format hidden input does not exist on the page.');
}
/**
* Test supported element types.
*/
public function testSupportedElementTypes() {
// Associate the unicorn text editor with the "Full HTML" text format.
$editor = Editor::create([
'format' => 'full_html',
'editor' => 'unicorn',
'image_upload' => [
'status' => FALSE,
'scheme' => file_default_scheme(),
'directory' => 'inline-images',
'max_size' => '',
'max_dimensions' => ['width' => '', 'height' => ''],
],
]);
$editor->save();
// Create an "page" node that uses the full_html text format.
$this->drupalCreateNode([
'type' => 'page',
'field_text' => [
['value' => $this->randomMachineName(32), 'format' => 'full_html'],
],
]);
// Assert the unicorn editor works with textfields.
$this->drupalLogin($this->privilegedUser);
$this->drupalGet('node/1/edit');
list(, $editor_settings_present, $editor_js_present, $field, $format_selector) = $this->getThingsToCheck('field-text', 'input');
$this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
$this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
$this->assertTrue(count($field) === 1, 'A text field exists.');
$this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
$specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-field-text-0-value"]');
$this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
// Associate the trex text editor with the "Full HTML" text format.
$editor->delete();
Editor::create([
'format' => 'full_html',
'editor' => 'trex',
])->save();
$this->drupalGet('node/1/edit');
list(, $editor_settings_present, $editor_js_present, $field, $format_selector) = $this->getThingsToCheck('field-text', 'input');
$this->assertFalse($editor_settings_present, "Text Editor module's JavaScript settings are not on the page.");
$this->assertFalse($editor_js_present, 'Text Editor JavaScript is not present.');
$this->assertTrue(count($field) === 1, 'A text field exists.');
$this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
$specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-field-text-0-value"]');
$this->assertFalse(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
}
protected function getThingsToCheck($field_name, $type = 'textarea') {
$settings = $this->getDrupalSettings();
return [
// JavaScript settings.
$settings,
// Editor.module's JS settings present.
isset($settings['editor']),
// Editor.module's JS present.
strpos($this->getSession()->getPage()->getContent(), drupal_get_path('module', 'editor') . '/js/editor.js') !== FALSE,
// Body field.
$this->xpath('//' . $type . '[@id="edit-' . $field_name . '-0-value"]'),
// Format selector.
$this->xpath('//select[contains(@class, "filter-list")]'),
];
}
}

View file

@ -112,7 +112,7 @@ class EditorPrivateFileReferenceFilterTest extends BrowserTestBase {
// When the published node is also unpublished, the image should also
// become inaccessible to anonymous users.
$published_node->setPublished(FALSE)->save();
$published_node->setUnpublished()->save();
$this->drupalGet($published_node->toUrl());
$this->assertSession()->statusCodeEquals(403);
@ -121,7 +121,7 @@ class EditorPrivateFileReferenceFilterTest extends BrowserTestBase {
// Disallow anonymous users to view the entity, which then should also
// disallow them to view the image.
$published_node->setPublished(TRUE)->save();
$published_node->setPublished()->save();
Role::load(RoleInterface::ANONYMOUS_ID)
->revokePermission('access content')
->save();

View file

@ -0,0 +1,453 @@
<?php
namespace Drupal\Tests\editor\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests XSS protection for content creators when using text editors.
*
* @group editor
*/
class EditorSecurityTest extends BrowserTestBase {
/**
* The sample content to use in all tests.
*
* @var string
*/
protected static $sampleContent = '<p style="color: red">Hello, Dumbo Octopus!</p><script>alert(0)</script><embed type="image/svg+xml" src="image.svg" />';
/**
* The secured sample content to use in most tests.
*
* @var string
*/
protected static $sampleContentSecured = '<p>Hello, Dumbo Octopus!</p>alert(0)';
/**
* The secured sample content to use in tests when the <embed> tag is allowed.
*
* @var string
*/
protected static $sampleContentSecuredEmbedAllowed = '<p>Hello, Dumbo Octopus!</p>alert(0)<embed type="image/svg+xml" src="image.svg" />';
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['filter', 'editor', 'editor_test', 'node'];
/**
* User with access to Restricted HTML text format without text editor.
*
* @var \Drupal\user\UserInterface
*/
protected $untrustedUser;
/**
* User with access to Restricted HTML text format with text editor.
*
* @var \Drupal\user\UserInterface
*/
protected $normalUser;
/**
* User with access to Restricted HTML text format, dangerous tags allowed
* with text editor.
*
* @var \Drupal\user\UserInterface
*/
protected $trustedUser;
/**
* User with access to all text formats and text editors.
*
* @var \Drupal\user\UserInterface
*/
protected $privilegedUser;
protected function setUp() {
parent::setUp();
// Create 5 text formats, to cover all potential use cases:
// 1. restricted_without_editor (untrusted: anonymous)
// 2. restricted_with_editor (normal: authenticated)
// 3. restricted_plus_dangerous_tag_with_editor (privileged: trusted)
// 4. unrestricted_without_editor (privileged: admin)
// 5. unrestricted_with_editor (privileged: admin)
// With text formats 2, 3 and 5, we also associate a text editor that does
// not guarantee XSS safety. "restricted" means the text format has XSS
// filters on output, "unrestricted" means the opposite.
$format = FilterFormat::create([
'format' => 'restricted_without_editor',
'name' => 'Restricted HTML, without text editor',
'weight' => 0,
'filters' => [
// A filter of the FilterInterface::TYPE_HTML_RESTRICTOR type.
'filter_html' => [
'status' => 1,
'settings' => [
'allowed_html' => '<h2> <h3> <h4> <h5> <h6> <p> <br> <strong> <a>',
],
],
],
]);
$format->save();
$format = FilterFormat::create([
'format' => 'restricted_with_editor',
'name' => 'Restricted HTML, with text editor',
'weight' => 1,
'filters' => [
// A filter of the FilterInterface::TYPE_HTML_RESTRICTOR type.
'filter_html' => [
'status' => 1,
'settings' => [
'allowed_html' => '<h2> <h3> <h4> <h5> <h6> <p> <br> <strong> <a>',
],
],
],
]);
$format->save();
$editor = Editor::create([
'format' => 'restricted_with_editor',
'editor' => 'unicorn',
]);
$editor->save();
$format = FilterFormat::create([
'format' => 'restricted_plus_dangerous_tag_with_editor',
'name' => 'Restricted HTML, dangerous tag allowed, with text editor',
'weight' => 1,
'filters' => [
// A filter of the FilterInterface::TYPE_HTML_RESTRICTOR type.
'filter_html' => [
'status' => 1,
'settings' => [
'allowed_html' => '<h2> <h3> <h4> <h5> <h6> <p> <br> <strong> <a> <embed>',
],
],
],
]);
$format->save();
$editor = Editor::create([
'format' => 'restricted_plus_dangerous_tag_with_editor',
'editor' => 'unicorn',
]);
$editor->save();
$format = FilterFormat::create([
'format' => 'unrestricted_without_editor',
'name' => 'Unrestricted HTML, without text editor',
'weight' => 0,
'filters' => [],
]);
$format->save();
$format = FilterFormat::create([
'format' => 'unrestricted_with_editor',
'name' => 'Unrestricted HTML, with text editor',
'weight' => 1,
'filters' => [],
]);
$format->save();
$editor = Editor::create([
'format' => 'unrestricted_with_editor',
'editor' => 'unicorn',
]);
$editor->save();
// Create node type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
// Create 4 users, each with access to different text formats/editors:
// - "untrusted": restricted_without_editor
// - "normal": restricted_with_editor,
// - "trusted": restricted_plus_dangerous_tag_with_editor
// - "privileged": restricted_without_editor, restricted_with_editor,
// restricted_plus_dangerous_tag_with_editor,
// unrestricted_without_editor and unrestricted_with_editor
$this->untrustedUser = $this->drupalCreateUser([
'create article content',
'edit any article content',
'use text format restricted_without_editor',
]);
$this->normalUser = $this->drupalCreateUser([
'create article content',
'edit any article content',
'use text format restricted_with_editor',
]);
$this->trustedUser = $this->drupalCreateUser([
'create article content',
'edit any article content',
'use text format restricted_plus_dangerous_tag_with_editor',
]);
$this->privilegedUser = $this->drupalCreateUser([
'create article content',
'edit any article content',
'use text format restricted_without_editor',
'use text format restricted_with_editor',
'use text format restricted_plus_dangerous_tag_with_editor',
'use text format unrestricted_without_editor',
'use text format unrestricted_with_editor',
]);
// Create an "article" node for each possible text format, with the same
// sample content, to do our tests on.
$samples = [
['author' => $this->untrustedUser->id(), 'format' => 'restricted_without_editor'],
['author' => $this->normalUser->id(), 'format' => 'restricted_with_editor'],
['author' => $this->trustedUser->id(), 'format' => 'restricted_plus_dangerous_tag_with_editor'],
['author' => $this->privilegedUser->id(), 'format' => 'unrestricted_without_editor'],
['author' => $this->privilegedUser->id(), 'format' => 'unrestricted_with_editor'],
];
foreach ($samples as $sample) {
$this->drupalCreateNode([
'type' => 'article',
'body' => [
['value' => self::$sampleContent, 'format' => $sample['format']],
],
'uid' => $sample['author'],
]);
}
}
/**
* Tests initial security: is the user safe without switching text formats?
*
* Tests 8 scenarios. Tests only with a text editor that is not XSS-safe.
*/
public function testInitialSecurity() {
$expected = [
[
'node_id' => 1,
'format' => 'restricted_without_editor',
// No text editor => no XSS filtering.
'value' => self::$sampleContent,
'users' => [
$this->untrustedUser,
$this->privilegedUser,
],
],
[
'node_id' => 2,
'format' => 'restricted_with_editor',
// Text editor => XSS filtering.
'value' => self::$sampleContentSecured,
'users' => [
$this->normalUser,
$this->privilegedUser,
],
],
[
'node_id' => 3,
'format' => 'restricted_plus_dangerous_tag_with_editor',
// Text editor => XSS filtering.
'value' => self::$sampleContentSecuredEmbedAllowed,
'users' => [
$this->trustedUser,
$this->privilegedUser,
],
],
[
'node_id' => 4,
'format' => 'unrestricted_without_editor',
// No text editor => no XSS filtering.
'value' => self::$sampleContent,
'users' => [
$this->privilegedUser,
],
],
[
'node_id' => 5,
'format' => 'unrestricted_with_editor',
// Text editor, no security filter => no XSS filtering.
'value' => self::$sampleContent,
'users' => [
$this->privilegedUser,
],
],
];
// Log in as each user that may edit the content, and assert the value.
foreach ($expected as $case) {
foreach ($case['users'] as $account) {
$this->pass(format_string('Scenario: sample %sample_id, %format.', [
'%sample_id' => $case['node_id'],
'%format' => $case['format'],
]));
$this->drupalLogin($account);
$this->drupalGet('node/' . $case['node_id'] . '/edit');
$dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
$this->assertIdentical($case['value'], $dom_node[0]->getText(), 'The value was correctly filtered for XSS attack vectors.');
}
}
}
/**
* Tests administrator security: is the user safe when switching text formats?
*
* Tests 24 scenarios. Tests only with a text editor that is not XSS-safe.
*
* When changing from a more restrictive text format with a text editor (or a
* text format without a text editor) to a less restrictive text format, it is
* possible that a malicious user could trigger an XSS.
*
* E.g. when switching a piece of text that uses the Restricted HTML text
* format and contains a <script> tag to the Full HTML text format, the
* <script> tag would be executed. Unless we apply appropriate filtering.
*/
public function testSwitchingSecurity() {
$expected = [
[
'node_id' => 1,
// No text editor => no XSS filtering.
'value' => self::$sampleContent,
'format' => 'restricted_without_editor',
'switch_to' => [
'restricted_with_editor' => self::$sampleContentSecured,
// Intersection of restrictions => most strict XSS filtering.
'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
// No text editor => no XSS filtering.
'unrestricted_without_editor' => FALSE,
'unrestricted_with_editor' => self::$sampleContentSecured,
],
],
[
'node_id' => 2,
// Text editor => XSS filtering.
'value' => self::$sampleContentSecured,
'format' => 'restricted_with_editor',
'switch_to' => [
// No text editor => no XSS filtering.
'restricted_without_editor' => FALSE,
// Intersection of restrictions => most strict XSS filtering.
'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
// No text editor => no XSS filtering.
'unrestricted_without_editor' => FALSE,
'unrestricted_with_editor' => self::$sampleContentSecured,
],
],
[
'node_id' => 3,
// Text editor => XSS filtering.
'value' => self::$sampleContentSecuredEmbedAllowed,
'format' => 'restricted_plus_dangerous_tag_with_editor',
'switch_to' => [
// No text editor => no XSS filtering.
'restricted_without_editor' => FALSE,
// Intersection of restrictions => most strict XSS filtering.
'restricted_with_editor' => self::$sampleContentSecured,
// No text editor => no XSS filtering.
'unrestricted_without_editor' => FALSE,
// Intersection of restrictions => most strict XSS filtering.
'unrestricted_with_editor' => self::$sampleContentSecured,
],
],
[
'node_id' => 4,
// No text editor => no XSS filtering.
'value' => self::$sampleContent,
'format' => 'unrestricted_without_editor',
'switch_to' => [
// No text editor => no XSS filtering.
'restricted_without_editor' => FALSE,
'restricted_with_editor' => self::$sampleContentSecured,
// Intersection of restrictions => most strict XSS filtering.
'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
// From no editor, no security filters, to editor, still no security
// filters: resulting content when viewed was already vulnerable, so
// it must be intentional.
'unrestricted_with_editor' => FALSE,
],
],
[
'node_id' => 5,
// Text editor => XSS filtering.
'value' => self::$sampleContentSecured,
'format' => 'unrestricted_with_editor',
'switch_to' => [
// From editor, no security filters to security filters, no editor: no
// risk.
'restricted_without_editor' => FALSE,
'restricted_with_editor' => self::$sampleContentSecured,
// Intersection of restrictions => most strict XSS filtering.
'restricted_plus_dangerous_tag_with_editor' => self::$sampleContentSecured,
// From no editor, no security filters, to editor, still no security
// filters: resulting content when viewed was already vulnerable, so
// it must be intentional.
'unrestricted_without_editor' => FALSE,
],
],
];
// Log in as the privileged user, and for every sample, do the following:
// - switch to every other text format/editor
// - assert the XSS-filtered values that we get from the server
$this->drupalLogin($this->privilegedUser);
$cookies = $this->getSessionCookies();
foreach ($expected as $case) {
$this->drupalGet('node/' . $case['node_id'] . '/edit');
// Verify data- attributes.
$dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
$this->assertIdentical(self::$sampleContent, $dom_node[0]->getAttribute('data-editor-value-original'), 'The data-editor-value-original attribute is correctly set.');
$this->assertIdentical('false', (string) $dom_node[0]->getAttribute('data-editor-value-is-changed'), 'The data-editor-value-is-changed attribute is correctly set.');
// Switch to every other text format/editor and verify the results.
foreach ($case['switch_to'] as $format => $expected_filtered_value) {
$this->pass(format_string('Scenario: sample %sample_id, switch from %original_format to %format.', [
'%sample_id' => $case['node_id'],
'%original_format' => $case['format'],
'%format' => $format,
]));
$post = [
'value' => self::$sampleContent,
'original_format_id' => $case['format'],
];
$client = $this->getHttpClient();
$response = $client->post($this->buildUrl('/editor/filter_xss/' . $format), [
'body' => http_build_query($post),
'cookies' => $cookies,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'http_errors' => FALSE,
]);
$this->assertEquals(200, $response->getStatusCode());
$json = Json::decode($response->getBody());
$this->assertIdentical($json, $expected_filtered_value, 'The value was correctly filtered for XSS attack vectors.');
}
}
}
/**
* Tests the standard text editor XSS filter being overridden.
*/
public function testEditorXssFilterOverride() {
// First: the Standard text editor XSS filter.
$this->drupalLogin($this->normalUser);
$this->drupalGet('node/2/edit');
$dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
$this->assertIdentical(self::$sampleContentSecured, $dom_node[0]->getText(), 'The value was filtered by the Standard text editor XSS filter.');
// Enable editor_test.module's hook_editor_xss_filter_alter() implementation
// to alter the text editor XSS filter class being used.
\Drupal::state()->set('editor_test_editor_xss_filter_alter_enabled', TRUE);
// First: the Insecure text editor XSS filter.
$this->drupalGet('node/2/edit');
$dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
$this->assertIdentical(self::$sampleContent, $dom_node[0]->getText(), 'The value was filtered by the Insecure text editor XSS filter.');
}
}

View file

@ -0,0 +1,227 @@
<?php
namespace Drupal\Tests\editor\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests scaling of inline images.
*
* @group editor
*/
class EditorUploadImageScaleTest extends BrowserTestBase {
use TestFileCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['editor', 'editor_test'];
/**
* A user with permission as administer for testing.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add text format.
FilterFormat::create([
'format' => 'basic_html',
'name' => 'Basic HTML',
'weight' => 0,
])->save();
// Set up text editor.
Editor::create([
'format' => 'basic_html',
'editor' => 'unicorn',
'image_upload' => [
'status' => TRUE,
'scheme' => 'public',
'directory' => 'inline-images',
'max_size' => '',
'max_dimensions' => [
'width' => NULL,
'height' => NULL,
],
],
])->save();
// Create admin user.
$this->adminUser = $this->drupalCreateUser(['administer filters', 'use text format basic_html']);
$this->drupalLogin($this->adminUser);
}
/**
* Tests scaling of inline images.
*/
public function testEditorUploadImageScale() {
// Generate testing images.
$testing_image_list = $this->getTestFiles('image');
// Case 1: no max dimensions set: uploaded image not scaled.
$test_image = $testing_image_list[0];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = NULL;
$max_height = NULL;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $image_file_width);
$this->assertEqual($uploaded_image_file_height, $image_file_height);
$this->assertNoRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $max_width . 'x' . $max_height]));
// Case 2: max width smaller than uploaded image: image scaled down.
$test_image = $testing_image_list[1];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = $image_file_width - 5;
$max_height = $image_file_height;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $max_width);
$this->assertEqual($uploaded_image_file_height, $uploaded_image_file_height * ($uploaded_image_file_width / $max_width));
$this->assertRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $max_width . 'x' . $max_height]));
// Case 3: max height smaller than uploaded image: image scaled down.
$test_image = $testing_image_list[2];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = $image_file_width;
$max_height = $image_file_height - 5;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $uploaded_image_file_width * ($uploaded_image_file_height / $max_height));
$this->assertEqual($uploaded_image_file_height, $max_height);
$this->assertRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $max_width . 'x' . $max_height]));
// Case 4: max dimensions greater than uploaded image: image not scaled.
$test_image = $testing_image_list[3];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = $image_file_width + 5;
$max_height = $image_file_height + 5;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $image_file_width);
$this->assertEqual($uploaded_image_file_height, $image_file_height);
$this->assertNoRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $max_width . 'x' . $max_height]));
// Case 5: only max width dimension was provided and it was smaller than
// uploaded image: image scaled down.
$test_image = $testing_image_list[4];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = $image_file_width - 5;
$max_height = NULL;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $max_width);
$this->assertEqual($uploaded_image_file_height, $uploaded_image_file_height * ($uploaded_image_file_width / $max_width));
$this->assertRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed width of %width pixels.', ['%width' => $max_width]));
// Case 6: only max height dimension was provided and it was smaller than
// uploaded image: image scaled down.
$test_image = $testing_image_list[5];
list($image_file_width, $image_file_height) = $this->getTestImageInfo($test_image->uri);
$max_width = NULL;
$max_height = $image_file_height - 5;
$this->setMaxDimensions($max_width, $max_height);
$this->assertSavedMaxDimensions($max_width, $max_height);
list($uploaded_image_file_width, $uploaded_image_file_height) = $this->uploadImage($test_image->uri);
$this->assertEqual($uploaded_image_file_width, $uploaded_image_file_width * ($uploaded_image_file_height / $max_height));
$this->assertEqual($uploaded_image_file_height, $max_height);
$this->assertRaw((string) new FormattableMarkup('The image was resized to fit within the maximum allowed height of %height pixels.', ['%height' => $max_height]));
}
/**
* Gets the dimensions of an uploaded image.
*
* @param string $uri
* The URI of the image.
*
* @return array
* An array containing the uploaded image's width and height.
*/
protected function getTestImageInfo($uri) {
$image_file = $this->container->get('image.factory')->get($uri);
return [
(int) $image_file->getWidth(),
(int) $image_file->getHeight(),
];
}
/**
* Sets the maximum dimensions and saves the configuration.
*
* @param string|int $width
* The width of the image.
* @param string|int $height
* The height of the image.
*/
protected function setMaxDimensions($width, $height) {
$editor = Editor::load('basic_html');
$image_upload_settings = $editor->getImageUploadSettings();
$image_upload_settings['max_dimensions']['width'] = $width;
$image_upload_settings['max_dimensions']['height'] = $height;
$editor->setImageUploadSettings($image_upload_settings);
$editor->save();
}
/**
* Uploads an image via the editor dialog.
*
* @param string $uri
* The URI of the image.
*
* @return array
* An array containing the uploaded image's width and height.
*/
protected function uploadImage($uri) {
$edit = [
'files[fid]' => \Drupal::service('file_system')->realpath($uri),
];
$this->drupalGet('editor/dialog/image/basic_html');
$this->drupalPostForm('editor/dialog/image/basic_html', $edit, t('Upload'));
$uploaded_image_file = $this->container->get('image.factory')->get('public://inline-images/' . basename($uri));
return [
(int) $uploaded_image_file->getWidth(),
(int) $uploaded_image_file->getHeight(),
];
}
/**
* Asserts whether the saved maximum dimensions equal the ones provided.
*
* @param string $width
* The expected width of the uploaded image.
* @param string $height
* The expected height of the uploaded image.
*
* @return bool
*/
protected function assertSavedMaxDimensions($width, $height) {
$image_upload_settings = Editor::load('basic_html')->getImageUploadSettings();
$expected = [
'width' => $image_upload_settings['max_dimensions']['width'],
'height' => $image_upload_settings['max_dimensions']['height'],
];
$same_width = $this->assertEqual($width, $expected['width'], 'Actual width of "' . $width . '" equals the expected width of "' . $expected['width'] . '"');
$same_height = $this->assertEqual($height, $expected['height'], 'Actual height of "' . $height . '" equals the expected width of "' . $expected['height'] . '"');
return $same_width && $same_height;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\Tests\editor\Functional\Hal;
use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group hal
*/
class EditorHalJsonAnonTest extends EditorResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\editor\Functional\Hal;
use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class EditorHalJsonBasicAuthTest extends EditorResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\editor\Functional\Hal;
use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class EditorHalJsonCookieTest extends EditorResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\Tests\editor\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests Quick Edit module integration endpoints.
*
* @group editor
*/
class QuickEditIntegrationLoadingTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['quickedit', 'filter', 'node', 'editor'];
/**
* The basic permissions necessary to view content and use in-place editing.
*
* @var array
*/
protected static $basicPermissions = ['access content', 'create article content', 'use text format filtered_html', 'access contextual links'];
protected function setUp() {
parent::setUp();
// Create a text format.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => [
'filter_caption' => [
'status' => 1,
],
],
]);
$filtered_html_format->save();
// Create a node type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
// Create one node of the above node type using the above text format.
$this->drupalCreateNode([
'type' => 'article',
'body' => [
0 => [
'value' => '<p>Do you also love Drupal?</p><img src="druplicon.png" data-caption="Druplicon" />',
'format' => 'filtered_html',
],
],
]);
}
/**
* Test loading of untransformed text when a user doesn't have access to it.
*/
public function testUsersWithoutPermission() {
// Create 3 users, each with insufficient permissions, i.e. without either
// or both of the following permissions:
// - the 'access in-place editing' permission
// - the 'edit any article content' permission (necessary to edit node 1)
$users = [
$this->drupalCreateUser(static::$basicPermissions),
$this->drupalCreateUser(array_merge(static::$basicPermissions, ['edit any article content'])),
$this->drupalCreateUser(array_merge(static::$basicPermissions, ['access in-place editing'])),
];
// Now test with each of the 3 users with insufficient permissions.
foreach ($users as $user) {
$this->drupalLogin($user);
$this->drupalGet('node/1');
// Ensure the text is transformed.
$this->assertRaw('<p>Do you also love Drupal?</p><figure role="group" class="caption caption-img"><img src="druplicon.png" /><figcaption>Druplicon</figcaption></figure>');
$client = $this->getHttpClient();
// Retrieving the untransformed text should result in an 403 response and
// return a different error message depending of the missing permission.
$response = $client->post($this->buildUrl('editor/node/1/body/en/full'), [
'query' => http_build_query([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']),
'cookies' => $this->getSessionCookies(),
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'http_errors' => FALSE,
]);
$this->assertEquals(403, $response->getStatusCode());
if (!$user->hasPermission('access in-place editing')) {
$message = "The 'access in-place editing' permission is required.";
}
else {
$message = '';
}
$body = Json::decode($response->getBody());
$this->assertIdentical($message, $body['message']);
}
}
/**
* Test loading of untransformed text when a user does have access to it.
*/
public function testUserWithPermission() {
$user = $this->drupalCreateUser(array_merge(static::$basicPermissions, ['edit any article content', 'access in-place editing']));
$this->drupalLogin($user);
$this->drupalGet('node/1');
// Ensure the text is transformed.
$this->assertRaw('<p>Do you also love Drupal?</p><figure role="group" class="caption caption-img"><img src="druplicon.png" /><figcaption>Druplicon</figcaption></figure>');
$client = $this->getHttpClient();
$response = $client->post($this->buildUrl('editor/node/1/body/en/full'), [
'query' => http_build_query([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']),
'cookies' => $this->getSessionCookies(),
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'http_errors' => FALSE,
]);
$this->assertEquals(200, $response->getStatusCode());
$ajax_commands = Json::decode($response->getBody());
$this->assertIdentical(1, count($ajax_commands), 'The untransformed text POST request results in one AJAX command.');
$this->assertIdentical('editorGetUntransformedText', $ajax_commands[0]['command'], 'The first AJAX command is an editorGetUntransformedText command.');
$this->assertIdentical('<p>Do you also love Drupal?</p><img src="druplicon.png" data-caption="Druplicon" />', $ajax_commands[0]['data'], 'The editorGetUntransformedText command contains the expected data.');
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class EditorJsonAnonTest extends EditorResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class EditorJsonBasicAuthTest extends EditorResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class EditorJsonCookieTest extends EditorResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,184 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for Editor entity.
*/
abstract class EditorResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ckeditor', 'editor'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'editor';
/**
* The Editor entity.
*
* @var \Drupal\editor\EditorInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer filters']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" filter format.
$llama_format = FilterFormat::create([
'name' => 'Llama',
'format' => 'llama',
'langcode' => 'es',
'filters' => [
'filter_html' => [
'status' => TRUE,
'settings' => [
'allowed_html' => '<p> <a> <b> <lo>',
],
],
],
]);
$llama_format->save();
// Create a "Camelids" editor.
$camelids = Editor::create([
'format' => 'llama',
'editor' => 'ckeditor',
]);
$camelids
->setImageUploadSettings([
'status' => FALSE,
'scheme' => file_default_scheme(),
'directory' => 'inline-images',
'max_size' => '',
'max_dimensions' => [
'width' => '',
'height' => '',
],
])
->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [
'config' => [
'filter.format.llama',
],
'module' => [
'ckeditor',
],
],
'editor' => 'ckeditor',
'format' => 'llama',
'image_upload' => [
'status' => FALSE,
'scheme' => 'public',
'directory' => 'inline-images',
'max_size' => '',
'max_dimensions' => [
'width' => NULL,
'height' => NULL,
],
],
'langcode' => 'en',
'settings' => [
'toolbar' => [
'rows' => [
[
[
'name' => 'Formatting',
'items' => [
'Bold',
'Italic',
],
],
[
'name' => 'Links',
'items' => [
'DrupalLink',
'DrupalUnlink',
],
],
[
'name' => 'Lists',
'items' => [
'BulletedList',
'NumberedList',
],
],
[
'name' => 'Media',
'items' => [
'Blockquote',
'DrupalImage',
],
],
[
'name' => 'Tools',
'items' => [
'Source',
],
],
],
],
],
'plugins' => [
'language' => [
'language_list' => 'un',
],
],
],
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
// @see ::createEntity()
return ['user.permissions'];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
return "The 'administer filters' permission is required.";
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class EditorXmlAnonTest extends EditorResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class EditorXmlBasicAuthTest extends EditorResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\Tests\editor\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class EditorXmlCookieTest extends EditorResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\Tests\editor\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests Editor module database updates.
*
* @group editor
* @group legacy
*/
class EditorUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
// Simulate an un-synchronized environment.
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.editor-editor_update_8001.php',
];
}
/**
* Tests editor_update_8001().
*
* @see editor_update_8001()
*/
public function testEditorUpdate8001() {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = $this->container->get('config.factory');
$format_basic_html = $config_factory->get('filter.format.basic_html');
$editor_basic_html = $config_factory->get('editor.editor.basic_html');
$format_full_html = $config_factory->get('filter.format.full_html');
$editor_full_html = $config_factory->get('editor.editor.full_html');
// Checks if the 'basic_html' format and editor statuses differ.
$this->assertTrue($format_basic_html->get('status'));
$this->assertFalse($editor_basic_html->get('status'));
$this->assertNotIdentical($format_basic_html->get('status'), $editor_basic_html->get('status'));
// Checks if the 'full_html' format and editor statuses differ.
$this->assertFalse($format_full_html->get('status'));
$this->assertTrue($editor_full_html->get('status'));
$this->assertNotIdentical($format_full_html->get('status'), $editor_full_html->get('status'));
// Run updates.
$this->runUpdates();
// Reload text formats and editors.
$format_basic_html = $config_factory->get('filter.format.basic_html');
$editor_basic_html = $config_factory->get('editor.editor.basic_html');
$format_full_html = $config_factory->get('filter.format.full_html');
$editor_full_html = $config_factory->get('editor.editor.full_html');
// Checks if the 'basic_html' format and editor statuses are in sync.
$this->assertTrue($format_basic_html->get('status'));
$this->assertTrue($editor_basic_html->get('status'));
$this->assertIdentical($format_basic_html->get('status'), $editor_basic_html->get('status'));
// Checks if the 'full_html' format and editor statuses are in sync.
$this->assertFalse($format_full_html->get('status'));
$this->assertFalse($editor_full_html->get('status'));
$this->assertIdentical($format_full_html->get('status'), $editor_full_html->get('status'));
}
}

View file

@ -46,7 +46,7 @@ class EditorFileReferenceFilterTest extends KernelTestBase {
public function testEditorFileReferenceFilter() {
$filter = $this->filters['editor_file_reference'];
$test = function($input) use ($filter) {
$test = function ($input) use ($filter) {
return $filter->process($input, 'und');
};

View file

@ -2,7 +2,6 @@
namespace Drupal\Tests\editor\Kernel;
use Drupal\Component\Utility\Unicode;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\KernelTests\KernelTestBase;
@ -25,7 +24,7 @@ class EditorFilterIntegrationTest extends KernelTestBase {
public function testTextFormatIntegration() {
// Create an arbitrary text format.
$format = FilterFormat::create([
'format' => Unicode::strtolower($this->randomMachineName()),
'format' => mb_strtolower($this->randomMachineName()),
'name' => $this->randomString(),
]);
$format->save();

View file

@ -9,7 +9,7 @@ use Drupal\editor\Entity\Editor;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\quickedit\MetadataGenerator;
use Drupal\Tests\quickedit\Kernel\QuickEditTestBase;
use Drupal\quickedit_test\MockEditEntityFieldAccessCheck;
use Drupal\quickedit_test\MockQuickEditEntityFieldAccessCheck;
use Drupal\editor\EditorController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
@ -38,7 +38,7 @@ class QuickEditIntegrationTest extends QuickEditTestBase {
/**
* The metadata generator object to be tested.
*
* @var \Drupal\quickedit\MetadataGeneratorInterface.php
* @var \Drupal\quickedit\MetadataGeneratorInterface
*/
protected $metadataGenerator;
@ -52,7 +52,7 @@ class QuickEditIntegrationTest extends QuickEditTestBase {
/**
* The access checker object to be used by the metadata generator object.
*
* @var \Drupal\quickedit\Access\EditEntityFieldAccessCheckInterface
* @var \Drupal\quickedit\Access\QuickEditEntityFieldAccessCheckInterface
*/
protected $accessChecker;
@ -165,7 +165,7 @@ class QuickEditIntegrationTest extends QuickEditTestBase {
*/
public function testMetadata() {
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
$this->accessChecker = new MockEditEntityFieldAccessCheck();
$this->accessChecker = new MockQuickEditEntityFieldAccessCheck();
$this->editorSelector = $this->container->get('quickedit.editor.selector');
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
@ -221,7 +221,7 @@ class QuickEditIntegrationTest extends QuickEditTestBase {
[
'command' => 'editorGetUntransformedText',
'data' => 'Test',
]
],
];
$ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor');

View file

@ -3,6 +3,8 @@
namespace Drupal\Tests\editor\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\editor\Entity\Editor;
use Drupal\Tests\UnitTestCase;
@ -22,9 +24,9 @@ class EditorConfigEntityUnitTest extends UnitTestCase {
/**
* The entity manager used for testing.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
protected $entityTypeManager;
/**
* The ID of the type of the entity under test.
@ -66,8 +68,8 @@ class EditorConfigEntityUnitTest extends UnitTestCase {
->method('getProvider')
->will($this->returnValue('editor'));
$this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
$this->entityManager->expects($this->any())
$this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
$this->entityTypeManager->expects($this->any())
->method('getDefinition')
->with($this->entityTypeId)
->will($this->returnValue($this->entityType));
@ -78,10 +80,16 @@ class EditorConfigEntityUnitTest extends UnitTestCase {
->disableOriginalConstructor()
->getMock();
$entity_manager = new EntityManager();
$container = new ContainerBuilder();
$container->set('entity.manager', $this->entityManager);
$container->set('entity.manager', $entity_manager);
$container->set('entity_type.manager', $this->entityTypeManager);
$container->set('uuid', $this->uuid);
$container->set('plugin.manager.editor', $this->editorPluginManager);
// Inject the container into entity.manager so it can defer to
// entity_type.manager.
$entity_manager->setContainer($container);
\Drupal::setContainer($container);
}
@ -120,7 +128,7 @@ class EditorConfigEntityUnitTest extends UnitTestCase {
->with($format_id)
->will($this->returnValue($filter_format));
$this->entityManager->expects($this->once())
$this->entityTypeManager->expects($this->once())
->method('getStorage')
->with('filter_format')
->will($this->returnValue($storage));

View file

@ -364,8 +364,11 @@ class StandardTest extends UnitTestCase {
// IMG STYLE with expression.
// @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#IMG_STYLE_with_expression
$data[] = ['exp/*<A STYLE=\'no\xss:noxss("*//*");
xss:ex/*XSS*//*/*/pression(alert("XSS"))\'>', 'exp/*<A>'];
$data[] = [
'exp/*<A STYLE=\'no\xss:noxss("*//*");
xss:ex/*XSS*//*/*/pression(alert("XSS"))\'>',
'exp/*<A>',
];
// STYLE tag (Older versions of Netscape only).
// @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#STYLE_tag_.28Older_versions_of_Netscape_only.29
@ -443,7 +446,9 @@ xss:ex/*XSS*//*/*/pression(alert("XSS"))\'>', 'exp/*<A>'];
// @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#Downlevel-Hidden_block
$data[] = ['<!--[if gte IE 4]>
<SCRIPT>alert(\'XSS\');</SCRIPT>
<![endif]-->', "\n alert('XSS');\n "];
<![endif]-->',
"\n alert('XSS');\n ",
];
// BASE tag.
// @see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#BASE_tag
@ -578,20 +583,20 @@ xss:ex/*XSS*//*/*/pression(alert("XSS"))\'>', 'exp/*<A>'];
'<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
'<unknown>Pink Fairy Armadillo</unknown><video src="gerenuk.mp4">alert(0)',
'Disallow only the script tag',
['script']
['script'],
],
[
'<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
'<unknown>Pink Fairy Armadillo</unknown>alert(0)',
'Disallow both the script and video tags',
['script', 'video']
['script', 'video'],
],
// No real use case for this, but it is an edge case we must ensure works.
[
'<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
'<unknown>Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
'Disallow no tags',
[]
[],
],
];
}