Commit dad5e2e1 authored by jrockowitz's avatar jrockowitz Committed by jrockowitz

Issue #3091662 by jrockowitz, DanChadwick: Add access control methods to webform handlers

parent bd8c3ea7
......@@ -3,6 +3,7 @@
namespace Drupal\webform\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Serialization\Yaml;
......@@ -2377,45 +2378,77 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
}
}
// If webform submission and alter settings, make sure to completely
// reset all settings to their original values.
if ($method === 'overrideSettings') {
$this->resetSettings();
$settings = $this->getSettings();
$handlers = $this->getHandlers();
foreach ($handlers as $handler) {
$handler->setWebformSubmission($webform_submission);
$this->invokeHandlerAlter($handler, $method, $args);
// Get handlers.
$handlers = $this->getHandlers();
if ($handler->isEnabled() && $handler->checkConditions($webform_submission)) {
$handler->overrideSettings($settings, $webform_submission);
switch ($method) {
case 'overrideSettings';
// If webform submission and alter settings, make sure to completely
// reset all settings to their original values.
$this->resetSettings();
$settings = $this->getSettings();
foreach ($handlers as $handler) {
$handler->setWebformSubmission($webform_submission);
$this->invokeHandlerAlter($handler, $method, $args);
if ($this->isHandlerEnabled($handler, $webform_submission)) {
$handler->overrideSettings($settings, $webform_submission);
}
}
}
// If a handler has change some settings set override.
// Only look for altered original settings, which prevents issues where
// a webform saved settings and default settings are out-of-sync.
if (array_intersect_key($settings, $this->settingsOriginal) != $this->settingsOriginal) {
$this->setSettingsOverride($settings);
}
}
else {
$handlers = $this->getHandlers();
foreach ($handlers as $handler) {
$handler->setWebformSubmission($webform_submission);
$this->invokeHandlerAlter($handler, $method, $args);
// If the handler is disabled never invoke it.
if ($handler->isDisabled()) {
continue;
// If a handler has change some settings set override.
// Only look for altered original settings, which prevents issues where
// a webform saved settings and default settings are out-of-sync.
if (array_intersect_key($settings, $this->settingsOriginal) != $this->settingsOriginal) {
$this->setSettingsOverride($settings);
}
// If the arguments contain the webform submission check conditions.
if ($webform_submission && !$handler->checkConditions($webform_submission)) {
continue;
return NULL;
case 'access':
// WebformHandler::access() returns a AccessResult.
/** @var \Drupal\Core\Access\AccessResultInterface $result */
$result = AccessResult::neutral();
foreach ($handlers as $handler) {
$handler->setWebformSubmission($webform_submission);
$this->invokeHandlerAlter($handler, $method, $args);
if ($this->isHandlerEnabled($handler, $webform_submission)) {
$result = $result->orIf($handler->$method($data, $context1, $context2));
}
}
return $result;
default:
foreach ($handlers as $handler) {
$handler->setWebformSubmission($webform_submission);
$this->invokeHandlerAlter($handler, $method, $args);
if ($this->isHandlerEnabled($handler, $webform_submission)) {
$handler->$method($data, $context1, $context2);
}
}
return NULL;
}
}
$handler->$method($data, $context1, $context2);
}
/**
* Determine if a webform handler is enabled.
*
* @param \Drupal\webform\Plugin\WebformHandlerInterface $handler
* A webform handler.
* @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission
* A webform submission.
*
* @return bool
* TRUE if a webform handler is enabled.
*/
protected function isHandlerEnabled(WebformHandlerInterface $handler, WebformSubmissionInterface $webform_submission = NULL) {
// Check if the handler is disabled.
if ($handler->isDisabled()) {
return FALSE;
}
// If webform submission defined, check the handlers conditions.
elseif ($webform_submission && !$handler->checkConditions($webform_submission)) {
return FALSE;
}
else {
return TRUE;
}
}
......
......@@ -550,7 +550,7 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
*/
public function invokeWebformHandlers($method, &$context1 = NULL, &$context2 = NULL) {
if ($webform = $this->getWebform()) {
$webform->invokeHandlers($method, $this, $context1, $context2);
return $webform->invokeHandlers($method, $this, $context1, $context2);
}
}
......@@ -850,6 +850,15 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
return $this->entityTypeManager()->getStorage($this->entityTypeId)->resave($this);
}
/**
* {@inheritdoc}
*/
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
$access = parent::access($operation, $account, TRUE)
->orIf($this->invokeWebformHandlers('access', $operation, $account));
return $return_as_object ? $access : $access->isAllowed();
}
/**
* {@inheritdoc}
*/
......
......@@ -8,6 +8,8 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\webform\Utility\WebformElementHelper;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionConditionsValidatorInterface;
......@@ -571,6 +573,13 @@ abstract class WebformHandlerBase extends PluginBase implements WebformHandlerIn
*/
public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) {}
/**
* {@inheritdoc}
*/
public function access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account = NULL) {
return AccessResult::neutral();
}
/****************************************************************************/
// Preprocessing methods.
/****************************************************************************/
......
......@@ -7,6 +7,7 @@ use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionInterface;
......@@ -471,6 +472,21 @@ interface WebformHandlerInterface extends PluginInspectionInterface, Configurabl
*/
public function postDelete(WebformSubmissionInterface $webform_submission);
/**
* Controls entity operation access to webform submission.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
* @param string $operation
* The operation that is to be performed on $entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The account trying to access the entity.
*
* @return \Drupal\Core\Core\AccessResultInterface
* The result of the access check. No option returns a nuetral result.
*/
public function access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account = NULL);
/****************************************************************************/
// Preprocessing methods.
/****************************************************************************/
......
......@@ -926,6 +926,9 @@ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollec
* (optional) An additional variable that is passed by reference.
* @param mixed $context2
* (optional) An additional variable that is passed by reference.
*
* @return \Drupal\Core\Access\AccessResult|null
* If 'access' method is invoked an AccessResult is returned.
*/
public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = NULL);
......
......@@ -408,6 +408,9 @@ interface WebformSubmissionInterface extends ContentEntityInterface, EntityOwner
*
* @param string $method
* The webform handler method to be invoked.
*
* @return \Drupal\Core\Access\AccessResult|null
* If 'access' method is invoked an AccessResult is returned.
*/
public function invokeWebformHandlers($method);
......
......@@ -1152,7 +1152,7 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
*/
public function invokeWebformHandlers($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) {
$webform = $webform_submission->getWebform();
$webform->invokeHandlers($method, $webform_submission, $context1, $context2);
return $webform->invokeHandlers($method, $webform_submission, $context1, $context2);
}
/**
......
......@@ -499,6 +499,9 @@ interface WebformSubmissionStorageInterface extends ContentEntityStorageInterfac
* (optional) An additional variable that is passed by reference.
* @param mixed $context2
* (optional) An additional variable that is passed by reference.
*
* @return \Drupal\Core\Access\AccessResult|null
* If 'access' method is invoked an AccessResult is returned.
*/
public function invokeWebformHandlers($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL);
......
......@@ -21,7 +21,7 @@ elements: |
'#type': textfield
'#title': 'Empty element'
'#description': 'Entering any value will throw an error'
css: ''
javascript: ''
settings:
......
......@@ -2,7 +2,9 @@
namespace Drupal\webform_test_handler\Plugin\WebformHandler;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionInterface;
......@@ -85,7 +87,8 @@ class TestWebformHandler extends WebformHandlerBase {
*/
public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
$this->displayMessage(__FUNCTION__);
if ($value = $form_state->getValue('element')) {
$value = $form_state->getValue('element');
if ($value && $value !== 'access denied') {
$form_state->setErrorByName('element', $this->t('The element must be empty. You entered %value.', ['%value' => $value]));
}
}
......@@ -155,6 +158,21 @@ class TestWebformHandler extends WebformHandlerBase {
$this->displayMessage(__FUNCTION__, $update ? 'update' : 'insert');
}
/**
* {@inheritdoc}
*/
public function access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account = NULL) {
$this->displayMessage(__FUNCTION__);
$value = $webform_submission->getElementData('element');
if ($value === 'access denied') {
$access_result = AccessResult::forbidden();
}
else {
$access_result = parent::access($webform_submission, $operation, $account);
}
return $access_result->setCacheMaxAge(0);
}
/**
* {@inheritdoc}
*/
......
......@@ -45,17 +45,20 @@ class WebformHandlerTest extends WebformBrowserTestBase {
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElement');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterForm');
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
// Check validate submission plugin invoked and displaying an error.
$this->postSubmission($webform_handler_test, ['element' => 'a value']);
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postCreate');
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElement');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterForm');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:validateForm');
$this->assertRaw('The element must be empty. You entered <em class="placeholder">a value</em>.');
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
$this->assertNoRaw('One two one two this is just a test');
// Check submit submission plugin invoking.
......@@ -77,6 +80,7 @@ class WebformHandlerTest extends WebformBrowserTestBase {
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preprocessConfirmation');
$this->assertRaw('<div class="webform-confirmation__message">::preprocessConfirmation</div>');
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
// Check confirmation with token.
$this->drupalGet('/webform/test_handler_test/confirmation', ['query' => ['token' => $webform_submission->getToken()]]);
......@@ -88,7 +92,7 @@ class WebformHandlerTest extends WebformBrowserTestBase {
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preprocessConfirmation');
$this->assertRaw('<div class="webform-confirmation__message">::preprocessConfirmation</div>');
// Check confirmation with out token.
// Check confirmation without token.
$this->drupalGet('/webform/test_handler_test/confirmation');
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postLoad');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements');
......@@ -99,15 +103,26 @@ class WebformHandlerTest extends WebformBrowserTestBase {
// Check update submission plugin invoking.
$this->drupalPostForm('/admin/structure/webform/manage/test_handler_test/submission/' . $sid . '/edit', [], t('Save'));
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postSave update');
// Check delete submission plugin invoking.
$this->drupalPostForm('/admin/structure/webform/manage/test_handler_test/submission/' . $sid . '/delete', [], t('Delete'));
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:access');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postLoad');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preDelete');
$this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postDelete');
$this->assertRaw('<em class="placeholder">Test: Handler: Test invoke methods: Submission #' . $webform_submission->serial() . '</em> has been deleted.');
// Check submission access returns forbidden when element value is set to 'access denied'.
$sid = $this->postSubmission($webform_handler_test, ['element' => 'access denied']);
$this->drupalGet('admin/structure/webform/manage/test_handler_test/submission/' . $sid);
$this->assertResponse(403);
$this->drupalGet('admin/structure/webform/manage/test_handler_test/submission/' . $sid . '/edit');
$this->assertResponse(403);
$this->drupalGet('admin/structure/webform/manage/test_handler_test/submission/' . $sid . '/delete');
$this->assertResponse(403);
// Check configuration settings.
$this->drupalPostForm('admin/structure/webform/manage/test_handler_test/handlers/test/edit', ['settings[message]' => '{message}'], t('Save'));
$this->postSubmission($webform_handler_test);
......@@ -235,7 +250,7 @@ class WebformHandlerTest extends WebformBrowserTestBase {
'elements' => "element:
'#type': textfield
'#title': 'Empty element'
'#description': 'Entering any value will throw an error'",
'#description': 'Entering any value will throw an error",
];
$this->drupalPostForm('admin/structure/webform/manage/test_handler_test', $edit, t('Save'));
$this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:createElement');
......
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