Skip to content

Commit 8727241

Browse files
authored
Merge pull request #245 from os2display/feature/4680-instant-booking-optional-allowed-resources-list
Added resource endpoint for limiting access to instant book interactive slide
2 parents 4e32570 + 0ec4ee4 commit 8727241

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
- [#245](https://github.com/os2display/display-api-service/pull/245)
8+
- Added resource endpoint for limiting access to instant book interactive slide.
79
- [#243](https://github.com/os2display/display-api-service/pull/243)
810
- Changed resource name in calendar api feed type resource selector.
911
- [#242](https://github.com/os2display/display-api-service/pull/242)

infrastructure/itkdev/display-api-service/etc/confd/templates/env.local.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS={{ getenv "APP_EVENTDATABASE_API_V2_CA
6767

6868
TRACK_SCREEN_INFO={{ getenv "APP_TRACK_SCREEN_INFO" "false" }}
6969
TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS={{ getenv "APP_TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS" "300" }}
70+
71+
APP_KEY_VAULT_JSON={{ getenv "APP_KEY_VAULT_JSON" "{}" }}

infrastructure/os2display/display-api-service/etc/confd/templates/env.local.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS={{ getenv "APP_EVENTDATABASE_API_V2_CA
6767

6868
TRACK_SCREEN_INFO={{ getenv "APP_TRACK_SCREEN_INFO" "false" }}
6969
TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS={{ getenv "APP_TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS" "300" }}
70+
71+
APP_KEY_VAULT_JSON={{ getenv "APP_KEY_VAULT_JSON" "{}" }}

src/InteractiveSlide/InstantBook.php

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
use App\Entity\Tenant\InteractiveSlide;
1010
use App\Entity\Tenant\Slide;
1111
use App\Entity\User;
12-
use App\Exceptions\InteractiveSlideException;
1312
use App\Service\InteractiveSlideService;
1413
use App\Service\KeyVaultService;
1514
use Psr\Cache\CacheItemInterface;
1615
use Psr\Cache\InvalidArgumentException;
1716
use Symfony\Bundle\SecurityBundle\Security;
17+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1818
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
1919
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
2020
use Symfony\Component\Security\Core\User\UserInterface;
@@ -36,6 +36,7 @@ class InstantBook implements InteractiveSlideInterface
3636
private const string SCOPE = 'https://graph.microsoft.com/.default';
3737
private const string GRANT_TYPE = 'password';
3838
private const string CACHE_PREFIX = 'MS-INSTANT-BOOK';
39+
private const string CACHE_ALLOWED_RESOURCES_PREFIX = 'INSTANT-BOOK-ALLOWED-RESOURCES-';
3940
private const string CACHE_KEY_TOKEN_PREFIX = self::CACHE_PREFIX.'-TOKEN-';
4041
private const string CACHE_KEY_OPTIONS_PREFIX = self::CACHE_PREFIX.'-OPTIONS-';
4142
private const string CACHE_PREFIX_SPAM_PROTECT_PREFIX = self::CACHE_PREFIX.'-SPAM-PROTECT-';
@@ -58,23 +59,29 @@ public function __construct(
5859

5960
public function getConfigOptions(): array
6061
{
62+
// All secrets are retrieved from the KeyVault. Therefore, the input for the different configurations are the
63+
// keys into the KeyVault where the values can be retrieved.
6164
return [
6265
'tenantId' => [
6366
'required' => true,
64-
'description' => 'The key in the KeyVault for the tenant id of the App',
67+
'description' => 'The key in the KeyVault for the tenant id of the Microsoft Graph App',
6568
],
6669
'clientId' => [
6770
'required' => true,
68-
'description' => 'The key in the KeyVault for the client id of the App',
71+
'description' => 'The key in the KeyVault for the client id of the Microsoft Graph App',
6972
],
7073
'username' => [
7174
'required' => true,
72-
'description' => 'The key in the KeyVault for the Microsoft Graph username that should perform the action.',
75+
'description' => 'The key in the KeyVault for the username that should perform the action.',
7376
],
7477
'password' => [
7578
'required' => true,
7679
'description' => 'The key in the KeyVault for the password of the user.',
7780
],
81+
'resourceEndpoint' => [
82+
'required' => false,
83+
'description' => 'The key in the KeyVault for the resources endpoint. This should supply a json list of resources that can be booked. The resources should have ResourceMail and allowInstantBooking ("True"/"False") properties set.',
84+
],
7885
];
7986
}
8087

@@ -83,7 +90,7 @@ public function performAction(UserInterface $user, Slide $slide, InteractionSlid
8390
return match ($interactionRequest->action) {
8491
self::ACTION_GET_QUICK_BOOK_OPTIONS => $this->getQuickBookOptions($slide, $interactionRequest),
8592
self::ACTION_QUICK_BOOK => $this->quickBook($slide, $interactionRequest),
86-
default => throw new InteractiveSlideException('Action not allowed'),
93+
default => throw new BadRequestHttpException('Action not allowed'),
8794
};
8895
}
8996

@@ -98,7 +105,7 @@ private function authenticate(array $configuration): array
98105
$password = $this->keyValueService->getValue($configuration['password']);
99106

100107
if (4 !== count(array_filter([$tenantId, $clientId, $username, $password]))) {
101-
throw new \Exception('tenantId, clientId, username, password must all be set.');
108+
throw new BadRequestHttpException('tenantId, clientId, username, password must all be set.');
102109
}
103110

104111
$url = self::LOGIN_ENDPOINT.$tenantId.self::OAUTH_PATH;
@@ -124,7 +131,7 @@ private function getToken(Tenant $tenant, InteractiveSlide $interactive): string
124131
$configuration = $interactive->getConfiguration();
125132

126133
if (null === $configuration) {
127-
throw new \Exception('InteractiveNoConfiguration');
134+
throw new BadRequestHttpException('InteractiveSlide has no configuration');
128135
}
129136

130137
return $this->interactiveSlideCache->get(
@@ -161,13 +168,16 @@ function (CacheItemInterface $item) use ($slide, $resource, $interactionRequest)
161168
$interactive = $this->interactiveService->getInteractiveSlide($tenant, $interactionRequest->implementationClass);
162169

163170
if (null === $interactive) {
164-
throw new \Exception('InteractiveNotFound');
171+
throw new \Exception('InteractiveSlide not found');
165172
}
166173

174+
// Optional limiting of available resources.
175+
$this->checkPermission($interactive, $resource);
176+
167177
$feed = $slide->getFeed();
168178

169179
if (null === $feed) {
170-
throw new \Exception('Slide.feed not set.');
180+
throw new \Exception('Slide feed not set.');
171181
}
172182

173183
if (!in_array($resource, $feed->getConfiguration()['resources'] ?? [])) {
@@ -247,7 +257,7 @@ private function createEntry(string $resource, array $schedules, string $startFo
247257
*/
248258
private function quickBook(Slide $slide, InteractionSlideRequest $interactionRequest): array
249259
{
250-
$resource = $this->getValueFromInterval('resource', $interactionRequest);
260+
$resource = (string) $this->getValueFromInterval('resource', $interactionRequest);
251261
$durationMinutes = $this->getValueFromInterval('durationMinutes', $interactionRequest);
252262

253263
$now = new \DateTime();
@@ -273,25 +283,28 @@ function (CacheItemInterface $item) use ($now): \DateTime {
273283
$interactive = $this->interactiveService->getInteractiveSlide($tenant, $interactionRequest->implementationClass);
274284

275285
if (null === $interactive) {
276-
throw new \Exception('InteractiveNotFound');
286+
throw new BadRequestHttpException('Interactive not found');
277287
}
278288

289+
// Optional limiting of available resources.
290+
$this->checkPermission($interactive, $resource);
291+
279292
$feed = $slide->getFeed();
280293

281294
if (null === $feed) {
282-
throw new \Exception('Slide.feed not set.');
295+
throw new BadRequestHttpException('Slide feed not set.');
283296
}
284297

285298
if (!in_array($resource, $feed->getConfiguration()['resources'] ?? [])) {
286-
throw new \Exception('Resource not in feed resources');
299+
throw new BadRequestHttpException('Resource not in feed resources');
287300
}
288301

289302
$token = $this->getToken($tenant, $interactive);
290303

291304
$configuration = $interactive->getConfiguration();
292305

293306
if (null === $configuration) {
294-
throw new \Exception('InteractiveNoConfiguration');
307+
throw new BadRequestHttpException('Interactive no configuration');
295308
}
296309

297310
$username = $this->keyValueService->getValue($configuration['username']);
@@ -411,13 +424,13 @@ private function getValueFromInterval(string $key, InteractionSlideRequest $inte
411424
$interval = $interactionRequest->data['interval'] ?? null;
412425

413426
if (null === $interval) {
414-
throw new \Exception('interval not set.');
427+
throw new BadRequestHttpException('interval not set.');
415428
}
416429

417430
$value = $interval[$key] ?? null;
418431

419432
if (null === $value) {
420-
throw new \Exception("interval.'.$key.' not set.");
433+
throw new BadRequestHttpException("interval.'.$key.' not set.");
421434
}
422435

423436
return $value;
@@ -431,4 +444,51 @@ private function getHeaders(string $token): array
431444
'Accept' => 'application/json',
432445
];
433446
}
447+
448+
private function checkPermission(InteractiveSlide $interactive, string $resource): void
449+
{
450+
$configuration = $interactive->getConfiguration();
451+
// Optional limiting of available resources.
452+
if (null !== $configuration && !empty($configuration['resourceEndpoint'])) {
453+
$allowedResources = $this->getAllowedResources($interactive);
454+
455+
if (!in_array($resource, $allowedResources)) {
456+
throw new \Exception('Not allowed');
457+
}
458+
}
459+
}
460+
461+
private function getAllowedResources(InteractiveSlide $interactive): array
462+
{
463+
return $this->interactiveSlideCache->get(self::CACHE_ALLOWED_RESOURCES_PREFIX.$interactive->getId(), function (CacheItemInterface $item) use ($interactive) {
464+
$item->expiresAfter(60 * 60);
465+
466+
$configuration = $interactive->getConfiguration();
467+
468+
$key = $configuration['resourceEndpoint'] ?? null;
469+
470+
if (null === $key) {
471+
throw new \Exception('resourceEndpoint not set');
472+
}
473+
474+
$resourceEndpoint = $this->keyValueService->getValue($key);
475+
476+
if (null === $resourceEndpoint) {
477+
throw new \Exception('resourceEndpoint value not set');
478+
}
479+
480+
$response = $this->client->request('GET', $resourceEndpoint);
481+
$content = $response->toArray();
482+
483+
$allowedResources = [];
484+
485+
foreach ($content as $resource) {
486+
if ('True' === $resource['allowInstantBooking']) {
487+
$allowedResources[] = $resource['ResourceMail'];
488+
}
489+
}
490+
491+
return $allowedResources;
492+
});
493+
}
434494
}

0 commit comments

Comments
 (0)