9
9
use App \Entity \Tenant \InteractiveSlide ;
10
10
use App \Entity \Tenant \Slide ;
11
11
use App \Entity \User ;
12
- use App \Exceptions \InteractiveSlideException ;
13
12
use App \Service \InteractiveSlideService ;
14
13
use App \Service \KeyVaultService ;
15
14
use Psr \Cache \CacheItemInterface ;
16
15
use Psr \Cache \InvalidArgumentException ;
17
16
use Symfony \Bundle \SecurityBundle \Security ;
17
+ use Symfony \Component \HttpKernel \Exception \BadRequestHttpException ;
18
18
use Symfony \Component \HttpKernel \Exception \ConflictHttpException ;
19
19
use Symfony \Component \HttpKernel \Exception \ServiceUnavailableHttpException ;
20
20
use Symfony \Component \Security \Core \User \UserInterface ;
@@ -36,6 +36,7 @@ class InstantBook implements InteractiveSlideInterface
36
36
private const string SCOPE = 'https://graph.microsoft.com/.default ' ;
37
37
private const string GRANT_TYPE = 'password ' ;
38
38
private const string CACHE_PREFIX = 'MS-INSTANT-BOOK ' ;
39
+ private const string CACHE_ALLOWED_RESOURCES_PREFIX = 'INSTANT-BOOK-ALLOWED-RESOURCES- ' ;
39
40
private const string CACHE_KEY_TOKEN_PREFIX = self ::CACHE_PREFIX .'-TOKEN- ' ;
40
41
private const string CACHE_KEY_OPTIONS_PREFIX = self ::CACHE_PREFIX .'-OPTIONS- ' ;
41
42
private const string CACHE_PREFIX_SPAM_PROTECT_PREFIX = self ::CACHE_PREFIX .'-SPAM-PROTECT- ' ;
@@ -58,23 +59,29 @@ public function __construct(
58
59
59
60
public function getConfigOptions (): array
60
61
{
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.
61
64
return [
62
65
'tenantId ' => [
63
66
'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 ' ,
65
68
],
66
69
'clientId ' => [
67
70
'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 ' ,
69
72
],
70
73
'username ' => [
71
74
'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. ' ,
73
76
],
74
77
'password ' => [
75
78
'required ' => true ,
76
79
'description ' => 'The key in the KeyVault for the password of the user. ' ,
77
80
],
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
+ ],
78
85
];
79
86
}
80
87
@@ -83,7 +90,7 @@ public function performAction(UserInterface $user, Slide $slide, InteractionSlid
83
90
return match ($ interactionRequest ->action ) {
84
91
self ::ACTION_GET_QUICK_BOOK_OPTIONS => $ this ->getQuickBookOptions ($ slide , $ interactionRequest ),
85
92
self ::ACTION_QUICK_BOOK => $ this ->quickBook ($ slide , $ interactionRequest ),
86
- default => throw new InteractiveSlideException ('Action not allowed ' ),
93
+ default => throw new BadRequestHttpException ('Action not allowed ' ),
87
94
};
88
95
}
89
96
@@ -98,7 +105,7 @@ private function authenticate(array $configuration): array
98
105
$ password = $ this ->keyValueService ->getValue ($ configuration ['password ' ]);
99
106
100
107
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. ' );
102
109
}
103
110
104
111
$ url = self ::LOGIN_ENDPOINT .$ tenantId .self ::OAUTH_PATH ;
@@ -124,7 +131,7 @@ private function getToken(Tenant $tenant, InteractiveSlide $interactive): string
124
131
$ configuration = $ interactive ->getConfiguration ();
125
132
126
133
if (null === $ configuration ) {
127
- throw new \ Exception ( ' InteractiveNoConfiguration ' );
134
+ throw new BadRequestHttpException ( ' InteractiveSlide has no configuration ' );
128
135
}
129
136
130
137
return $ this ->interactiveSlideCache ->get (
@@ -161,13 +168,16 @@ function (CacheItemInterface $item) use ($slide, $resource, $interactionRequest)
161
168
$ interactive = $ this ->interactiveService ->getInteractiveSlide ($ tenant , $ interactionRequest ->implementationClass );
162
169
163
170
if (null === $ interactive ) {
164
- throw new \Exception ('InteractiveNotFound ' );
171
+ throw new \Exception ('InteractiveSlide not found ' );
165
172
}
166
173
174
+ // Optional limiting of available resources.
175
+ $ this ->checkPermission ($ interactive , $ resource );
176
+
167
177
$ feed = $ slide ->getFeed ();
168
178
169
179
if (null === $ feed ) {
170
- throw new \Exception ('Slide. feed not set. ' );
180
+ throw new \Exception ('Slide feed not set. ' );
171
181
}
172
182
173
183
if (!in_array ($ resource , $ feed ->getConfiguration ()['resources ' ] ?? [])) {
@@ -247,7 +257,7 @@ private function createEntry(string $resource, array $schedules, string $startFo
247
257
*/
248
258
private function quickBook (Slide $ slide , InteractionSlideRequest $ interactionRequest ): array
249
259
{
250
- $ resource = $ this ->getValueFromInterval ('resource ' , $ interactionRequest );
260
+ $ resource = ( string ) $ this ->getValueFromInterval ('resource ' , $ interactionRequest );
251
261
$ durationMinutes = $ this ->getValueFromInterval ('durationMinutes ' , $ interactionRequest );
252
262
253
263
$ now = new \DateTime ();
@@ -273,25 +283,28 @@ function (CacheItemInterface $item) use ($now): \DateTime {
273
283
$ interactive = $ this ->interactiveService ->getInteractiveSlide ($ tenant , $ interactionRequest ->implementationClass );
274
284
275
285
if (null === $ interactive ) {
276
- throw new \ Exception ( ' InteractiveNotFound ' );
286
+ throw new BadRequestHttpException ( ' Interactive not found ' );
277
287
}
278
288
289
+ // Optional limiting of available resources.
290
+ $ this ->checkPermission ($ interactive , $ resource );
291
+
279
292
$ feed = $ slide ->getFeed ();
280
293
281
294
if (null === $ feed ) {
282
- throw new \ Exception ('Slide. feed not set. ' );
295
+ throw new BadRequestHttpException ('Slide feed not set. ' );
283
296
}
284
297
285
298
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 ' );
287
300
}
288
301
289
302
$ token = $ this ->getToken ($ tenant , $ interactive );
290
303
291
304
$ configuration = $ interactive ->getConfiguration ();
292
305
293
306
if (null === $ configuration ) {
294
- throw new \ Exception ( ' InteractiveNoConfiguration ' );
307
+ throw new BadRequestHttpException ( ' Interactive no configuration ' );
295
308
}
296
309
297
310
$ username = $ this ->keyValueService ->getValue ($ configuration ['username ' ]);
@@ -411,13 +424,13 @@ private function getValueFromInterval(string $key, InteractionSlideRequest $inte
411
424
$ interval = $ interactionRequest ->data ['interval ' ] ?? null ;
412
425
413
426
if (null === $ interval ) {
414
- throw new \ Exception ('interval not set. ' );
427
+ throw new BadRequestHttpException ('interval not set. ' );
415
428
}
416
429
417
430
$ value = $ interval [$ key ] ?? null ;
418
431
419
432
if (null === $ value ) {
420
- throw new \ Exception ("interval.'. $ key.' not set. " );
433
+ throw new BadRequestHttpException ("interval.'. $ key.' not set. " );
421
434
}
422
435
423
436
return $ value ;
@@ -431,4 +444,51 @@ private function getHeaders(string $token): array
431
444
'Accept ' => 'application/json ' ,
432
445
];
433
446
}
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
+ }
434
494
}
0 commit comments