Permission to download webform attachments

Created on 29 September 2023, almost 2 years ago

Problem/Motivation

A Webform Task allows the assigned user(s) to view the related webform submission. They don't need permission to view the webform submission directly.

However, if the webform has uploaded files and the user clicks the links to download those files, they get "Access Denied" - because the Webform module checks their permissions at that point.

As a workaround, I had to give "View any submissions" permission for that webform to the relevant users... But ideally they would only be able to view submissions and download files relating to tasks that are currently assigned to them.

Steps to reproduce

Untested, but I think:

  • Create a webform with at least one file upload field, linked to a workflow
  • Create a user that doesn't have permission to view the submissions for that webform
  • Create a Webform Task that displays the results of that form, assigned to the unprivileged user
  • Submit the webform to start the workflow, and progress it to that task
  • As the unprivileged user, view the submission and try to click the file download link

Proposed resolution

I dug through the code for a while, and couldn't find a simple solution. I tried implementing hook_webform_submission_access(), but it didn't seem to get called. I was able to get it partly working by adding this function to MaestroWebformHandler:

  public function access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account = NULL) {
    if ($operation === 'view' && ...) {
      return AccessResult::allowed();
    }

    return parent::access($webform_submission, $operation, $account);
  }

But I'm not sure how to implement the missing part, which would need to check whether the user has at least one active task that uses that webform assigned to them.

Of course this also gives them access to the View Submission page itself - but because of the way Webform permissions are implemented, that's the only option I could see. Something like && Drupal::request()->get('_controller') === '\Drupal\system\FileDownloadController::download' might work, but doesn't feel like a safe solution!

Also, it doesn't do anything like checking tokens (we don't use that functionality). A better solution may be to replace the links in the webform submission output to URLs handled by Maestro...

At this point I decided to stick with the "View any submissions" solution - but thought I'd write this up in case anyone else has a similar issue, or a better idea how to solve it.

Remaining tasks

User interface changes

API changes

Data model changes

Feature request
Status

Active

Version

3.1

Component

Code

Created by

🇬🇧United Kingdom mi-dave Oxford, England

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @mi-dave
  • 🇬🇧United Kingdom mi-dave Oxford, England
  • 🇨🇦Canada _randy

    Generally, we'd implement the Webform access rules and webform submission access api. It does feel "clunky" though rather than just a singular hook during submission access to modify.

    Perhaps we just make this generic. We implement the access method in the MaestroWebformHandler which just does a hook invocation.

    This way you can code up any sort of check and return value and we just pass that along as the return from the access method. We'd return Neutral as a default.

  • 🇬🇧United Kingdom mi-dave Oxford, England

    I decided to revisit this. This time I was able to get hook_webform_submission_access() working. I thought I'd share it in case it's useful for anyone else.

    use Drupal\Core\Access\AccessResult;
    use Drupal\Core\Session\AccountInterface;
    use Drupal\maestro\Engine\MaestroEngine;
    use Drupal\maestro_webform\Plugin\EngineTasks\MaestroWebformTask;
    use Drupal\webform\WebformSubmissionInterface;
    
    /**
     * Implements hook_webform_submission_access().
     */
    function mi_workflow_webform_submission_access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account) {
      // Only allow them to view the submission - not edit or delete it
      if ($operation !== 'view') {
        return AccessResult::neutral();
      }
    
      // If no-one is logged in, there's nothing for us to check
      if ($account->isAnonymous()) {
        return AccessResult::neutral()->addCacheContexts(['user']);
      }
    
      // Only give access to the file downloads, not the submission itself
      if (Drupal::routeMatch()->getRouteName() !== 'system.files') {
        return AccessResult::neutral()->addCacheContexts(['user', 'route']);
      }
    
      // Look for related Webform tasks assigned to the user
      $submission_id = $webform_submission->id();
      $queue_ids = MaestroEngine::getAssignedTaskQueueIds($account->id());
    
      foreach ($queue_ids as $queue_id) {
        // Check if this is a Webform task
        $template_task = MaestroEngine::getTemplateTaskByQueueID($queue_id);
        if (!$template_task) {
          continue;
        }
    
        $plugin_task = MaestroEngine::getPluginTask($template_task['tasktype']);
        if (!($plugin_task instanceof MaestroWebformTask)) {
          continue;
        }
    
        // Check if the submission ID matches
        $process_id = MaestroEngine::getProcessIdFromQueueId($queue_id);
        if (!$process_id) {
          continue;
        }
    
        $queued_submission_id = MaestroEngine::getEntityIdentiferByUniqueID($process_id, $template_task['data']['unique_id']);
    
        if ($queued_submission_id === $submission_id) {
          return AccessResult::allowed()->addCacheContexts(['user', 'route']);
        }
      }
    
      return AccessResult::neutral()->addCacheContexts(['user', 'route']);
    }
    
  • 🇨🇦Canada _randy

    Glad you were able to sort it out and thanks for posting your solution.

Production build 0.71.5 2024