@webex/internal-plugin-calendar 3.0.0-bnr.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +6 -0
- package/babel.config.js +3 -0
- package/dist/calendar.decrypt.helper.js +8 -3
- package/dist/calendar.decrypt.helper.js.map +1 -1
- package/dist/calendar.encrypt.helper.js +11 -8
- package/dist/calendar.encrypt.helper.js.map +1 -1
- package/dist/calendar.js +236 -33
- package/dist/calendar.js.map +1 -1
- package/dist/collection.js +3 -4
- package/dist/collection.js.map +1 -1
- package/dist/config.js +1 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -12
- package/dist/constants.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/util.js +3 -4
- package/dist/util.js.map +1 -1
- package/jest.config.js +3 -0
- package/package.json +28 -13
- package/process +1 -0
- package/src/calendar.decrypt.helper.js +121 -0
- package/src/calendar.encrypt.helper.js +98 -0
- package/src/calendar.js +223 -17
- package/src/index.js +27 -3
- package/test/unit/spec/calendar.decrypt.helper.js +145 -0
- package/test/unit/spec/calendar.encrypt.helper.js +52 -0
- package/test/unit/spec/calendar.js +211 -47
- package/dist/internal-plugin-calendar.d.ts +0 -4
- package/dist/tsdoc-metadata.json +0 -11
- package/dist/types/calendar.d.ts +0 -2
- package/dist/types/collection.d.ts +0 -58
- package/dist/types/config.d.ts +0 -7
- package/dist/types/constants.d.ts +0 -6
- package/dist/types/index.d.ts +0 -1
- package/dist/types/util.d.ts +0 -15
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {isArray} from 'lodash';
|
|
2
|
+
|
|
3
|
+
const _encryptTextProp = (ctx, name, key, object) => {
|
|
4
|
+
if (!object[name]) {
|
|
5
|
+
return Promise.resolve();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return ctx.webex.internal.encryption
|
|
9
|
+
.encryptText(key.uri || key, object[name])
|
|
10
|
+
.then((ciphertext) => {
|
|
11
|
+
object[name] = ciphertext;
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const _encryptCalendarEventPayload = (data, ctx) => {
|
|
16
|
+
Object.assign(data, {encryptionKeyUrl: ctx.encryptionKeyUrl});
|
|
17
|
+
|
|
18
|
+
const encryptedAttendees = data.attendees
|
|
19
|
+
? data.attendees.map((attendee) =>
|
|
20
|
+
Promise.all([
|
|
21
|
+
_encryptTextProp(ctx, 'displayName', data.encryptionKeyUrl, attendee),
|
|
22
|
+
_encryptTextProp(ctx, 'email', data.encryptionKeyUrl, attendee),
|
|
23
|
+
])
|
|
24
|
+
)
|
|
25
|
+
: [];
|
|
26
|
+
|
|
27
|
+
return Promise.all(
|
|
28
|
+
[
|
|
29
|
+
_encryptTextProp(ctx, 'subject', data.encryptionKeyUrl, data),
|
|
30
|
+
_encryptTextProp(ctx, 'notes', data.encryptionKeyUrl, data),
|
|
31
|
+
_encryptTextProp(ctx, 'webexOptions', data.encryptionKeyUrl, data),
|
|
32
|
+
].concat([encryptedAttendees])
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const _encryptFreeBusyPayload = (data, ctx) => {
|
|
37
|
+
Object.assign(data, {encryptionKeyUrl: ctx.encryptionKeyUrl});
|
|
38
|
+
|
|
39
|
+
const promises = [];
|
|
40
|
+
if (data.emails && Array.isArray(data.emails)) {
|
|
41
|
+
data.emails.map((item, index) =>
|
|
42
|
+
promises.push(
|
|
43
|
+
ctx.webex.internal.encryption
|
|
44
|
+
.encryptText(data.encryptionKeyUrl, item)
|
|
45
|
+
.then((encryptText) => {
|
|
46
|
+
data.emails[index] = encryptText;
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Promise.all(promises);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const EncryptHelper = {
|
|
56
|
+
/**
|
|
57
|
+
* Encrypt create / update calendar event request payload
|
|
58
|
+
* @param {object} [ctx] context
|
|
59
|
+
* @param {object} [data] meeting payload data
|
|
60
|
+
* @returns {Promise} Resolves with encrypted request payload
|
|
61
|
+
* */
|
|
62
|
+
encryptCalendarEventRequest: (ctx, data) => {
|
|
63
|
+
if (ctx.encryptionKeyUrl) {
|
|
64
|
+
return _encryptCalendarEventPayload(data, ctx);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return ctx.webex.internal.encryption.kms.createUnboundKeys({count: 1}).then((keys) => {
|
|
68
|
+
const key = isArray(keys) ? keys[0] : keys;
|
|
69
|
+
ctx.encryptionKeyUrl = key.uri;
|
|
70
|
+
|
|
71
|
+
return _encryptCalendarEventPayload(data, ctx);
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt free-busy request payload, if request payload only includes the sensitive data, like email, need to encrypt these reqeust parameters, and playload includes encrypt url.
|
|
76
|
+
* Otherwise, don't encrypt playload and without encrypt url,Due to calendar serivce will vaild both encrypt url and sensitive that are both present. if not, will return 400 bad reqeust to caller.
|
|
77
|
+
* @param {object} [ctx] context
|
|
78
|
+
* @param {object} [data] free busy payload data
|
|
79
|
+
* @returns {Promise} Resolves with encrypted request payload
|
|
80
|
+
* */
|
|
81
|
+
encryptFreeBusyRequest: (ctx, data) => {
|
|
82
|
+
if (!data.emails || !Array.isArray(data.emails)) {
|
|
83
|
+
return Promise.resolve();
|
|
84
|
+
}
|
|
85
|
+
if (ctx.encryptionKeyUrl) {
|
|
86
|
+
return _encryptFreeBusyPayload(data, ctx);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return ctx.webex.internal.encryption.kms.createUnboundKeys({count: 1}).then((keys) => {
|
|
90
|
+
const key = isArray(keys) ? keys[0] : keys;
|
|
91
|
+
ctx.encryptionKeyUrl = key.uri;
|
|
92
|
+
|
|
93
|
+
return _encryptFreeBusyPayload(data, ctx);
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default EncryptHelper;
|
package/src/calendar.js
CHANGED
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* @instance
|
|
42
42
|
* @memberof Calendar
|
|
43
43
|
*/
|
|
44
|
-
|
|
45
|
-
import
|
|
44
|
+
import {isArray} from 'lodash';
|
|
45
|
+
import {base64} from '@webex/common';
|
|
46
46
|
import {WebexPlugin} from '@webex/webex-core';
|
|
47
47
|
|
|
48
48
|
import CalendarCollection from './collection';
|
|
@@ -54,6 +54,9 @@ import {
|
|
|
54
54
|
CALENDAR_UPDATED,
|
|
55
55
|
} from './constants';
|
|
56
56
|
|
|
57
|
+
import EncryptHelper from './calendar.encrypt.helper';
|
|
58
|
+
import DecryptHelper from './calendar.decrypt.helper';
|
|
59
|
+
|
|
57
60
|
const Calendar = WebexPlugin.extend({
|
|
58
61
|
namespace: 'Calendar',
|
|
59
62
|
|
|
@@ -65,6 +68,65 @@ const Calendar = WebexPlugin.extend({
|
|
|
65
68
|
*/
|
|
66
69
|
registered: false,
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Cache all rpc event request locally
|
|
73
|
+
* */
|
|
74
|
+
rpcEventRequests: [],
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Cache KMS encryptionKeyUrl
|
|
78
|
+
* */
|
|
79
|
+
encryptionKeyUrl: null,
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pre-fetch a KMS encryption key url to improve performance.
|
|
83
|
+
* Waits for the user to be authorized and skips if an unverified guest.
|
|
84
|
+
* @private
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
prefetchEncryptionKey() {
|
|
88
|
+
if (!this.webex.canAuthorize) {
|
|
89
|
+
this.listenToOnce(this.webex, 'change:canAuthorize', () => {
|
|
90
|
+
this.prefetchEncryptionKey();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.webex.credentials.isUnverifiedGuest) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.webex.internal.encryption.kms.createUnboundKeys({count: 1}).then((keys) => {
|
|
101
|
+
const key = isArray(keys) ? keys[0] : keys;
|
|
102
|
+
this.encryptionKeyUrl = key ? key.uri : null;
|
|
103
|
+
this.logger.info('calendar->bind a KMS encryption key url');
|
|
104
|
+
this.webex.internal.encryption
|
|
105
|
+
.getKey(this.encryptionKeyUrl, {onBehalfOf: null})
|
|
106
|
+
.then((retrievedKey) => {
|
|
107
|
+
this.encryptionKeyUrl = retrievedKey ? retrievedKey.uri : null;
|
|
108
|
+
this.logger.info('calendar->retrieve the KMS encryption key url and cache it');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* WebexPlugin initialize method. This triggers once Webex has completed its
|
|
115
|
+
* initialization workflow.
|
|
116
|
+
*
|
|
117
|
+
* If the plugin is meant to perform startup actions, place them in this
|
|
118
|
+
* `initialize()` method instead of the `constructor()` method.
|
|
119
|
+
* @private
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
initialize() {
|
|
123
|
+
// Used to perform actions after webex is fully qualified and ready for
|
|
124
|
+
// operation.
|
|
125
|
+
this.listenToOnce(this.webex, 'ready', () => {
|
|
126
|
+
this.prefetchEncryptionKey();
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
|
|
68
130
|
/**
|
|
69
131
|
* Explicitly sets up the calendar plugin by registering
|
|
70
132
|
* the device, connecting to mercury, and listening for calendar events.
|
|
@@ -148,6 +210,9 @@ const Calendar = WebexPlugin.extend({
|
|
|
148
210
|
this.webex.internal.mercury.on('event:calendar.meeting.delete', (envelope) => {
|
|
149
211
|
this._handleDelete(envelope.data);
|
|
150
212
|
});
|
|
213
|
+
this.webex.internal.mercury.on('event:calendar.free_busy', (envelope) => {
|
|
214
|
+
this._handleFreeBusy(envelope.data);
|
|
215
|
+
});
|
|
151
216
|
},
|
|
152
217
|
|
|
153
218
|
/**
|
|
@@ -161,6 +226,7 @@ const Calendar = WebexPlugin.extend({
|
|
|
161
226
|
this.webex.internal.mercury.off('event:calendar.meeting.update');
|
|
162
227
|
this.webex.internal.mercury.off('event:calendar.meeting.update.minimal');
|
|
163
228
|
this.webex.internal.mercury.off('event:calendar.meeting.delete');
|
|
229
|
+
this.webex.internal.mercury.off('event:calendar.free_busy');
|
|
164
230
|
},
|
|
165
231
|
|
|
166
232
|
/**
|
|
@@ -199,6 +265,32 @@ const Calendar = WebexPlugin.extend({
|
|
|
199
265
|
this.trigger(CALENDAR_DELETE, item);
|
|
200
266
|
},
|
|
201
267
|
|
|
268
|
+
/**
|
|
269
|
+
* handles free_busy events
|
|
270
|
+
* @param {Object} data
|
|
271
|
+
* @returns {undefined}
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
_handleFreeBusy(data) {
|
|
275
|
+
DecryptHelper.decryptFreeBusyResponse(this, data).then(() => {
|
|
276
|
+
let response = {};
|
|
277
|
+
if (data && data.calendarFreeBusyScheduleResponse) {
|
|
278
|
+
response = data.calendarFreeBusyScheduleResponse;
|
|
279
|
+
}
|
|
280
|
+
if (response && response.requestId && response.requestId in this.rpcEventRequests) {
|
|
281
|
+
this.logger.log(
|
|
282
|
+
`webex.internal.calendar - receive requests, requestId: ${response.requestId}`
|
|
283
|
+
);
|
|
284
|
+
delete response.encryptionKeyUrl;
|
|
285
|
+
const {resolve} = this.rpcEventRequests[response.requestId];
|
|
286
|
+
resolve(response);
|
|
287
|
+
delete this.rpcEventRequests[response.requestId];
|
|
288
|
+
} else {
|
|
289
|
+
this.logger.log('webex.internal.calendar - receive other requests.');
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
|
|
202
294
|
/**
|
|
203
295
|
* Retrieves a collection of calendars based on the request parameters
|
|
204
296
|
* Defaults to 1 day before and 7 days ahead
|
|
@@ -245,15 +337,26 @@ const Calendar = WebexPlugin.extend({
|
|
|
245
337
|
},
|
|
246
338
|
|
|
247
339
|
/**
|
|
248
|
-
* Retrieves an array of meeting participants for the meeting
|
|
249
|
-
* @param {String}
|
|
340
|
+
* Retrieves an array of meeting participants for the meeting participantsUrl
|
|
341
|
+
* @param {String} participantsUrl
|
|
250
342
|
* @returns {Promise} Resolves with an object of meeting participants
|
|
251
343
|
*/
|
|
252
|
-
getParticipants(
|
|
344
|
+
getParticipants(participantsUrl) {
|
|
253
345
|
return this.request({
|
|
254
346
|
method: 'GET',
|
|
255
|
-
|
|
256
|
-
|
|
347
|
+
uri: participantsUrl,
|
|
348
|
+
});
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* get meeting notes using notesUrl from meeting object.
|
|
353
|
+
* @param {String} notesUrl
|
|
354
|
+
* @returns {Promise} Resolves with an object of meeting notes
|
|
355
|
+
*/
|
|
356
|
+
getNotesByUrl(notesUrl) {
|
|
357
|
+
return this.request({
|
|
358
|
+
method: 'GET',
|
|
359
|
+
uri: notesUrl,
|
|
257
360
|
});
|
|
258
361
|
},
|
|
259
362
|
|
|
@@ -266,7 +369,7 @@ const Calendar = WebexPlugin.extend({
|
|
|
266
369
|
return this.request({
|
|
267
370
|
method: 'GET',
|
|
268
371
|
service: 'calendar',
|
|
269
|
-
resource: `calendarEvents/${
|
|
372
|
+
resource: `calendarEvents/${base64.encode(id)}/notes`,
|
|
270
373
|
});
|
|
271
374
|
},
|
|
272
375
|
|
|
@@ -292,17 +395,9 @@ const Calendar = WebexPlugin.extend({
|
|
|
292
395
|
const promises = [];
|
|
293
396
|
|
|
294
397
|
meetingObjects.forEach((meeting) => {
|
|
295
|
-
if (!meeting.encryptedNotes) {
|
|
296
|
-
promises.push(
|
|
297
|
-
this.getNotes(meeting.id).then((notesResponse) => {
|
|
298
|
-
meeting.encryptedNotes = notesResponse.body && notesResponse.body.encryptedNotes;
|
|
299
|
-
})
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
398
|
if (!meeting.encryptedParticipants) {
|
|
304
399
|
promises.push(
|
|
305
|
-
this.getParticipants(meeting.
|
|
400
|
+
this.getParticipants(meeting.participantsUrl).then((notesResponse) => {
|
|
306
401
|
meeting.encryptedParticipants = notesResponse.body.encryptedParticipants;
|
|
307
402
|
})
|
|
308
403
|
);
|
|
@@ -312,6 +407,117 @@ const Calendar = WebexPlugin.extend({
|
|
|
312
407
|
return Promise.all(promises).then(() => meetingObjects);
|
|
313
408
|
});
|
|
314
409
|
},
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Create calendar event
|
|
413
|
+
* @param {object} [data] meeting payload data
|
|
414
|
+
* @param {object} [query] the query parameters for specific usage
|
|
415
|
+
* @returns {Promise} Resolves with creating calendar event response
|
|
416
|
+
* */
|
|
417
|
+
createCalendarEvent(data, query) {
|
|
418
|
+
return EncryptHelper.encryptCalendarEventRequest(this, data).then(() =>
|
|
419
|
+
this.request({
|
|
420
|
+
method: 'POST',
|
|
421
|
+
service: 'calendar',
|
|
422
|
+
body: data,
|
|
423
|
+
resource: 'calendarEvents/sync',
|
|
424
|
+
qs: query || {},
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Update calendar event
|
|
431
|
+
* @param {string} [id] calendar event id
|
|
432
|
+
* @param {object} [data] meeting payload data
|
|
433
|
+
* @param {object} [query] the query parameters for specific usage
|
|
434
|
+
* @returns {Promise} Resolves with updating calendar event response
|
|
435
|
+
* */
|
|
436
|
+
updateCalendarEvent(id, data, query) {
|
|
437
|
+
return EncryptHelper.encryptCalendarEventRequest(this, data).then(() =>
|
|
438
|
+
this.request({
|
|
439
|
+
method: 'PATCH',
|
|
440
|
+
service: 'calendar',
|
|
441
|
+
body: data,
|
|
442
|
+
resource: `calendarEvents/${base64.encode(id)}/sync`,
|
|
443
|
+
qs: query || {},
|
|
444
|
+
})
|
|
445
|
+
);
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Delete calendar event
|
|
450
|
+
* @param {string} [id] calendar event id
|
|
451
|
+
* @param {object} [query] the query parameters for specific usage
|
|
452
|
+
* @returns {Promise} Resolves with deleting calendar event response
|
|
453
|
+
* */
|
|
454
|
+
deleteCalendarEvent(id, query) {
|
|
455
|
+
return this.request({
|
|
456
|
+
method: 'DELETE',
|
|
457
|
+
service: 'calendar',
|
|
458
|
+
resource: `calendarEvents/${base64.encode(id)}/sync`,
|
|
459
|
+
qs: query || {},
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* @typedef QuerySchedulerDataOptions
|
|
465
|
+
* @param {string} [siteName] it is site full url, must have. Example: ccctest.dmz.webex.com
|
|
466
|
+
* @param {string} [id] it is seriesOrOccurrenceId. If present, the series/occurrence meeting ID to fetch data for.
|
|
467
|
+
* Example: 040000008200E00074C5B7101A82E008000000004A99F11A0841D9010000000000000000100000009EE499D4A71C1A46B51494C70EC7BFE5
|
|
468
|
+
* @param {string} [clientMeetingId] If present, the client meeting UUID to fetch data for.
|
|
469
|
+
* Example: 7f318aa9-887c-6e94-802a-8dc8e6eb1a0a
|
|
470
|
+
* @param {string} [scheduleTemplateId] it template id.
|
|
471
|
+
* @param {string} [sessionTypeId] it session type id.
|
|
472
|
+
* @param {string} [organizerCIUserId] required in schedule-on-behalf case. It is the organizer's CI UUID.
|
|
473
|
+
* @param {boolean} [usmPreference]
|
|
474
|
+
* @param {string} [webexMeetingId] webex side meeting UUID
|
|
475
|
+
* @param {string} [eventId] event ID.
|
|
476
|
+
* @param {string} [icalUid] icalendar UUID.
|
|
477
|
+
* @param {string} [thirdPartyType] third part type, such as: Microsoft
|
|
478
|
+
*/
|
|
479
|
+
/**
|
|
480
|
+
* Get scheduler data from calendar service
|
|
481
|
+
* @param {QuerySchedulerDataOptions} [query] the command parameters for fetching scheduler data.
|
|
482
|
+
* @returns {Promise} Resolves with a decrypted scheduler data
|
|
483
|
+
* */
|
|
484
|
+
getSchedulerData(query) {
|
|
485
|
+
return this.request({
|
|
486
|
+
method: 'GET',
|
|
487
|
+
service: 'calendar',
|
|
488
|
+
resource: 'schedulerData',
|
|
489
|
+
qs: query || {},
|
|
490
|
+
}).then((response) => {
|
|
491
|
+
return DecryptHelper.decryptSchedulerDataResponse(this, response.body).then(() => response);
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get free busy status from calendar service
|
|
497
|
+
* @param {Object} [data] the command parameters for fetching free busy status.
|
|
498
|
+
* @param {object} [query] the query parameters for specific usage
|
|
499
|
+
* @returns {Promise} Resolves with a decrypted response
|
|
500
|
+
* */
|
|
501
|
+
getFreeBusy(data, query) {
|
|
502
|
+
return EncryptHelper.encryptFreeBusyRequest(this, data)
|
|
503
|
+
.then(() => {
|
|
504
|
+
return this.request({
|
|
505
|
+
method: 'POST',
|
|
506
|
+
service: 'calendar',
|
|
507
|
+
body: data,
|
|
508
|
+
resource: 'freebusy',
|
|
509
|
+
qs: query || {},
|
|
510
|
+
});
|
|
511
|
+
})
|
|
512
|
+
.then(() => {
|
|
513
|
+
return new Promise((resolve, reject) => {
|
|
514
|
+
this.rpcEventRequests[data.requestId] = {resolve, reject};
|
|
515
|
+
});
|
|
516
|
+
})
|
|
517
|
+
.catch((error) => {
|
|
518
|
+
throw error;
|
|
519
|
+
});
|
|
520
|
+
},
|
|
315
521
|
});
|
|
316
522
|
|
|
317
523
|
export default Calendar;
|
package/src/index.js
CHANGED
|
@@ -20,7 +20,15 @@ registerInternalPlugin('calendar', Calendar, {
|
|
|
20
20
|
name: 'transformMeetingNotes',
|
|
21
21
|
direction: 'inbound',
|
|
22
22
|
test(ctx, response) {
|
|
23
|
-
return Promise.resolve(
|
|
23
|
+
return Promise.resolve(
|
|
24
|
+
has(response, 'body.encryptedNotes') &&
|
|
25
|
+
!(
|
|
26
|
+
response.options &&
|
|
27
|
+
response.options.service === 'calendar' &&
|
|
28
|
+
response.options.method === 'GET' &&
|
|
29
|
+
response.options.resource === 'schedulerData'
|
|
30
|
+
)
|
|
31
|
+
);
|
|
24
32
|
},
|
|
25
33
|
extract(response) {
|
|
26
34
|
return Promise.resolve(response.body);
|
|
@@ -30,7 +38,15 @@ registerInternalPlugin('calendar', Calendar, {
|
|
|
30
38
|
name: 'transformMeetingParticipants',
|
|
31
39
|
direction: 'inbound',
|
|
32
40
|
test(ctx, response) {
|
|
33
|
-
return Promise.resolve(
|
|
41
|
+
return Promise.resolve(
|
|
42
|
+
has(response, 'body.encryptedParticipants') &&
|
|
43
|
+
!(
|
|
44
|
+
response.options &&
|
|
45
|
+
response.options.service === 'calendar' &&
|
|
46
|
+
response.options.method === 'GET' &&
|
|
47
|
+
response.options.resource === 'schedulerData'
|
|
48
|
+
)
|
|
49
|
+
);
|
|
34
50
|
},
|
|
35
51
|
extract(response) {
|
|
36
52
|
return Promise.resolve(response.body);
|
|
@@ -50,7 +66,15 @@ registerInternalPlugin('calendar', Calendar, {
|
|
|
50
66
|
name: 'transformMeeting',
|
|
51
67
|
direction: 'inbound',
|
|
52
68
|
test(ctx, response) {
|
|
53
|
-
return Promise.resolve(
|
|
69
|
+
return Promise.resolve(
|
|
70
|
+
has(response, 'body.seriesId') &&
|
|
71
|
+
!(
|
|
72
|
+
response.options &&
|
|
73
|
+
response.options.service === 'calendar' &&
|
|
74
|
+
response.options.method === 'GET' &&
|
|
75
|
+
response.options.resource === 'schedulerData'
|
|
76
|
+
)
|
|
77
|
+
);
|
|
54
78
|
},
|
|
55
79
|
extract(response) {
|
|
56
80
|
return Promise.resolve(response.body);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import sinon from "sinon";
|
|
2
|
+
import { expect } from "@webex/test-helper-chai";
|
|
3
|
+
import DecryptHelper from "@webex/internal-plugin-calendar/src/calendar.decrypt.helper";
|
|
4
|
+
|
|
5
|
+
describe("internal-plugin-calendar", () => {
|
|
6
|
+
describe("DecryptHelper", () => {
|
|
7
|
+
let ctx;
|
|
8
|
+
let encryptedSchedulerData;
|
|
9
|
+
let encryptedFreeBusyData;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
ctx = {
|
|
13
|
+
webex: {
|
|
14
|
+
internal: {
|
|
15
|
+
encryption: {
|
|
16
|
+
decryptText: sinon.stub()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
encryptedSchedulerData = {
|
|
23
|
+
encryptionKeyUrl: "http://example.com/encryption-key",
|
|
24
|
+
encryptedSubject: "some encrypted subject",
|
|
25
|
+
encryptedLocation: "some encrypted location",
|
|
26
|
+
encryptedNotes: "some encrypted notes",
|
|
27
|
+
encryptedParticipants: [
|
|
28
|
+
{
|
|
29
|
+
encryptedEmailAddress: "some encrypted email address",
|
|
30
|
+
encryptedName: "some encrypted name"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
encryptedEmailAddress: "another encrypted email address",
|
|
34
|
+
encryptedName: "another encrypted name"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
encryptedScheduleFor: {
|
|
38
|
+
"user1@example.com": {
|
|
39
|
+
encryptedEmail: "some encrypted email address",
|
|
40
|
+
encryptedDisplayName: "some encrypted display name"
|
|
41
|
+
},
|
|
42
|
+
"user2@example.com": {
|
|
43
|
+
encryptedEmail: "another encrypted email address",
|
|
44
|
+
encryptedDisplayName: "another encrypted display name"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
meetingJoinInfo: {
|
|
48
|
+
meetingJoinURI: "some encrypted meeting join URI",
|
|
49
|
+
meetingJoinURL: "some encrypted meeting join URL"
|
|
50
|
+
},
|
|
51
|
+
encryptedOrganizer: {
|
|
52
|
+
encryptedEmailAddress: "some encrypted email address",
|
|
53
|
+
encryptedName: "some encrypted name"
|
|
54
|
+
},
|
|
55
|
+
webexURI: "some encrypted webex URI",
|
|
56
|
+
webexURL: "some encrypted webex URL",
|
|
57
|
+
spaceMeetURL: "some encrypted space meet URL",
|
|
58
|
+
spaceURI: "some encrypted space URI",
|
|
59
|
+
spaceURL: "some encrypted space URL"
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
encryptedFreeBusyData = {
|
|
63
|
+
calendarFreeBusyScheduleResponse: {
|
|
64
|
+
encryptionKeyUrl: "https://encryption.key/url",
|
|
65
|
+
calendarFreeBusyItems: [
|
|
66
|
+
{
|
|
67
|
+
email: "encrypted-email"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
sinon.restore();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("#decryptSchedulerDataResponse - should resolve with undefined if data is undefined", async () => {
|
|
79
|
+
const decryptedData = await DecryptHelper.decryptSchedulerDataResponse(ctx, undefined);
|
|
80
|
+
expect(decryptedData).to.be.undefined;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("#decryptSchedulerDataResponse - should resolve with undefined if data.encryptionKeyUrl is undefined", async () => {
|
|
84
|
+
encryptedSchedulerData.encryptionKeyUrl = undefined;
|
|
85
|
+
const decryptedData = await DecryptHelper.decryptSchedulerDataResponse(ctx, encryptedSchedulerData);
|
|
86
|
+
expect(decryptedData).to.be.undefined;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("#decryptSchedulerDataResponse - should replace encrypted data with decrypted data in response", () => {
|
|
90
|
+
it("should decrypt scheduler data response correctly", async () => {
|
|
91
|
+
// Stub the decryption method to return the plaintext value.
|
|
92
|
+
const expectedCiphertext = "some decrypted text for testing";
|
|
93
|
+
|
|
94
|
+
ctx.webex.internal.encryption.decryptText.callsFake((key, ciphertext) => Promise.resolve(expectedCiphertext));
|
|
95
|
+
|
|
96
|
+
// Decrypt the data.
|
|
97
|
+
await DecryptHelper.decryptSchedulerDataResponse(ctx, encryptedSchedulerData);
|
|
98
|
+
|
|
99
|
+
// Check that all encrypted properties were decrypted correctly.
|
|
100
|
+
expect(encryptedSchedulerData.encryptedSubject).to.equal(expectedCiphertext);
|
|
101
|
+
expect(encryptedSchedulerData.encryptedLocation).to.equal(expectedCiphertext);
|
|
102
|
+
expect(encryptedSchedulerData.encryptedNotes).to.equal(expectedCiphertext);
|
|
103
|
+
expect(encryptedSchedulerData.encryptedParticipants[0].encryptedEmailAddress).to.equal(expectedCiphertext);
|
|
104
|
+
expect(encryptedSchedulerData.encryptedParticipants[0].encryptedName).to.equal(expectedCiphertext);
|
|
105
|
+
expect(encryptedSchedulerData.encryptedScheduleFor["user1@example.com"].encryptedEmail).to.equal(expectedCiphertext);
|
|
106
|
+
expect(encryptedSchedulerData.encryptedScheduleFor["user1@example.com"].encryptedDisplayName).to.equal(expectedCiphertext);
|
|
107
|
+
expect(encryptedSchedulerData.meetingJoinInfo.meetingJoinURI).to.equal(expectedCiphertext);
|
|
108
|
+
expect(encryptedSchedulerData.meetingJoinInfo.meetingJoinURL).to.equal(expectedCiphertext);
|
|
109
|
+
|
|
110
|
+
expect(encryptedSchedulerData.encryptedOrganizer.encryptedEmailAddress).to.equal(expectedCiphertext);
|
|
111
|
+
expect(encryptedSchedulerData.encryptedOrganizer.encryptedName).to.equal(expectedCiphertext);
|
|
112
|
+
expect(encryptedSchedulerData.webexURI).to.equal(expectedCiphertext);
|
|
113
|
+
expect(encryptedSchedulerData.webexURL).to.equal(expectedCiphertext);
|
|
114
|
+
expect(encryptedSchedulerData.spaceMeetURL).to.equal(expectedCiphertext);
|
|
115
|
+
expect(encryptedSchedulerData.spaceURI).to.equal(expectedCiphertext);
|
|
116
|
+
expect(encryptedSchedulerData.spaceURL).to.equal(expectedCiphertext);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("#decryptFreeBusyResponse - should resolve with undefined if data is undefined", async () => {
|
|
121
|
+
const decryptedData = await DecryptHelper.decryptFreeBusyResponse(ctx, undefined);
|
|
122
|
+
expect(decryptedData).to.be.undefined;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("#decryptFreeBusyResponse - should resolve with undefined if data.calendarFreeBusyScheduleResponse is undefined", async () => {
|
|
126
|
+
const decryptedData = await DecryptHelper.decryptFreeBusyResponse(ctx, {});
|
|
127
|
+
expect(decryptedData).to.be.undefined;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("#decryptFreeBusyResponse - should resolve with undefined if data.calendarFreeBusyScheduleResponse.encryptionKeyUrl is undefined", async () => {
|
|
131
|
+
encryptedFreeBusyData.calendarFreeBusyScheduleResponse.encryptionKeyUrl = undefined;
|
|
132
|
+
const decryptedData = await DecryptHelper.decryptFreeBusyResponse(ctx, encryptedFreeBusyData);
|
|
133
|
+
expect(decryptedData).to.be.undefined;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("#decryptFreeBusyResponse - should replace encrypted email with decrypted email in calendarFreeBusyItems", async () => {
|
|
137
|
+
const decryptTextStub = ctx.webex.internal.encryption.decryptText;
|
|
138
|
+
decryptTextStub.resolves("decrypted-email");
|
|
139
|
+
|
|
140
|
+
await DecryptHelper.decryptFreeBusyResponse(ctx, encryptedFreeBusyData);
|
|
141
|
+
|
|
142
|
+
expect(encryptedFreeBusyData.calendarFreeBusyScheduleResponse.calendarFreeBusyItems[0].email).to.equal("decrypted-email");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import {expect} from '@webex/test-helper-chai';
|
|
3
|
+
import EncryptHelper from '@webex/internal-plugin-calendar/src/calendar.encrypt.helper';
|
|
4
|
+
describe('internal-plugin-calendar', () => {
|
|
5
|
+
describe('encryptHelper', () => {
|
|
6
|
+
let ctx;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
ctx = {
|
|
9
|
+
encryptionKeyUrl: 'http://example.com/encryption-key',
|
|
10
|
+
webex: {
|
|
11
|
+
internal: {
|
|
12
|
+
encryption: {
|
|
13
|
+
encryptText: sinon.stub(),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
sinon.restore();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('#encryptFreebusyRequestData with emails should ', async () => {
|
|
25
|
+
const freeBusyRequest = {
|
|
26
|
+
start: '20230712T10:20:00Z',
|
|
27
|
+
end: '20230712T20:20:00Z',
|
|
28
|
+
emails: ['test@webex.com'],
|
|
29
|
+
};
|
|
30
|
+
const expectedCiphertext = 'some encrpty data';
|
|
31
|
+
ctx.webex.internal.encryption.encryptText.callsFake((key, ciphertext) =>
|
|
32
|
+
Promise.resolve(expectedCiphertext)
|
|
33
|
+
);
|
|
34
|
+
await EncryptHelper.encryptFreeBusyRequest(ctx, freeBusyRequest);
|
|
35
|
+
expect(freeBusyRequest.emails[0]).to.be.equal(expectedCiphertext);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('#encryptFreebusyRequestData not include emails, but include ids- should b', async () => {
|
|
39
|
+
const freeBusyRequest = {
|
|
40
|
+
start: '20230712T10:20:00Z',
|
|
41
|
+
end: '20230712T20:20:00Z',
|
|
42
|
+
userIds: ['91aee1231'],
|
|
43
|
+
};
|
|
44
|
+
const expectedCiphertext = '91aee1231';
|
|
45
|
+
ctx.webex.internal.encryption.encryptText.callsFake((key, ciphertext) =>
|
|
46
|
+
Promise.resolve(expectedCiphertext)
|
|
47
|
+
);
|
|
48
|
+
await EncryptHelper.encryptFreeBusyRequest(ctx, freeBusyRequest);
|
|
49
|
+
expect(freeBusyRequest.userIds[0]).to.equal(expectedCiphertext);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|