Skip to content

Commit

Permalink
Merge pull request #5 from OS2Forms/feature/FORMS-984-rest-webform-al…
Browse files Browse the repository at this point in the history
…l-submissions

FORMS-984: Added rest webform endpoint for getting all submissions
  • Loading branch information
jekuaitk authored Oct 24, 2023
2 parents f84e10f + 75949f0 commit 4b5c31b
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 22 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: [ '7.4', '8.0', '8.1' ]
php-versions: [ '8.1' ]
dependency-version: [ prefer-lowest, prefer-stable ]
steps:
- uses: actions/checkout@master
Expand Down Expand Up @@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: [ '7.4', '8.0', '8.1' ]
php-versions: [ '8.1' ]
dependency-version: [ prefer-lowest, prefer-stable ]
steps:
- uses: actions/checkout@master
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: [ '7.4', '8.0', '8.1' ]
php-versions: [ '8.1' ]
dependency-version: [ prefer-lowest, prefer-stable ]
steps:
- uses: actions/checkout@master
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ about writing changes to this log.

## [Unreleased]

- Added endpoint for getting all submissions on a webform.
- **Updated** permissions such that users must be given access explicitly.

## [1.1.0]

### Added
Expand Down
63 changes: 49 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,28 @@ vendor/bin/drush pm:enable os2forms_rest_api
We use [Key auth](https://www.drupal.org/project/key_auth) for authenticating
api users.

A user can access the Webforrm REST API if
A user can access the Webform REST API if

1. it has the “OS2Form REST API user” (`os2forms_rest_api_user`) role and
2. has a generated key (User > Edit > Key authentication; `/user/«user
1. it has the “OS2Form REST API user” (`os2forms_rest_api_user`) role,
2. has been granted access to the form
(see [Custom access control](#custom-access-control) )
3. has a generated key (User > Edit > Key authentication; `/user/«user
id»/key-auth`).

The “OS2Form REST API user” role gives read-only access to the API. To get read
The “OS2Form REST API user” role gives read-only access to the API. To get write
access, a user must also have the “OS2Form REST API user (write)”
(`os2forms_rest_api_user_write`) role.

## Endpoints

| Name | Path | Methods |
|--------------------|------------------------------------------------|---------|
| Webform Elements | `/webform_rest/{webform_id}/elements` | GET |
| Webform Fields | `/webform_rest/{webform_id}/fields` | GET |
| Webform Submission | `/webform_rest/{webform_id}/submission/{uuid}` | GET |
| Webform Submit | `/webform_rest/submit` | POST |
| File | `/entity/file/{file_id}` | GET |
| Name | Path | Methods |
|---------------------|------------------------------------------------|---------|
| Webform Elements | `/webform_rest/{webform_id}/elements` | GET |
| Webform Fields | `/webform_rest/{webform_id}/fields` | GET |
| Webform Submission | `/webform_rest/{webform_id}/submission/{uuid}` | GET |
| Webform Submissions | `/webform_rest/{webform_id}/submissions` | GET |
| Webform Submit | `/webform_rest/submit` | POST |
| File | `/entity/file/{file_id}` | GET |

## Examples

Expand Down Expand Up @@ -125,14 +128,46 @@ Response:

(the `sid` value is a webform submission uuid).

### Webform submissions

You can filter results based on submission time by
adding query parameters to the URL:

| Name | Value | Example |
|-------------|----------------------|--------------|
| `starttime` | [PHP Date and Time Formats](https://www.php.net/manual/en/datetime.formats.php) | `yesterday` |
| `endtime` | [PHP Date and Time Formats](https://www.php.net/manual/en/datetime.formats.php) | `2023-10-23` |

If left out, filtering upon the left out parameter will not be done.

This example requests all submissions on or after October 1st, 2023:

Request:

```sh
> curl --silent --header 'api-key: …' 'https://127.0.0.1:8000/webform_rest/some_webform_id/submissions?starttime=2023-10-01'
```

Response:

```json
{
"webform_id": "some_webform_id",
"starttime": "2023-10-01",
"submissions": {
"123": "https://127.0.0.1:8000/da/webform_rest/some_webform_id/submission/44b1fe1b-ee96-481e-b941-d1219d1dcb55",
"124": "https://127.0.0.1:8000/da/webform_rest/some_webform_id/submission/3652836d-3dab-4919-b880-e82cbbf3c24c"
}
}
```

## Custom access control

To limit access to webforms, you can specify a list of API users that are
To give access to webforms, you need to specify a list of API users that are
allowed to access a webform's data via the API.

Go to Settings > General > Third party settings > OS2Forms > REST API to specify
which users can access a webform's data. **If no users are specified, all API
users can access the data.**
which users can access a webform's data.

### Technical details

Expand Down
187 changes: 187 additions & 0 deletions src/Plugin/rest/resource/WebformAllFormSubmissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php

namespace Drupal\os2forms_rest_api\Plugin\rest\resource;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Url;
use Drupal\os2forms_rest_api\WebformHelper;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;

/**
* Creates a rest resource for retrieving webform submissions.
*
* @RestResource(
* id = "webform_rest_form_submissions",
* label = @Translation("Webform - submissions for a form"),
* uri_paths = {
* "canonical" = "/webform_rest/{webform_id}/submissions"
* }
* )
*/
class WebformAllFormSubmissions extends ResourceBase {
/**
* Allowed DateTime query parameters and their operation.
*/
private const ALLOWED_DATETIME_QUERY_PARAMS = [
'starttime' => '>=',
'endtime' => '<=',
];

/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
private $currentRequest;

/**
* The entity type manager object.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
private $entityTypeManager;

/**
* The webform helper.
*
* @var \Drupal\os2forms_rest_api\WebformHelper
*/
private $webformHelper;

/**
* {@inheritdoc}
*
* @phpstan-param array<string, mixed> $configuration
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);

$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->currentRequest = $container->get('request_stack')->getCurrentRequest();
$instance->webformHelper = $container->get(WebformHelper::class);

return $instance;
}

/**
* Get submissions for a given webform.
*
* @param string $webform_id
* Webform ID.
*
* @return \Drupal\rest\ModifiedResourceResponse
* Response object.
*/
public function get(string $webform_id): ModifiedResourceResponse {
if (empty($webform_id)) {
$errors = [
'error' => [
'message' => 'Webform ID is required.',
],
];
return new ModifiedResourceResponse($errors, Response::HTTP_BAD_REQUEST);
}

// Attempt finding webform.
$webform = $this->webformHelper->getWebform($webform_id);

if (NULL === $webform) {
$errors = [
'error' => [
'message' => $this->t('Could not find webform with id :webform_id', [':webform_id' => $webform_id]),
],
];

return new ModifiedResourceResponse($errors, Response::HTTP_NOT_FOUND);
}

// Webform access check.
if (!$this->webformHelper->hasWebformAccess($webform, $this->webformHelper->getCurrentUser())) {
$errors = [
'error' => [
'message' => $this->t('Access denied'),
],
];

return new ModifiedResourceResponse($errors, Response::HTTP_UNAUTHORIZED);
}

$result = ['webform_id' => $webform_id];

try {
$submissionEntityStorage = $this->entityTypeManager->getStorage('webform_submission');
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
$errors = [
'error' => [
'message' => $this->t('Could not load webform submission storage'),
],
];

return new ModifiedResourceResponse($errors, Response::HTTP_INTERNAL_SERVER_ERROR);
}

// Query for webform submissions with this webform_id.
$submissionQuery = $submissionEntityStorage->getQuery()
->condition('webform_id', $webform_id);

$requestQuery = $this->currentRequest->query;

foreach (self::ALLOWED_DATETIME_QUERY_PARAMS as $param => $operator) {
$value = $requestQuery->get($param);

if (!empty($value)) {
try {
$dateTime = new \DateTimeImmutable($value);
$submissionQuery->condition('created', $dateTime->getTimestamp(), $operator);
$result[$param] = $value;
}
catch (\Exception $e) {
$errors = [
'error' => [
'message' => $this->t('Invalid :param: :value', [':param' => $param, ':value' => $value]),
],
];

return new ModifiedResourceResponse($errors, Response::HTTP_BAD_REQUEST);
}
}
}

// Complete query.
$submissionQuery->accessCheck(FALSE);
$sids = $submissionQuery->execute();

// Generate submission URLs.
try {
$result['submissions'] = array_map(
static fn($submission) => Url::fromRoute(
'rest.webform_rest_submission.GET',
[
'webform_id' => $webform_id,
'uuid' => $submission->uuid(),
]
)
->setAbsolute()
->toString(TRUE)->getGeneratedUrl(),
$submissionEntityStorage->loadMultiple($sids ?: [])
);
}
catch (\Exception $e) {
$errors = [
'error' => [
'message' => $this->t('Could not generate submission URLs'),
],
];

return new ModifiedResourceResponse($errors, Response::HTTP_INTERNAL_SERVER_ERROR);
}

return new ModifiedResourceResponse($result);
}

}
22 changes: 17 additions & 5 deletions src/WebformHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ public function webformThirdPartySettingsFormAlter(array &$form, FormStateInterf
$routes = [
'rest.webform_rest_elements.GET',
'rest.webform_rest_fields.GET',
'rest.webform_rest_form_submissions.GET',
'rest.webform_rest_submission.GET',
];

$requireUuid = static function ($route) {
return in_array(
$route,
Expand Down Expand Up @@ -141,11 +143,11 @@ public function webformThirdPartySettingsFormAlter(array &$form, FormStateInterf
/** @var \Drupal\user\Entity\User $apiUser */
$apiUser = $this->entityTypeManager->getStorage('user')->load($this->currentUser->id());
// Don't show API data links if current user is not included in
// (non-empty) list of allowed users.
if (!empty($allowedUsers) && !isset($allowedUsers[$apiUser->id()])) {
// list of allowed users.
if (!isset($allowedUsers[$apiUser->id()])) {
$apiUser = NULL;
}
$apiKey = $apiUser ? $apiUser->api_key->value : NULL;
$apiKey = $apiUser?->api_key->value;
if (!empty($apiKey)) {
$form['third_party_settings']['os2forms']['os2forms_rest_api']['api_info']['endpoints_test'] = [
'#type' => 'fieldset',
Expand Down Expand Up @@ -258,7 +260,7 @@ public function hasWebformAccess(WebformInterface $webform, $user): bool {

$allowedUsers = $this->getAllowedUsers($webform);

return empty($allowedUsers) || isset($allowedUsers[$userId]);
return isset($allowedUsers[$userId]);
}

/**
Expand All @@ -278,7 +280,7 @@ private function loadUsers(array $spec): array {
*
* Note: This is only used to deny access to a file that is attached to a
* webform (submission) that the user does not have permission to access.
* Permission to access private files are handles elsewhere.
* Permission to access private files are handled elsewhere.
*
* @phpstan-return int|array<string, string>|null
*/
Expand Down Expand Up @@ -307,4 +309,14 @@ public function fileDownload(string $uri) {
return NULL;
}

/**
* Return current user.
*
* @return \Drupal\Core\Session\AccountProxyInterface
* The current user.
*/
public function getCurrentUser(): AccountProxyInterface {
return $this->currentUser;
}

}

0 comments on commit 4b5c31b

Please sign in to comment.