Commit 9d4d62d0 authored by alexpott's avatar alexpott

Issue #2419693 by Upchuk: Move URI access validation from widget to field constraint

parent 88350da6
......@@ -27,7 +27,7 @@
* description = @Translation("Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link."),
* default_widget = "link_default",
* default_formatter = "link",
* constraints = {"LinkType" = {}}
* constraints = {"LinkType" = {}, "LinkAccess" = {}}
* )
*/
class LinkItem extends FieldItemBase implements LinkItemInterface {
......
......@@ -156,9 +156,6 @@ public static function validateUriElement($element, FormStateInterface $form_sta
// Disallow unrouted internal URLs (i.e. disallow 'base:' URIs).
$disallowed = !$url->isRouted() && !$url->isExternal();
// Disallow URLs if the current user doesn't have the 'link to any page'
// permission nor can access this URI.
$disallowed = $disallowed || (!\Drupal::currentUser()->hasPermission('link to any page') && !$url->access());
// Disallow external URLs using untrusted protocols.
$disallowed = $disallowed || ($url->isExternal() && !in_array(parse_url($uri, PHP_URL_SCHEME), UrlHelper::getAllowedProtocols()));
// Disallow routed URLs that don't exist.
......@@ -172,7 +169,7 @@ public static function validateUriElement($element, FormStateInterface $form_sta
}
if ($disallowed) {
$form_state->setError($element, t("The path '@link_path' is either invalid or you do not have access to it.", ['@link_path' => static::getUriAsDisplayableString($uri)]));
$form_state->setError($element, t("The path '@link_path' is invalid.", ['@link_path' => static::getUriAsDisplayableString($uri)]));
}
}
}
......
<?php
/**
* @file
* Contains \Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraint.
*/
namespace Drupal\link\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Defines an access validation constraint for links.
*
* @Plugin(
* id = "LinkAccess",
* label = @Translation("Link URI can be accessed by the user.", context = "Validation"),
* )
*/
class LinkAccessConstraint extends Constraint {
public $message = "The path '@uri' is inaccessible.";
}
<?php
/**
* @file
* Contains \Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraintValidator.
*/
namespace Drupal\link\Plugin\Validation\Constraint;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ExecutionContextInterface;
/**
* Validates the LinkAccess constraint.
*/
class LinkAccessConstraintValidator implements ConstraintValidatorInterface, ContainerInjectionInterface {
/**
* Stores the validator's state during validation.
*
* @var \Symfony\Component\Validator\ExecutionContextInterface
*/
protected $context;
/**
* Proxy for the current user account.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $current_user;
/**
* Constructs an instance of the LinkAccessConstraintValidator class.
*
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user account.
*/
public function __construct(AccountProxyInterface $current_user) {
$this->current_user = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function initialize(ExecutionContextInterface $context) {
$this->context = $context;
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
if (isset($value)) {
try {
$url = $value->getUrl();
}
// If the URL is malformed this constraint cannot check access.
catch (\InvalidArgumentException $e) {
return;
}
// Disallow URLs if the current user doesn't have the 'link to any page'
// permission nor can access this URI.
$allowed = $this->current_user->hasPermission('link to any page') || $url->access();
if (!$allowed) {
$this->context->addViolation($constraint->message, array('@uri' => $value->uri));
}
}
}
}
......@@ -22,7 +22,7 @@
*/
class LinkTypeConstraint extends Constraint implements ConstraintValidatorInterface {
public $message = "The path '@uri' is either invalid or you do not have access to it.";
public $message = "The path '@uri' is invalid.";
/**
* @var \Symfony\Component\Validator\ExecutionContextInterface
......
......@@ -134,7 +134,7 @@ function testURLValidation() {
);
// Define some invalid URLs.
$validation_error_1 = "The path '@link_path' is either invalid or you do not have access to it.";
$validation_error_1 = "The path '@link_path' is invalid.";
$validation_error_2 = 'Manually entered paths should start with /, ? or #.';
$invalid_external_entries = array(
// Missing protcol
......
<?php
/**
* @file
* Contains \Drupal\Tests\link\LinkAccessConstraintValidatorTest.
*/
namespace Drupal\Tests\link;
use Drupal\Core\Url;
use Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraint;
use Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraintValidator;
use Drupal\Tests\UnitTestCase;
/**
* Tests the LinkAccessConstraintValidator validator.
*
* @coversDefaultClass \Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraintValidator
* @group validation
*/
class LinkAccessConstraintValidatorTest extends UnitTestCase {
/**
* Tests the \Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraintValidator::validate()
* method.
*
* @param \Drupal\link\LinkItemInterface $value
* The link item.
* @param \Drupal\Core\Session\AccountProxyInterface $user
* The user account.
* @param bool $valid
* A boolean indicating if the combination is expected to be valid.
*
* @covers ::validate
* @dataProvider providerValidate
*/
public function testValidate($value, $user, $valid) {
$context = $this->getMock('Symfony\Component\Validator\ExecutionContextInterface');
if ($valid) {
$context->expects($this->never())
->method('addViolation');
}
else {
$context->expects($this->once())
->method('addViolation');
}
$constraint = new LinkAccessConstraint();
$validate = new LinkAccessConstraintValidator($user);
$validate->initialize($context);
$validate->validate($value, $constraint);
}
/**
* Data provider for LinkAccessConstraintValidator::validate().
*
* @return array
* An array of tests, matching the parameter inputs for testValidate.
*
* @see \Drupal\Tests\link\LinkAccessConstraintValidatorTest::validate()
*/
public function providerValidate() {
$data = [];
$cases = [
['may_link_any_page' => TRUE, 'url_access' => TRUE, 'valid' => TRUE],
['may_link_any_page' => TRUE, 'url_access' => FALSE, 'valid' => TRUE],
['may_link_any_page' => FALSE, 'url_access' => TRUE, 'valid' => TRUE],
['may_link_any_page' => FALSE, 'url_access' => FALSE, 'valid' => FALSE],
];
foreach ($cases as $case) {
// Mock a Url object that returns a boolean indicating user access.
$url = $this->getMockBuilder('Drupal\Core\Url')
->disableOriginalConstructor()
->getMock();
$url->expects($this->once())
->method('access')
->willReturn($case['url_access']);
// Mock a link object that returns the URL object.
$link = $this->getMock('Drupal\link\LinkItemInterface');
$link->expects($this->any())
->method('getUrl')
->willReturn($url);
// Mock a user object that returns a boolean indicating user access to all
// links.
$user = $this->getMock('Drupal\Core\Session\AccountProxyInterface');
$user->expects($this->any())
->method('hasPermission')
->with($this->equalTo('link to any page'))
->willReturn($case['may_link_any_page']);
$data[] = [$link, $user, $case['valid']];
}
return $data;
}
}
......@@ -639,13 +639,18 @@ function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded
* Attempts to add menu link with invalid path or no access permission.
*/
function addInvalidMenuLink() {
foreach (array('/-&-', '/admin/people/permissions') as $link_path) {
foreach (array('valid' => '/-&-', 'access' => '/admin/people/permissions') as $type => $link_path) {
$edit = array(
'link[0][uri]' => $link_path,
'title[0][value]' => 'title',
);
$this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
$this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $link_path)), 'Menu link was not created');
if ($type === 'access') {
$this->assertRaw(t("The path '@link_path' is inaccessible.", array('@link_path' => $link_path)), 'Menu link was not created');
}
else {
$this->assertRaw(t("The path '@link_path' is invalid.", array('@link_path' => $link_path)), 'Menu link was not created');
}
}
}
......
......@@ -96,7 +96,7 @@ public function testShortcutLinkAdd() {
];
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertResponse(200);
$this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", ['@link_path' => '/admin']));
$this->assertRaw(t("The path '@link_path' is inaccessible.", ['@link_path' => '/admin']));
$form_data = [
'title[0][value]' => $title,
......
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