99use App \Entity \Tenant \InteractiveSlide ;
1010use App \Entity \Tenant \Slide ;
1111use App \Entity \User ;
12- use App \Exceptions \InteractiveSlideException ;
1312use App \Service \InteractiveSlideService ;
1413use App \Service \KeyVaultService ;
1514use Psr \Cache \CacheItemInterface ;
1615use Psr \Cache \InvalidArgumentException ;
1716use Symfony \Bundle \SecurityBundle \Security ;
17+ use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
1818use Symfony \Component \HttpKernel \Exception \ConflictHttpException ;
1919use Symfony \Component \HttpKernel \Exception \ServiceUnavailableHttpException ;
2020use 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