Skip to content

Commit 6a6b589

Browse files
committed
Added module code
1 parent 00a951b commit 6a6b589

File tree

7 files changed

+582
-0
lines changed

7 files changed

+582
-0
lines changed

README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,136 @@
11
# OS2Forms REST API
22

3+
We use [Webform REST](https://www.drupal.org/project/webform_rest) to expose a
4+
number of API endpoints.
5+
6+
## Authentication
7+
8+
We use [Key auth](https://www.drupal.org/project/key_auth) for authenticating
9+
api users.
10+
11+
A user can access the REST API if
12+
13+
1. it has the “API user” (`api_user`) role and
14+
2. has a generated key (User > Edit > Key authentication; `/user/«user
15+
id»/key-auth`).
16+
17+
The “API user” role gives read-only access to the API. To get read access, a
18+
user must also have the “API user (write)” (`api_user_write`) role.
19+
20+
## Endpoints
21+
22+
| Name | Path | Methods |
23+
|--------------------|------------------------------------------------|---------|
24+
| Webform Elements | `/webform_rest/{webform_id}/elements` | GET |
25+
| Webform Fields | `/webform_rest/{webform_id}/fields` | GET |
26+
| Webform Submission | `/webform_rest/{webform_id}/submission/{uuid}` | GET |
27+
| Webform Submit | `/webform_rest/submit` | POST |
28+
| File | `/entity/file/{file_id}` | GET |
29+
30+
## Examples
31+
32+
### Submit webform
33+
34+
Request:
35+
36+
```sh
37+
> curl --silent --location --header 'api-key: …' --header 'content-type: application/json' https://127.0.0.1:8000/webform_rest/submit --data @- <<'JSON'
38+
{
39+
"webform_id": "{webform_id}",
40+
"//": "Webform field values (cf. /webform_rest/{webform_id}/fields)",
41+
"navn_": "Mikkel",
42+
"adresse": "Livets landevej",
43+
"mail_": "[email protected]",
44+
"telefonnummer_": "12345678"
45+
}
46+
JSON
47+
```
48+
49+
Response:
50+
51+
```json
52+
{"sid":"6d95afe9-18d1-4a7d-a1bf-fd38c58c7733"}
53+
```
54+
55+
(the `sid`value is a webform submission uuid).
56+
57+
### Get document from webform id and submission uuid
58+
59+
Example uses `some_webform_id` as webform id, `some_submission_id` as
60+
submission id and `dokumenter` as the webform document element key.
61+
62+
Request:
63+
64+
```sh
65+
> curl --silent --header 'api-key: …' https://127.0.0.1:8000/webform_rest/some_webform_id/submission/some_submission_uuid
66+
```
67+
68+
Response:
69+
70+
```json
71+
{
72+
…,
73+
"data": {
74+
"navn": "Jeppe",
75+
"telefon": "87654321"
76+
"dokumenter": {
77+
"some_document_id",
78+
"some_other_docuent_id"
79+
}
80+
}
81+
}
82+
```
83+
84+
Use the file endpoint from above to get information on a file,
85+
substituting `{file_id}` with the actual file id (`some_document_id`)
86+
from the previous request.
87+
88+
Request:
89+
90+
```sh
91+
> curl --silent --header 'api-key: …' https://127.0.0.1:8000/webform_rest/entity/file/some_document_id
92+
```
93+
94+
Response:
95+
96+
```json
97+
{
98+
…,
99+
"uri": [
100+
{
101+
"value": "private:…",
102+
"url": "/system/files/webform/some_webform_id/…"
103+
}
104+
],
105+
106+
}
107+
```
108+
109+
Finally, you can get the actual file by combining the base url
110+
with the url from above response:
111+
112+
```sh
113+
> curl --silent --header 'api-key: …' http://127.0.0.1:8000/system/files/webform/some_webform_id/…
114+
```
115+
116+
Response:
117+
The actual document.
118+
119+
## Custom access control
120+
121+
To limit access to webforms, you can specify a list of API users that are
122+
allowed to access a webform's data via the API.
123+
124+
Go to Settings > General > Third party settings > OS2Forms > REST API to specify
125+
which users can access a webform's data. **If no users are specified, all API
126+
users can access the data.**
127+
128+
### Technical details
129+
130+
The custom access check is implemented in an event subscriber listening on the
131+
`KernelEvents::REQUEST` event. See
132+
[EventSubscriber::onRequest](src/EventSubscriber/EventSubscriber.php) for
133+
details.
134+
135+
In order to make documents accessible for api users the Key auth `authentication_provider`
136+
service has been overwritten to be global. See [os2forms_rest_api.services](os2forms_rest_api.services.yml).

composer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "os2forms/os2forms_rest_api",
3+
"description": "OS2Forms REST API",
4+
"type": "drupal-module",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Mikkel Ricky",
9+
"email": "[email protected]"
10+
}
11+
],
12+
"minimum-stability": "dev",
13+
"prefer-stable": true,
14+
"require-dev": {
15+
"drupal/coder": "^8.3",
16+
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.1"
17+
},
18+
"scripts": {
19+
"coding-standards-check": "phpcs --standard=phpcs.xml.dist",
20+
"coding-standards-apply": "phpcbf --standard=phpcs.xml.dist"
21+
}
22+
}

os2forms_rest_api.info.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: 'OS2Form REST API'
2+
type: module
3+
description: 'OS2Form REST API'
4+
package: Web services
5+
core_version_requirement: ^9
6+
dependencies:
7+
- drupal:key_auth

os2forms_rest_api.module

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Contains hooks related to OS2Forms REST API module.
6+
*/
7+
8+
use Drupal\Core\Form\FormStateInterface;
9+
use Drupal\os2forms_rest_api\WebformHelper;
10+
11+
/**
12+
* Implements hook_webform_third_party_settings_form_alter().
13+
*
14+
* @see WebformHelper::webformThirdPartySettingsFormAlter()
15+
*/
16+
function os2forms_rest_api_webform_third_party_settings_form_alter(array &$form, FormStateInterface $form_state) {
17+
\Drupal::service(WebformHelper::class)->webformThirdPartySettingsFormAlter($form, $form_state);
18+
}
19+
20+
/**
21+
* Implements hook_file_download().
22+
*/
23+
function os2forms_rest_api_file_download(string $uri) {
24+
return \Drupal::service(WebformHelper::class)->fileDownload($uri);
25+
}

os2forms_rest_api.services.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
services:
2+
Drupal\os2forms_rest_api\WebformHelper:
3+
arguments:
4+
- '@entity_type.manager'
5+
- '@current_user'
6+
- '@key_auth.authentication.key_auth'
7+
8+
Drupal\os2forms_rest_api\EventSubscriber\EventSubscriber:
9+
arguments:
10+
- '@current_route_match'
11+
- '@current_user'
12+
- '@Drupal\os2forms_rest_api\WebformHelper'
13+
tags:
14+
- { name: 'event_subscriber' }
15+
16+
# Overwrite, adding global tag
17+
# @see https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic-services
18+
key_auth.authentication.key_auth:
19+
class: Drupal\key_auth\Authentication\Provider\KeyAuth
20+
arguments: [ '@key_auth' ]
21+
tags:
22+
- { name: authentication_provider, provider_id: 'key_auth', priority: 200, global: true }
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace Drupal\os2forms_rest_api\EventSubscriber;
4+
5+
use Drupal\Core\Routing\RouteMatchInterface;
6+
use Drupal\Core\Session\AccountProxyInterface;
7+
use Drupal\os2forms_rest_api\WebformHelper;
8+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9+
use Symfony\Component\HttpKernel\Event\KernelEvent;
10+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
11+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
12+
use Symfony\Component\HttpKernel\KernelEvents;
13+
14+
/**
15+
* Event subscriber.
16+
*/
17+
class EventSubscriber implements EventSubscriberInterface {
18+
/**
19+
* The route match.
20+
*
21+
* @var \Drupal\Core\Routing\RouteMatchInterface
22+
*/
23+
private RouteMatchInterface $routeMatch;
24+
25+
/**
26+
* The current user.
27+
*
28+
* @var \Drupal\Core\Session\AccountProxyInterface
29+
*/
30+
private AccountProxyInterface $currentUser;
31+
32+
/**
33+
* The webform helper.
34+
*
35+
* @var \Drupal\os2forms_rest_api\WebformHelper
36+
*/
37+
private WebformHelper $webformHelper;
38+
39+
/**
40+
* Constructor.
41+
*/
42+
public function __construct(RouteMatchInterface $routeMatch, AccountProxyInterface $currentUser, WebformHelper $webformHelper) {
43+
$this->routeMatch = $routeMatch;
44+
$this->currentUser = $currentUser;
45+
$this->webformHelper = $webformHelper;
46+
}
47+
48+
/**
49+
* On request handler.
50+
*
51+
* Check for user access to webform API resource.
52+
*/
53+
public function onRequest(KernelEvent $event) {
54+
$routeName = $this->routeMatch->getRouteName();
55+
$restRouteNames = [
56+
'rest.webform_rest_elements.GET',
57+
'rest.webform_rest_fields.GET',
58+
'rest.webform_rest_submission.GET',
59+
'rest.webform_rest_submission.PATCH',
60+
'rest.webform_rest_submit.POST',
61+
];
62+
if ($this->currentUser->isAnonymous() || !in_array($routeName, $restRouteNames, TRUE)) {
63+
return;
64+
}
65+
66+
$webformId = $this->routeMatch->getParameter('webform_id');
67+
$submissionUuid = $this->routeMatch->getParameter('uuid');
68+
69+
// Handle webform submission.
70+
if ('rest.webform_rest_submit.POST' === $routeName) {
71+
try {
72+
$content = json_decode($event->getRequest()->getContent(), TRUE, 512, JSON_THROW_ON_ERROR);
73+
$webformId = (string) $content['webform_id'];
74+
}
75+
catch (\JsonException $exception) {
76+
}
77+
}
78+
79+
if (!isset($webformId)) {
80+
throw new BadRequestHttpException('Cannot get webform id');
81+
}
82+
83+
$webform = $this->webformHelper->getWebform($webformId, $submissionUuid);
84+
85+
if (NULL === $webform) {
86+
return;
87+
}
88+
89+
$allowedUsers = $this->webformHelper->getAllowedUsers($webform);
90+
if (!empty($allowedUsers) && !isset($allowedUsers[$this->currentUser->id()])) {
91+
throw new AccessDeniedHttpException('Access denied');
92+
}
93+
}
94+
95+
/**
96+
* {@inheritdoc}
97+
*/
98+
public static function getSubscribedEvents() {
99+
return [
100+
// @see https://www.drupal.org/project/drupal/issues/2924954#comment-12350447
101+
KernelEvents::REQUEST => ['onRequest', 31],
102+
];
103+
}
104+
105+
}

0 commit comments

Comments
 (0)