Commit a18b6da3 authored by bucefal91's avatar bucefal91 Committed by bucefal91

Issue #2996996 by bucefal91, jrockowitz: Introduce service to encapsulate...

Issue #2996996 by bucefal91, jrockowitz: Introduce service to encapsulate access rules logic therein
parent 81673070
......@@ -120,6 +120,10 @@ class WebformNodeAccessTest extends WebformNodeTestBase {
* @see \Drupal\webform\Tests\WebformEntityAccessControlsTest::testAccessRules
*/
public function testAccessRules() {
/** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */
$access_rules_manager = \Drupal::service('webform.access_rules_manager');
$default_access_rules = $access_rules_manager->getDefaultAccessRules();
$webform = Webform::load('contact');
$node = $this->createWebformNode('contact');
$nid = $node->id();
......@@ -140,7 +144,7 @@ class WebformNodeAccessTest extends WebformNodeTestBase {
$sid = $this->postNodeSubmission($node, $edit);
// Check create authenticated/anonymous access.
$webform->setAccessRules(Webform::getDefaultAccessRules())->save();
$webform->setAccessRules($default_access_rules)->save();
$this->drupalGet('node/' . $node->id());
$this->assertFieldByName('name', $this->normalUser->getAccountName());
$this->assertFieldByName('email', $this->normalUser->getEmail());
......@@ -150,7 +154,7 @@ class WebformNodeAccessTest extends WebformNodeTestBase {
'roles' => [],
'users' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
// Check no access.
......@@ -189,7 +193,7 @@ class WebformNodeAccessTest extends WebformNodeTestBase {
'roles' => [$rid],
'users' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalGet($path);
$this->assertResponse(200, 'Webform allows access via role access rules');
......@@ -200,7 +204,7 @@ class WebformNodeAccessTest extends WebformNodeTestBase {
'roles' => [],
'users' => [$uid],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalGet($path);
$this->assertResponse(200, 'Webform allows access via user access rules');
......
......@@ -965,176 +965,6 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
];
}
/**
* {@inheritdoc}
*/
public static function getDefaultAccessRules() {
return [
'create' => [
'roles' => [
'anonymous',
'authenticated',
],
'users' => [],
'permissions' => [],
],
'view_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'update_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'delete_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'purge_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'view_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'update_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'delete_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'administer' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'test' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
];
}
/**
* {@inheritdoc}
*/
public function checkAccessRules($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission = NULL) {
// Always grant access to user that can administer webforms.
if ($account->hasPermission('administer webform')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Grant user with administer webform submission access to view all webform submissions.
if ($account->hasPermission('administer webform submission') && $operation != 'administer') {
return AccessResult::allowed()->cachePerPermissions();
}
// The "page" operation is the same as "create" but requires that the
// Webform is allowed to be displayed as dedicated page.
// Used by the 'entity.webform.canonical' route.
if ($operation == 'page') {
if (empty($this->settings['page'])) {
return AccessResult::forbidden()->addCacheableDependency($this);
}
else {
$operation = 'create';
}
}
$access_rules = $this->getAccessRules() + static::getDefaultAccessRules();
$cacheability = new CacheableMetadata();
$cacheability->addCacheableDependency($this);
$cacheability->addCacheContexts(['user.permissions']);
foreach ($access_rules as $access_rule) {
// If there is some per-user access logic, our response must be cacheable
// accordingly.
if (!empty($access_rule['users'])) {
$cacheability->addCacheContexts(['user']);
}
}
// Check administer access rule and grant full access to user.
if ($this->checkAccessRule($access_rules['administer'], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
// Check operation specific access rules.
if (in_array($operation, ['create', 'view_any', 'update_any', 'delete_any', 'purge_any', 'administer', 'test'])
&& $this->checkAccessRule($access_rules[$operation], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
if (isset($access_rules[$operation . '_any'])
&& $this->checkAccessRule($access_rules[$operation . '_any'], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
// If webform submission is not set then check 'view own'.
// @see \Drupal\webform\WebformSubmissionForm::displayMessages.
if (empty($webform_submission)
&& $operation === 'view_own'
&& $this->checkAccessRule($access_rules[$operation], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
// If webform submission is set then check the webform submission owner.
if (!empty($webform_submission)) {
$is_authenticated_owner = ($account->isAuthenticated() && $account->id() === $webform_submission->getOwnerId());
$is_anonymous_owner = ($account->isAnonymous() && !empty($_SESSION['webform_submissions']) && isset($_SESSION['webform_submissions'][$webform_submission->id()]));
$is_owner = ($is_authenticated_owner || $is_anonymous_owner);
if ($is_owner) {
if (isset($access_rules[$operation . '_own'])
&& $this->checkAccessRule($access_rules[$operation . '_own'], $account)) {
return AccessResult::allowed()->cachePerUser()->addCacheableDependency($cacheability);
}
}
}
return AccessResult::forbidden()->addCacheableDependency($cacheability);
}
/**
* Checks an access rule against a user account's roles and id.
*
* @param array $access_rule
* An access rule.
* @param \Drupal\Core\Session\AccountInterface $account
* The user session for which to check access.
*
* @return bool
* The access result. Returns a TRUE if access is allowed.
*
* @see \Drupal\webform\Plugin\WebformElementBase::checkAccessRule
*/
protected function checkAccessRule(array $access_rule, AccountInterface $account) {
if (!empty($access_rule['roles']) && array_intersect($access_rule['roles'], $account->getRoles())) {
return TRUE;
}
elseif (!empty($access_rule['users']) && in_array($account->id(), $access_rule['users'])) {
return TRUE;
}
elseif (!empty($access_rule['permissions'])) {
foreach ($access_rule['permissions'] as $permission) {
if ($account->hasPermission($permission)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
......@@ -1808,11 +1638,14 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
/** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */
$access_rules_manager = \Drupal::service('webform.access_rules_manager');
$values += [
'status' => \Drupal::config('webform.settings')->get('settings.default_status'),
'uid' => \Drupal::currentUser()->id(),
'settings' => static::getDefaultSettings(),
'access' => static::getDefaultAccessRules(),
'access' => $access_rules_manager->getDefaultAccessRules(),
];
// Convert boolean status to STATUS constant.
......
......@@ -335,7 +335,7 @@ interface WebformElementInterface extends PluginInspectionInterface, PluginFormI
* @return bool
* TRUE is the element can be accessed by the user.
*
* @see \Drupal\webform\Entity\Webform::checkAccessRules
* @see \Drupal\webform\WebformAccessRulesManagerInterface::checkWebformAccess
*/
public function checkAccessRules($operation, array $element, AccountInterface $account = NULL);
......
......@@ -43,6 +43,10 @@ class WebformAccessRulesTest extends WebformTestBase {
public function testAccessRules() {
global $base_path;
/** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */
$access_rules_manager = \Drupal::service('webform.access_rules_manager');
$default_access_rules = $access_rules_manager->getDefaultAccessRules();
/** @var \Drupal\webform\WebformInterface $webform */
$webform = Webform::load('test_submissions');
/** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */
......@@ -64,7 +68,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [$uid],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalLogin($account);
$this->drupalGet("webform/$webform_id/test");
......@@ -78,7 +82,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [$uid],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalLogin($account);
$this->drupalGet("admin/structure/webform/manage/$webform_id/settings");
......@@ -88,7 +92,7 @@ class WebformAccessRulesTest extends WebformTestBase {
$this->drupalLogout($account);
// Check create authenticated/anonymous access.
$webform->setAccessRules(Webform::getDefaultAccessRules())->save();
$webform->setAccessRules($default_access_rules)->save();
$this->drupalGet('webform/' . $webform->id());
$this->assertResponse(200, 'Webform create submission access for anonymous/authenticated user.');
......@@ -98,7 +102,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
// Check no access.
......@@ -148,7 +152,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalGet($path);
$this->assertResponse(200, 'Webform allows access via role access rules');
......@@ -160,7 +164,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [$uid],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalGet($path);
$this->assertResponse(200, 'Webform allows access via user access rules');
......@@ -172,7 +176,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [],
'permissions' => ['access content'],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
$this->drupalGet($path);
$this->assertResponse(200, "Webform allows access via permission access rules");
......@@ -195,7 +199,7 @@ class WebformAccessRulesTest extends WebformTestBase {
'users' => [],
'permissions' => [],
],
] + Webform::getDefaultAccessRules();
] + $default_access_rules;
$webform->setAccessRules($access_rules)->save();
// Must delete all existing anonymous submission to prevent them from
......
<?php
namespace Drupal\webform;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Session\AccountInterface;
/**
* The webform access rules manager service.
*/
class WebformAccessRulesManager implements WebformAccessRulesManagerInterface {
/**
* {@inheritdoc}
*/
public function checkWebformAccess($operation, AccountInterface $account, WebformInterface $webform) {
$access_rules = $this->getAccessRules($webform);
return $this->checkAccessRules($operation, $account, $access_rules)
->addCacheableDependency($webform);
}
/**
* {@inheritdoc}
*/
public function checkWebformSubmissionAccess($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission) {
$webform = $webform_submission->getWebform();
$access_rules = $this->getAccessRules($webform);
$access = $this->checkAccessRules($operation, $account, $access_rules);
$access->addCacheableDependency($webform);
$access->addCacheableDependency($webform_submission);
if ($access->isAllowed()) {
return $access;
}
// Check the webform submission owner.
$is_authenticated_owner = ($account->isAuthenticated() && $account->id() === $webform_submission->getOwnerId());
$is_anonymous_owner = ($account->isAnonymous() && !empty($_SESSION['webform_submissions']) && isset($_SESSION['webform_submissions'][$webform_submission->id()]));
$is_owner = ($is_authenticated_owner || $is_anonymous_owner);
if ($is_owner && isset($access_rules[$operation . '_own']) && $this->checkAccessRule($access_rules[$operation . '_own'], $account)) {
return AccessResult::allowed()->cachePerUser()->addCacheableDependency($access);
}
return AccessResult::forbidden()->addCacheableDependency($access);
}
/****************************************************************************/
// Get access rules methods.
/****************************************************************************/
/**
* {@inheritdoc}
*/
public function getDefaultAccessRules() {
return [
'create' => [
'roles' => [
'anonymous',
'authenticated',
],
'users' => [],
'permissions' => [],
],
'view_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'update_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'delete_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'purge_any' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'view_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'update_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'delete_own' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'administer' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
'test' => [
'roles' => [],
'users' => [],
'permissions' => [],
],
];
}
/**
* Retrieve a list of access rules from a webform.
*
* @param \Drupal\webform\WebformInterface $webform
* Webform whose access rules to retrieve.
*
* @return array
* Associative array of access rules contained in the provided webform. Keys
* are operation names whereas values are sub arrays with the following
* structure:
* - roles: (array) Array of roles that should have access to this operation
* - users: (array) Array of UIDs that should have access to this operation
* - permissions: (array) Array of permissions that should grant access to
* this operation
*/
protected function getAccessRules(WebformInterface $webform) {
return $webform->getAccessRules() + $this->getDefaultAccessRules();
}
/****************************************************************************/
// Get access rules methods.
/****************************************************************************/
/**
* Check access for a given operation and set of access rules.
*
* @param string $operation
* Operation that is being requested.
* @param \Drupal\Core\Session\AccountInterface $account
* Account that is requesting access to the operation.
* @param array $access_rules
* A set of access rules to check against.
*
* @return AccessResult
* Access result.
*/
protected function checkAccessRules($operation, AccountInterface $account, array $access_rules) {
$cacheability = new CacheableMetadata();
$cacheability->addCacheContexts(['user.permissions']);
foreach ($access_rules as $access_rule) {
// If there is some per-user access logic, our response must be cacheable
// accordingly.
if (!empty($access_rule['users'])) {
$cacheability->addCacheContexts(['user']);
}
}
// Check administer access rule and grant full access to user.
if ($this->checkAccessRule($access_rules['administer'], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
// Check operation specific access rules.
if (isset($access_rules[$operation])
&& $this->checkAccessRule($access_rules[$operation], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
if (isset($access_rules[$operation . '_any'])
&& $this->checkAccessRule($access_rules[$operation . '_any'], $account)) {
return AccessResult::allowed()->addCacheableDependency($cacheability);
}
return AccessResult::forbidden()->addCacheableDependency($cacheability);
}
/**
* Checks an access rule against a user account's roles and id.
*
* @param array $access_rule
* An access rule.
* @param \Drupal\Core\Session\AccountInterface $account
* The user session for which to check access.
*
* @return bool
* The access result. Returns a TRUE if access is allowed.
*
* @see \Drupal\webform\Plugin\WebformElementBase::checkAccessRule
*/
protected function checkAccessRule(array $access_rule, AccountInterface $account) {
if (!empty($access_rule['roles']) && array_intersect($access_rule['roles'], $account->getRoles())) {
return TRUE;
}
elseif (!empty($access_rule['users']) && in_array($account->id(), $access_rule['users'])) {
return TRUE;
}
elseif (!empty($access_rule['permissions'])) {
foreach ($access_rule['permissions'] as $permission) {
if ($account->hasPermission($permission)) {
return TRUE;
}
}
}
return FALSE;
}
}
<?php
namespace Drupal\webform;
use Drupal\Core\Session\AccountInterface;
/**
* Interface of webform access rules manager.
*/
interface WebformAccessRulesManagerInterface {
/**
* Check if operation is allowed through access rules for a given webform.
*
* @param string $operation
* Operation to check.
* @param \Drupal\Core\Session\AccountInterface $account
* Account who is requesting the operation.
* @param \Drupal\webform\WebformInterface $webform
* Webform on which the operation is requested.
*
* @return \Drupal\Core\Access\AccessResultInterface
* Access result.
*/
public function checkWebformAccess($operation, AccountInterface $account, WebformInterface $webform);
/**
* Check if operation is allowed through access rules for a submission.
*
* @param string $operation
* Operation to check.
* @param \Drupal\Core\Session\AccountInterface $account
* Account who is requesting the operation.
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* Webform submission on which the operation is requested.
*
* @return \Drupal\Core\Access\AccessResultInterface
* Access result.
*/
public function checkWebformSubmissionAccess($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission);
/**
* Returns the webform default access rules.
*
* @return array
* A structured array containing all the webform default access rules.
*/
public function getDefaultAccessRules();
}
......@@ -41,6 +41,13 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
*/
protected $webformSourceEntityManager;
/**
* Webform access rules manager service.
*
* @var \Drupal\webform\WebformAccessRulesManagerInterface
*/
protected $accessRulesManager;
/**
* WebformEntityAccessControlHandler constructor.
*
......@@ -52,13 +59,16 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
* The entity type manager.
* @param \Drupal\webform\Plugin\WebformSourceEntityManagerInterface $webform_source_entity_manager
* Webform source entity plugin manager.
* @param \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager
* Webform access rules manager service.
*/
public function __construct(EntityTypeInterface $entity_type, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, WebformSourceEntityManagerInterface $webform_source_entity_manager) {
public function __construct(EntityTypeInterface $entity_type, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, WebformSourceEntityManagerInterface $webform_source_entity_manager, WebformAccessRulesManagerInterface $access_rules_manager) {
parent::__construct($entity_type);
$this->requestStack = $request_stack;
$this->entityTypeManager = $entity_type_manager;
$this->webformSourceEntityManager = $webform_source_entity_manager;
$this->accessRulesManager = $access_rules_manager;
}
/**
......@@ -69,7 +79,8 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
$entity_type,
$container->get('request_stack'),
$container->get('entity_type.manager'),
$container->get('plugin.manager.webform.source_entity')
$container->get('plugin.manager.webform.source_entity'),
$container->get('webform.access_rules_manager')
);
}
......@@ -91,12 +102,16 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\webform\WebformInterface $entity */
if ($account->hasPermission('administer webform')) {
return AccessResult::allowed()->cachePerPermissions();
}
$uid = $entity->getOwnerId();
$is_owner = ($account->isAuthenticated() && $account->id() == $uid);
// Check if 'view' (aka 'access configuration'), 'update', or 'delete'
// of 'own' or 'any' webform is allowed.
if ($account->isAuthenticated()) {
$has_administer = $entity->checkAccessRules('administer', $account);
$has_administer = $this->accessRulesManager->checkWebformAccess('administer', $account, $entity);
switch ($operation) {
case 'view':
// The 'view' operation is reserved for accessing a
......@@ -136,7 +151,7 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
// Check test operation.
if ($operation === 'test') {
$access_rules = $entity->checkAccessRules($operation, $account);
$access_rules = $this->accessRulesManager->checkWebformAccess($operation, $account, $entity);
if ($access_rules->isAllowed()) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheableDependency($access_rules);
}
......@@ -144,6 +159,12 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
// Check submission_* operation.
if (strpos($operation, 'submission_') === 0) {
// Grant user with administer webform submission access to do whatever he
// likes on the submission operations.
if ($account->hasPermission('administer webform submission')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Allow users with 'view any webform submission' or
// 'administer webform submission' to view all submissions.
if ($operation == 'submission_view_any' && ($account->hasPermission('view any webform submission') || $account->hasPermission('administer webform submission'))) {
......@@ -174,18 +195,27 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
}
}
// Completely block access to a template if the user can't create new
// Webforms.
if ($operation == 'submission_page' && $entity->isTemplate()) {