@webex/internal-plugin-conversation 2.59.2 → 2.59.3-next.1

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.
Files changed (48) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +47 -47
  3. package/babel.config.js +3 -3
  4. package/dist/activities.js +4 -4
  5. package/dist/activities.js.map +1 -1
  6. package/dist/activity-thread-ordering.js +34 -34
  7. package/dist/activity-thread-ordering.js.map +1 -1
  8. package/dist/config.js +12 -12
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js.map +1 -1
  11. package/dist/conversation.js +474 -474
  12. package/dist/conversation.js.map +1 -1
  13. package/dist/convo-error.js +4 -4
  14. package/dist/convo-error.js.map +1 -1
  15. package/dist/decryption-transforms.js +155 -155
  16. package/dist/decryption-transforms.js.map +1 -1
  17. package/dist/encryption-transforms.js.map +1 -1
  18. package/dist/index.js +2 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/share-activity.js +57 -57
  21. package/dist/share-activity.js.map +1 -1
  22. package/dist/to-array.js +7 -7
  23. package/dist/to-array.js.map +1 -1
  24. package/jest.config.js +3 -3
  25. package/package.json +21 -20
  26. package/process +1 -1
  27. package/src/activities.js +157 -157
  28. package/src/activity-thread-ordering.js +283 -283
  29. package/src/activity-threading.md +282 -282
  30. package/src/config.js +37 -37
  31. package/src/constants.js +3 -3
  32. package/src/conversation.js +2535 -2535
  33. package/src/convo-error.js +15 -15
  34. package/src/decryption-transforms.js +541 -541
  35. package/src/encryption-transforms.js +345 -345
  36. package/src/index.js +327 -327
  37. package/src/share-activity.js +436 -436
  38. package/src/to-array.js +29 -29
  39. package/test/integration/spec/create.js +290 -290
  40. package/test/integration/spec/encryption.js +333 -333
  41. package/test/integration/spec/get.js +1255 -1255
  42. package/test/integration/spec/mercury.js +94 -94
  43. package/test/integration/spec/share.js +537 -537
  44. package/test/integration/spec/verbs.js +1041 -1041
  45. package/test/unit/spec/conversation.js +823 -823
  46. package/test/unit/spec/decrypt-transforms.js +460 -460
  47. package/test/unit/spec/encryption-transforms.js +93 -93
  48. package/test/unit/spec/share-activity.js +178 -178
@@ -1,541 +1,541 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {camelCase, capitalize, curry} from 'lodash';
6
-
7
- import toArray from './to-array';
8
-
9
- const decryptTextProp = curry((name, ctx, key, object) =>
10
- ctx.transform('decryptTextProp', name, key, object)
11
- );
12
-
13
- // eslint-disable-next-line import/prefer-default-export
14
- export const transforms = toArray('inbound', {
15
- /**
16
- * This function is used recursively to decrypt various properties on conversations, activities, etc
17
- * @param {Object} ctx An object containing a webex instance and a transform
18
- * @param {String} key KMS encryption key url
19
- * @param {Object} object Generic object that you want to decrypt some property on based on the type
20
- * @returns {Promise} Returns a transform promise
21
- */
22
- decryptObject(ctx, key, object) {
23
- if (!object) {
24
- object = key; // eslint-disable-line no-param-reassign
25
- key = undefined; // eslint-disable-line no-param-reassign
26
- }
27
-
28
- if (!object) {
29
- return Promise.resolve();
30
- }
31
-
32
- if (!object.objectType) {
33
- return Promise.resolve();
34
- }
35
-
36
- if (!key && object.encryptionKeyUrl) {
37
- key = object.encryptionKeyUrl; // eslint-disable-line no-param-reassign
38
- }
39
-
40
- // Transcoded content was not showing up on the activities since the
41
- // decryptFile was not being called. Calling decryptFile for
42
- // transcodedContent fixes the issue.
43
- if (object.objectType === 'transcodedContent') {
44
- return Promise.all(object.files.items.map((item) => ctx.transform('decryptFile', key, item)));
45
- }
46
-
47
- return ctx.transform(`decrypt${capitalize(object.objectType)}`, key, object);
48
- },
49
-
50
- /**
51
- * Decrypt an individual submit object from a cardAction activity
52
- * (object.objectType === 'submit')
53
- * @param {Object} ctx An object containing a webex instance and a transform
54
- * @param {String} key KMS key
55
- * @param {Object} object An instance of a Webex submit object
56
- * these objects are returned when a user clicks on a Action.Submit button in a card
57
- * @returns {Promise} Returns a ctx.transform promise
58
- */
59
- decryptSubmit(ctx, key, object) {
60
- if (!object.inputs) {
61
- return Promise.resolve();
62
- }
63
- const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
64
-
65
- return ctx
66
- .transform('decryptPropCardItem', 0, key, [object.inputs])
67
- .then((inputs) => {
68
- object.inputs = JSON.parse(inputs[0]); // eslint-disable-line no-param-reassign
69
- })
70
- .catch((reason) => {
71
- ctx.webex.logger.warn(
72
- `plugin-conversation: failed to decrypt attachmentAction.inputs: ${reason}`
73
- );
74
- object.inputs = decryptionFailureMessage; // eslint-disable-line no-param-reassign
75
-
76
- return Promise.resolve(decryptionFailureMessage);
77
- });
78
- },
79
-
80
- /**
81
- * Decrypt an individual reaction2Summary activity (object.objectType === 'reaction2Summary')
82
- * @param {Object} ctx An object containing a webex instance and a transform
83
- * @param {String} key KMS key
84
- * @param {Object} object An instance of a Webex reaction2Summary object
85
- * these objects are returned by various conversation APIs and over mercury
86
- * represents an aggregated summary of all reactions to a specific activity
87
- * @returns {Promise} Returns a ctx.transform promise
88
- */
89
- decryptReaction2summary(ctx, key, object) {
90
- if (!object.reactions) {
91
- return Promise.resolve();
92
- }
93
-
94
- return Promise.all(
95
- object.reactions.map((reaction) => ctx.transform('decryptPropDisplayName', key, reaction))
96
- );
97
- },
98
-
99
- /**
100
- * Decrypt an individual reaction2SelfSummary activity (object.objectType === 'reaction2SelfSummary')
101
- * @param {Object} ctx An object containing a webex instance and a transform
102
- * @param {String} key KMS key
103
- * @param {Object} object An instance of a Webex reaction2SelfSummary object
104
- * these objects are returned by various conversation APIs and NOT over mercury
105
- * they are ONLY received by the self user
106
- * they represent ONLY the self user's reactions and are used for enforcing
107
- * limit of times they can react to a specific activity
108
- * @returns {Promise} Returns a ctx.transform promise
109
- */
110
- decryptReaction2selfsummary(ctx, key, object) {
111
- if (!object.reactions) {
112
- return Promise.resolve();
113
- }
114
-
115
- return Promise.all(
116
- object.reactions.map((reaction) => ctx.transform('decryptPropDisplayName', key, reaction))
117
- );
118
- },
119
-
120
- /**
121
- * Decrypt an individual reaction2 activity (object.objectType === 'reaction2')
122
- * @param {Object} ctx An object containing a webex instance and a transform
123
- * @param {String} key KMS key
124
- * @param {Object} object An instance of a Webex reaction2 object
125
- * these objects are returned by various conversation APIs and over mercury
126
- * ONLY self users receive these objects
127
- * @returns {Promise} Returns a ctx.transform promise
128
- */
129
- decryptReaction2(ctx, key, object) {
130
- return ctx.transform('decryptPropDisplayName', key, object);
131
- },
132
-
133
- /**
134
- * Decrypt an individual threadObject
135
- * @param {Object} ctx An object containing a webex instance and a transform
136
- * @param {Object} threadObject An instance of a Webex threadObject (the objects returned by the /conversation/api/v1/threads api)
137
- * @returns {Promise} Returns a ctx.transform promise
138
- */
139
- decryptThread(ctx, threadObject) {
140
- let promises = [];
141
-
142
- if (threadObject.childActivities && Array.isArray(threadObject.childActivities)) {
143
- promises = threadObject.childActivities.map((child) =>
144
- ctx.transform('decryptObject', null, child)
145
- );
146
- }
147
-
148
- return Promise.all(promises);
149
- },
150
-
151
- /**
152
- * Decrypt an individual meeting container activity
153
- * @param {Object} ctx An object containing a webex instance and a transform
154
- * @param {object} key KMS encryption key url
155
- * @param {Object} meetingContainerActivity An instance of a Webex meeting container activity
156
- * @returns {Promise} Returns a ctx.transform promise
157
- */
158
- decryptMeetingcontainer(ctx, key, meetingContainerActivity) {
159
- const promises = [];
160
-
161
- if (meetingContainerActivity.displayName) {
162
- const usableKey = meetingContainerActivity.encryptionKeyUrl || key;
163
-
164
- promises.push(ctx.transform('decryptPropDisplayName', usableKey, meetingContainerActivity));
165
- }
166
-
167
- if (meetingContainerActivity.extensions) {
168
- const itemsToDecrypt = meetingContainerActivity.extensions.items.filter(
169
- (item) => item.data.objectType === 'recording'
170
- );
171
-
172
- itemsToDecrypt.forEach((itemToDecrypt) => {
173
- promises.push(
174
- ctx.transform('decryptPropTopic', itemToDecrypt.encryptionKeyUrl, itemToDecrypt.data)
175
- );
176
- });
177
- }
178
-
179
- return Promise.all(promises);
180
- },
181
-
182
- /**
183
- * Decrypts a given conversation and it's activities by building an array of promises that call
184
- * decryptObject, which may then call other decrypt functions
185
- *
186
- * @param {Object} ctx An object containing a webex instance and a transform
187
- * @param {String} key KMS encryption key url (or actual key?)
188
- * @param {Object} conversation A Webex conversation object
189
- * @returns {Promise} Returns the result of Promise.all
190
- */
191
- decryptConversation(ctx, key, conversation) {
192
- const promises = [];
193
-
194
- if (conversation.activities.items) {
195
- promises.push(
196
- Promise.all(
197
- conversation.activities.items.map((item) => ctx.transform('decryptObject', null, item))
198
- )
199
- );
200
- }
201
-
202
- const usableKey = conversation.encryptionKeyUrl || key;
203
- const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
204
-
205
- if (usableKey) {
206
- promises.push(
207
- ctx.transform('decryptPropDisplayName', usableKey, conversation).catch((error) => {
208
- ctx.webex.logger.warn(
209
- 'plugin-conversation: failed to decrypt display name of ',
210
- conversation.url,
211
- error
212
- );
213
- Promise.resolve(decryptionFailureMessage);
214
- })
215
- );
216
- promises.push(ctx.transform('decryptPropContent', usableKey, conversation));
217
- }
218
- if (conversation.avatarEncryptionKeyUrl) {
219
- promises.push(
220
- ctx.transform('decryptObject', conversation.avatarEncryptionKeyUrl, conversation.avatar)
221
- );
222
- }
223
- // TODO (holsted 04/06/19): This was deprecated in favor of .previousValue below. I wanted to remove this entirely
224
- // but I wasn't sure if some open source use cases may be reading from cached conversations or not so leaving it for now.
225
- if (conversation.previous) {
226
- promises.push(ctx.transform('decryptPropDisplayName', usableKey, conversation.previous));
227
- }
228
- if (conversation.previousValue) {
229
- promises.push(ctx.transform('decryptPropDisplayName', usableKey, conversation.previousValue));
230
- }
231
-
232
- return Promise.all(promises);
233
- },
234
-
235
- /**
236
- * Decrypt an individual activity
237
- * @param {Object} ctx An object containing a webex instance and a transform
238
- * @param {String} key KMS encryption key url (or actual key?)
239
- * @param {Object} activity An instance of a Webex activity
240
- * @returns {Promise} Returns a ctx.transform promise
241
- */
242
- decryptActivity(ctx, key, activity) {
243
- if (!activity.encryptionKeyUrl && !(activity.object && activity.object.encryptionKeyUrl)) {
244
- return Promise.resolve(activity);
245
- }
246
-
247
- const keyUrl = activity.encryptionKeyUrl || activity.object.encryptionKeyUrl || key;
248
-
249
- let promises = [];
250
-
251
- // iterate and recursively decrypt over children objects
252
-
253
- if (activity.children && Array.isArray(activity.children)) {
254
- promises = activity.children.map((child) =>
255
- ctx.transform('decryptObject', keyUrl, child.activity)
256
- );
257
- }
258
-
259
- promises.push(ctx.transform('decryptObject', keyUrl, activity.object));
260
-
261
- return Promise.all(promises);
262
- },
263
-
264
- /**
265
- * Decrypts a microappInstance (recording) model
266
- * @param {Object} ctx An object containing a webex instance and transform prop
267
- * @param {String} key KMS key
268
- * @param {Object} microappInstance A microappInstance which includes several properties of a recording
269
- * @param {String} microappInstance.model An encrypted string which decrypts to an object
270
- * @returns {Promise} Returns a context transform
271
- */
272
- decryptMicroappinstance(ctx, key, microappInstance) {
273
- return ctx.transform('decryptPropModel', key, microappInstance);
274
- },
275
-
276
- /**
277
- * Decrypts a comment...
278
- * @param {Object} ctx An object containing a webex instance and transform prop
279
- * @param {String} key KMS key
280
- * @param {Object} comment A comment object with a displayName and content (encrypted)
281
- * @returns {Promise} Returns the results of Promise.all on two transforms
282
- */
283
- decryptComment(ctx, key, comment) {
284
- const promises = [
285
- ctx.transform('decryptPropDisplayName', key, comment),
286
- ctx.transform('decryptPropContent', key, comment),
287
- ];
288
-
289
- if (comment.cards && Array.isArray(comment.cards)) {
290
- comment.cards.map((item, index) =>
291
- promises.push(ctx.transform('decryptPropCardItem', index, key, comment.cards))
292
- );
293
- }
294
-
295
- return Promise.all(promises);
296
- },
297
-
298
- /**
299
- * Decrypts a content field
300
- * @param {Object} ctx An object containing a webex instance and transform prop
301
- * @param {String} key KMS key
302
- * @param {Object} content An object with properties to be decrypted
303
- * @returns {Promise} A promise that will return when the decryption has finished
304
- */
305
- decryptContent(ctx, key, content) {
306
- if (content.contentCategory === 'links') {
307
- return ctx.transform('decryptContentLinks', key, content);
308
- }
309
-
310
- return ctx.transform('decryptContentFiles', key, content);
311
- },
312
-
313
- /**
314
- * Decrypts a content field which contains files and possibly links
315
- * @param {Object} ctx An object containing a webex instance and transform prop
316
- * @param {String} key KMS key
317
- * @param {Object} content An object with properties to be decrypted
318
- * @param {Array} content.files An array of files to decrypt by calling decryptObject
319
- * @param {Array} content.links An array of links to decrypt by calling decryptObject
320
- * @returns {Promise} A promise that will return when the decryption has finished
321
- */
322
- decryptContentFiles(ctx, key, content) {
323
- if (!content.files || !content.files.items || !Array.isArray(content.files.items)) {
324
- return Promise.resolve();
325
- }
326
-
327
- const promises = content.files.items.map((item) => ctx.transform('decryptObject', key, item));
328
-
329
- promises.push(ctx.transform('decryptComment', key, content));
330
-
331
- if (content.links && content.links.items && Array.isArray(content.links.items)) {
332
- content.links.items.forEach((item) =>
333
- promises.push(ctx.transform('decryptObject', key, item))
334
- );
335
- }
336
-
337
- return Promise.all(promises);
338
- },
339
-
340
- /**
341
- * Decrypts a content field which contains links
342
- * @param {Object} ctx An object containing a webex instance and transform prop
343
- * @param {String} key KMS key
344
- * @param {Object} content An object with properties to be decrypted
345
- * @param {Array} content.links An array of links to decrypt by calling decryptObject
346
- * @returns {Promise} A promise that will return when the decryption has finished
347
- */
348
- decryptContentLinks(ctx, key, content) {
349
- if (!content.links || !content.links.items || !Array.isArray(content.links.items)) {
350
- return Promise.resolve();
351
- }
352
-
353
- const promises = content.links.items.map((item) => ctx.transform('decryptObject', key, item));
354
-
355
- promises.push(ctx.transform('decryptComment', key, content));
356
-
357
- return Promise.all(promises);
358
- },
359
-
360
- /**
361
- * Decrypts what may be a meeting event?
362
- * @param {Object} ctx An object containing a webex instance and transform prop
363
- * @param {String} key KMS key
364
- * @param {Object} event An object with a display name and location to be decrypted
365
- * @returns {Promise} Returns the result of Promise.all
366
- */
367
- decryptEvent(ctx, key, event) {
368
- const promises = [ctx.transform('decryptPropDisplayName', key, event)];
369
-
370
- if (event.location && event.location.split('.').length === 5) {
371
- promises.push(ctx.transform('decryptPropLocation', key, event));
372
- }
373
-
374
- return Promise.all(promises);
375
- },
376
-
377
- /**
378
- * Decrypts a file and it's transcodedContents if they exist
379
- * @param {Object} ctx An object containing a webex instance and transform prop
380
- * @param {String} key KMS key
381
- * @param {Object} file A file object with file props an optional transcodedCollection to decrypt
382
- * @returns {Promise} Returns the result of Promise.all
383
- */
384
- decryptFile(ctx, key, file) {
385
- // using object encryption keyUrl for images instead of activity encryptionKeyUrl
386
- if (file.encryptionKeyUrl && file.encryptionKeyUrl !== key) {
387
- key = file.encryptionKeyUrl;
388
- }
389
-
390
- return Promise.all([
391
- file.transcodedCollection &&
392
- Promise.all(
393
- file.transcodedCollection.items.map((item) => ctx.transform('decryptObject', key, item))
394
- ),
395
- ctx.transform('decryptPropScr', key, file),
396
- ctx.transform('decryptPropDisplayName', key, file),
397
- ctx.transform('decryptPropContent', key, file),
398
- file.image && ctx.transform('decryptPropScr', key, file.image),
399
- ]);
400
- },
401
-
402
- /**
403
- * Decrypts a file and it's transcodedContents if they exist
404
- * @param {Object} ctx An object containing a webex instance and transform prop
405
- * @param {String} key KMS key
406
- * @param {Object} link A link object with a URL to decrypt
407
- * @returns {Promise} Returns the result of Promise.all
408
- */
409
- decryptLink(ctx, key, link) {
410
- return Promise.all([
411
- ctx.transform('decryptPropSslr', key, link),
412
- ctx.transform('decryptPropDisplayName', key, link),
413
- ]);
414
- },
415
-
416
- /**
417
- * Decrypts transcoded file content. Calls decryptFile
418
- * @param {Object} ctx An object containing a webex instance and transform prop
419
- * @param {String} key KMS key
420
- * @param {Object} transcodedContent Transcoded content with a files prop
421
- * @returns {Promise} Returns the result of Promise.all
422
- */
423
- decryptTranscodedContent(ctx, key, transcodedContent) {
424
- return Promise.all(
425
- transcodedContent.files.items.map((item) => ctx.transform('decryptFile', key, item))
426
- );
427
- },
428
-
429
- /**
430
- * Decrypts an image uri
431
- * @param {Object} ctx An object containing a webex instance and transform prop
432
- * @param {String} key KMS key
433
- * @param {String} imageURI URI of the image to decrypt
434
- * @returns {Promise} Returns a promise.
435
- */
436
- decryptImageURI(ctx, key, imageURI) {
437
- return ctx.transform('decryptPropLocation', key, imageURI);
438
- },
439
-
440
- /**
441
- * The heart of most decryption logic ends here. Decrypting text.
442
- * @param {Object} ctx An object containing a webex instance and transform prop
443
- * @param {String} name Property of an object to be decrypted
444
- * @param {String} key KMS key
445
- * @param {Object} object A generic object with text props to be decrypted
446
- * @returns {Promise} Returns a lonely Promise
447
- */
448
- decryptTextProp(ctx, name, key, object) {
449
- if (!object[name]) {
450
- return Promise.resolve();
451
- }
452
- const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
453
-
454
- return ctx.webex.internal.encryption
455
- .decryptText(key, object[name])
456
- .then((plaintext) => {
457
- if (ctx.webex.config.conversation.keepEncryptedProperties) {
458
- const encryptedPropName = camelCase(`encrypted_${name}`);
459
-
460
- object[encryptedPropName] = object[name]; // eslint-disable-line no-param-reassign
461
- }
462
-
463
- object[name] = plaintext; // eslint-disable-line no-param-reassign
464
- })
465
- .catch((reason) => {
466
- ctx.webex.logger.warn(`plugin-conversation: failed to decrypt ${name} `, reason);
467
- object[name] = decryptionFailureMessage; // eslint-disable-line no-param-reassign
468
-
469
- return Promise.resolve(decryptionFailureMessage);
470
- });
471
- },
472
-
473
- /**
474
- * Decrypting an element in an Array.
475
- * @param {Object} ctx An object containing a webex instance and transform prop
476
- * @param {Integer} index Property of an object to be decrypted
477
- * @param {String} key KMS key
478
- * @param {Array} array An array of Strings to be decrypted
479
- * @returns {Promise} Returns a lonely Promise
480
- */
481
- decryptPropCardItem(ctx, index, key, array) {
482
- if (
483
- !Number.isInteger(index) ||
484
- !array ||
485
- !Array.isArray(array) ||
486
- index < 0 ||
487
- index >= array.length ||
488
- !(array[index] instanceof String || typeof array[index] === 'string')
489
- ) {
490
- return Promise.resolve();
491
- }
492
- const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
493
-
494
- return ctx.webex.internal.encryption
495
- .decryptText(key, array[index])
496
- .then((plaintext) => {
497
- array[index] = plaintext; // eslint-disable-line no-param-reassign
498
- })
499
- .catch((reason) => {
500
- ctx.webex.logger.warn(`plugin-conversation: failed to decrypt card at ${index} `, reason);
501
- array[index] = decryptionFailureMessage; // eslint-disable-line no-param-reassign
502
-
503
- return Promise.resolve(decryptionFailureMessage);
504
- });
505
- },
506
- /**
507
- * Decrypts the src of an object (for images?)
508
- * @param {Object} ctx An object containing a webex instance and transform prop
509
- * @param {String} key KMS key
510
- * @param {Object} object An object with a scr property to be decrypted
511
- * @returns {Promise} Returns a promise
512
- */
513
- decryptPropScr(ctx, key, object) {
514
- return ctx.webex.internal.encryption.decryptScr(key, object.scr).then((scr) => {
515
- object.scr = scr; // eslint-disable-line no-param-reassign
516
- });
517
- },
518
-
519
- /**
520
- * Decrypts the sslr object
521
- * @param {Object} ctx An object containing a webex instance and transform prop
522
- * @param {String} key KMS key
523
- * @param {Object} object An object with a sslr property to be decrypted
524
- * @returns {Promise} Returns a promise
525
- */
526
- decryptPropSslr(ctx, key, object) {
527
- return ctx.webex.internal.encryption.decryptScr(key, object.sslr).then((sslr) => {
528
- object.sslr = sslr; // eslint-disable-line no-param-reassign
529
- });
530
- },
531
-
532
- decryptPropDisplayName: decryptTextProp('displayName'),
533
-
534
- decryptPropContent: decryptTextProp('content'),
535
-
536
- decryptPropModel: decryptTextProp('model'),
537
-
538
- decryptPropLocation: decryptTextProp('location'),
539
-
540
- decryptPropTopic: decryptTextProp('topic'),
541
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {camelCase, capitalize, curry} from 'lodash';
6
+
7
+ import toArray from './to-array';
8
+
9
+ const decryptTextProp = curry((name, ctx, key, object) =>
10
+ ctx.transform('decryptTextProp', name, key, object)
11
+ );
12
+
13
+ // eslint-disable-next-line import/prefer-default-export
14
+ export const transforms = toArray('inbound', {
15
+ /**
16
+ * This function is used recursively to decrypt various properties on conversations, activities, etc
17
+ * @param {Object} ctx An object containing a webex instance and a transform
18
+ * @param {String} key KMS encryption key url
19
+ * @param {Object} object Generic object that you want to decrypt some property on based on the type
20
+ * @returns {Promise} Returns a transform promise
21
+ */
22
+ decryptObject(ctx, key, object) {
23
+ if (!object) {
24
+ object = key; // eslint-disable-line no-param-reassign
25
+ key = undefined; // eslint-disable-line no-param-reassign
26
+ }
27
+
28
+ if (!object) {
29
+ return Promise.resolve();
30
+ }
31
+
32
+ if (!object.objectType) {
33
+ return Promise.resolve();
34
+ }
35
+
36
+ if (!key && object.encryptionKeyUrl) {
37
+ key = object.encryptionKeyUrl; // eslint-disable-line no-param-reassign
38
+ }
39
+
40
+ // Transcoded content was not showing up on the activities since the
41
+ // decryptFile was not being called. Calling decryptFile for
42
+ // transcodedContent fixes the issue.
43
+ if (object.objectType === 'transcodedContent') {
44
+ return Promise.all(object.files.items.map((item) => ctx.transform('decryptFile', key, item)));
45
+ }
46
+
47
+ return ctx.transform(`decrypt${capitalize(object.objectType)}`, key, object);
48
+ },
49
+
50
+ /**
51
+ * Decrypt an individual submit object from a cardAction activity
52
+ * (object.objectType === 'submit')
53
+ * @param {Object} ctx An object containing a webex instance and a transform
54
+ * @param {String} key KMS key
55
+ * @param {Object} object An instance of a Webex submit object
56
+ * these objects are returned when a user clicks on a Action.Submit button in a card
57
+ * @returns {Promise} Returns a ctx.transform promise
58
+ */
59
+ decryptSubmit(ctx, key, object) {
60
+ if (!object.inputs) {
61
+ return Promise.resolve();
62
+ }
63
+ const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
64
+
65
+ return ctx
66
+ .transform('decryptPropCardItem', 0, key, [object.inputs])
67
+ .then((inputs) => {
68
+ object.inputs = JSON.parse(inputs[0]); // eslint-disable-line no-param-reassign
69
+ })
70
+ .catch((reason) => {
71
+ ctx.webex.logger.warn(
72
+ `plugin-conversation: failed to decrypt attachmentAction.inputs: ${reason}`
73
+ );
74
+ object.inputs = decryptionFailureMessage; // eslint-disable-line no-param-reassign
75
+
76
+ return Promise.resolve(decryptionFailureMessage);
77
+ });
78
+ },
79
+
80
+ /**
81
+ * Decrypt an individual reaction2Summary activity (object.objectType === 'reaction2Summary')
82
+ * @param {Object} ctx An object containing a webex instance and a transform
83
+ * @param {String} key KMS key
84
+ * @param {Object} object An instance of a Webex reaction2Summary object
85
+ * these objects are returned by various conversation APIs and over mercury
86
+ * represents an aggregated summary of all reactions to a specific activity
87
+ * @returns {Promise} Returns a ctx.transform promise
88
+ */
89
+ decryptReaction2summary(ctx, key, object) {
90
+ if (!object.reactions) {
91
+ return Promise.resolve();
92
+ }
93
+
94
+ return Promise.all(
95
+ object.reactions.map((reaction) => ctx.transform('decryptPropDisplayName', key, reaction))
96
+ );
97
+ },
98
+
99
+ /**
100
+ * Decrypt an individual reaction2SelfSummary activity (object.objectType === 'reaction2SelfSummary')
101
+ * @param {Object} ctx An object containing a webex instance and a transform
102
+ * @param {String} key KMS key
103
+ * @param {Object} object An instance of a Webex reaction2SelfSummary object
104
+ * these objects are returned by various conversation APIs and NOT over mercury
105
+ * they are ONLY received by the self user
106
+ * they represent ONLY the self user's reactions and are used for enforcing
107
+ * limit of times they can react to a specific activity
108
+ * @returns {Promise} Returns a ctx.transform promise
109
+ */
110
+ decryptReaction2selfsummary(ctx, key, object) {
111
+ if (!object.reactions) {
112
+ return Promise.resolve();
113
+ }
114
+
115
+ return Promise.all(
116
+ object.reactions.map((reaction) => ctx.transform('decryptPropDisplayName', key, reaction))
117
+ );
118
+ },
119
+
120
+ /**
121
+ * Decrypt an individual reaction2 activity (object.objectType === 'reaction2')
122
+ * @param {Object} ctx An object containing a webex instance and a transform
123
+ * @param {String} key KMS key
124
+ * @param {Object} object An instance of a Webex reaction2 object
125
+ * these objects are returned by various conversation APIs and over mercury
126
+ * ONLY self users receive these objects
127
+ * @returns {Promise} Returns a ctx.transform promise
128
+ */
129
+ decryptReaction2(ctx, key, object) {
130
+ return ctx.transform('decryptPropDisplayName', key, object);
131
+ },
132
+
133
+ /**
134
+ * Decrypt an individual threadObject
135
+ * @param {Object} ctx An object containing a webex instance and a transform
136
+ * @param {Object} threadObject An instance of a Webex threadObject (the objects returned by the /conversation/api/v1/threads api)
137
+ * @returns {Promise} Returns a ctx.transform promise
138
+ */
139
+ decryptThread(ctx, threadObject) {
140
+ let promises = [];
141
+
142
+ if (threadObject.childActivities && Array.isArray(threadObject.childActivities)) {
143
+ promises = threadObject.childActivities.map((child) =>
144
+ ctx.transform('decryptObject', null, child)
145
+ );
146
+ }
147
+
148
+ return Promise.all(promises);
149
+ },
150
+
151
+ /**
152
+ * Decrypt an individual meeting container activity
153
+ * @param {Object} ctx An object containing a webex instance and a transform
154
+ * @param {object} key KMS encryption key url
155
+ * @param {Object} meetingContainerActivity An instance of a Webex meeting container activity
156
+ * @returns {Promise} Returns a ctx.transform promise
157
+ */
158
+ decryptMeetingcontainer(ctx, key, meetingContainerActivity) {
159
+ const promises = [];
160
+
161
+ if (meetingContainerActivity.displayName) {
162
+ const usableKey = meetingContainerActivity.encryptionKeyUrl || key;
163
+
164
+ promises.push(ctx.transform('decryptPropDisplayName', usableKey, meetingContainerActivity));
165
+ }
166
+
167
+ if (meetingContainerActivity.extensions) {
168
+ const itemsToDecrypt = meetingContainerActivity.extensions.items.filter(
169
+ (item) => item.data.objectType === 'recording'
170
+ );
171
+
172
+ itemsToDecrypt.forEach((itemToDecrypt) => {
173
+ promises.push(
174
+ ctx.transform('decryptPropTopic', itemToDecrypt.encryptionKeyUrl, itemToDecrypt.data)
175
+ );
176
+ });
177
+ }
178
+
179
+ return Promise.all(promises);
180
+ },
181
+
182
+ /**
183
+ * Decrypts a given conversation and it's activities by building an array of promises that call
184
+ * decryptObject, which may then call other decrypt functions
185
+ *
186
+ * @param {Object} ctx An object containing a webex instance and a transform
187
+ * @param {String} key KMS encryption key url (or actual key?)
188
+ * @param {Object} conversation A Webex conversation object
189
+ * @returns {Promise} Returns the result of Promise.all
190
+ */
191
+ decryptConversation(ctx, key, conversation) {
192
+ const promises = [];
193
+
194
+ if (conversation.activities.items) {
195
+ promises.push(
196
+ Promise.all(
197
+ conversation.activities.items.map((item) => ctx.transform('decryptObject', null, item))
198
+ )
199
+ );
200
+ }
201
+
202
+ const usableKey = conversation.encryptionKeyUrl || key;
203
+ const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
204
+
205
+ if (usableKey) {
206
+ promises.push(
207
+ ctx.transform('decryptPropDisplayName', usableKey, conversation).catch((error) => {
208
+ ctx.webex.logger.warn(
209
+ 'plugin-conversation: failed to decrypt display name of ',
210
+ conversation.url,
211
+ error
212
+ );
213
+ Promise.resolve(decryptionFailureMessage);
214
+ })
215
+ );
216
+ promises.push(ctx.transform('decryptPropContent', usableKey, conversation));
217
+ }
218
+ if (conversation.avatarEncryptionKeyUrl) {
219
+ promises.push(
220
+ ctx.transform('decryptObject', conversation.avatarEncryptionKeyUrl, conversation.avatar)
221
+ );
222
+ }
223
+ // TODO (holsted 04/06/19): This was deprecated in favor of .previousValue below. I wanted to remove this entirely
224
+ // but I wasn't sure if some open source use cases may be reading from cached conversations or not so leaving it for now.
225
+ if (conversation.previous) {
226
+ promises.push(ctx.transform('decryptPropDisplayName', usableKey, conversation.previous));
227
+ }
228
+ if (conversation.previousValue) {
229
+ promises.push(ctx.transform('decryptPropDisplayName', usableKey, conversation.previousValue));
230
+ }
231
+
232
+ return Promise.all(promises);
233
+ },
234
+
235
+ /**
236
+ * Decrypt an individual activity
237
+ * @param {Object} ctx An object containing a webex instance and a transform
238
+ * @param {String} key KMS encryption key url (or actual key?)
239
+ * @param {Object} activity An instance of a Webex activity
240
+ * @returns {Promise} Returns a ctx.transform promise
241
+ */
242
+ decryptActivity(ctx, key, activity) {
243
+ if (!activity.encryptionKeyUrl && !(activity.object && activity.object.encryptionKeyUrl)) {
244
+ return Promise.resolve(activity);
245
+ }
246
+
247
+ const keyUrl = activity.encryptionKeyUrl || activity.object.encryptionKeyUrl || key;
248
+
249
+ let promises = [];
250
+
251
+ // iterate and recursively decrypt over children objects
252
+
253
+ if (activity.children && Array.isArray(activity.children)) {
254
+ promises = activity.children.map((child) =>
255
+ ctx.transform('decryptObject', keyUrl, child.activity)
256
+ );
257
+ }
258
+
259
+ promises.push(ctx.transform('decryptObject', keyUrl, activity.object));
260
+
261
+ return Promise.all(promises);
262
+ },
263
+
264
+ /**
265
+ * Decrypts a microappInstance (recording) model
266
+ * @param {Object} ctx An object containing a webex instance and transform prop
267
+ * @param {String} key KMS key
268
+ * @param {Object} microappInstance A microappInstance which includes several properties of a recording
269
+ * @param {String} microappInstance.model An encrypted string which decrypts to an object
270
+ * @returns {Promise} Returns a context transform
271
+ */
272
+ decryptMicroappinstance(ctx, key, microappInstance) {
273
+ return ctx.transform('decryptPropModel', key, microappInstance);
274
+ },
275
+
276
+ /**
277
+ * Decrypts a comment...
278
+ * @param {Object} ctx An object containing a webex instance and transform prop
279
+ * @param {String} key KMS key
280
+ * @param {Object} comment A comment object with a displayName and content (encrypted)
281
+ * @returns {Promise} Returns the results of Promise.all on two transforms
282
+ */
283
+ decryptComment(ctx, key, comment) {
284
+ const promises = [
285
+ ctx.transform('decryptPropDisplayName', key, comment),
286
+ ctx.transform('decryptPropContent', key, comment),
287
+ ];
288
+
289
+ if (comment.cards && Array.isArray(comment.cards)) {
290
+ comment.cards.map((item, index) =>
291
+ promises.push(ctx.transform('decryptPropCardItem', index, key, comment.cards))
292
+ );
293
+ }
294
+
295
+ return Promise.all(promises);
296
+ },
297
+
298
+ /**
299
+ * Decrypts a content field
300
+ * @param {Object} ctx An object containing a webex instance and transform prop
301
+ * @param {String} key KMS key
302
+ * @param {Object} content An object with properties to be decrypted
303
+ * @returns {Promise} A promise that will return when the decryption has finished
304
+ */
305
+ decryptContent(ctx, key, content) {
306
+ if (content.contentCategory === 'links') {
307
+ return ctx.transform('decryptContentLinks', key, content);
308
+ }
309
+
310
+ return ctx.transform('decryptContentFiles', key, content);
311
+ },
312
+
313
+ /**
314
+ * Decrypts a content field which contains files and possibly links
315
+ * @param {Object} ctx An object containing a webex instance and transform prop
316
+ * @param {String} key KMS key
317
+ * @param {Object} content An object with properties to be decrypted
318
+ * @param {Array} content.files An array of files to decrypt by calling decryptObject
319
+ * @param {Array} content.links An array of links to decrypt by calling decryptObject
320
+ * @returns {Promise} A promise that will return when the decryption has finished
321
+ */
322
+ decryptContentFiles(ctx, key, content) {
323
+ if (!content.files || !content.files.items || !Array.isArray(content.files.items)) {
324
+ return Promise.resolve();
325
+ }
326
+
327
+ const promises = content.files.items.map((item) => ctx.transform('decryptObject', key, item));
328
+
329
+ promises.push(ctx.transform('decryptComment', key, content));
330
+
331
+ if (content.links && content.links.items && Array.isArray(content.links.items)) {
332
+ content.links.items.forEach((item) =>
333
+ promises.push(ctx.transform('decryptObject', key, item))
334
+ );
335
+ }
336
+
337
+ return Promise.all(promises);
338
+ },
339
+
340
+ /**
341
+ * Decrypts a content field which contains links
342
+ * @param {Object} ctx An object containing a webex instance and transform prop
343
+ * @param {String} key KMS key
344
+ * @param {Object} content An object with properties to be decrypted
345
+ * @param {Array} content.links An array of links to decrypt by calling decryptObject
346
+ * @returns {Promise} A promise that will return when the decryption has finished
347
+ */
348
+ decryptContentLinks(ctx, key, content) {
349
+ if (!content.links || !content.links.items || !Array.isArray(content.links.items)) {
350
+ return Promise.resolve();
351
+ }
352
+
353
+ const promises = content.links.items.map((item) => ctx.transform('decryptObject', key, item));
354
+
355
+ promises.push(ctx.transform('decryptComment', key, content));
356
+
357
+ return Promise.all(promises);
358
+ },
359
+
360
+ /**
361
+ * Decrypts what may be a meeting event?
362
+ * @param {Object} ctx An object containing a webex instance and transform prop
363
+ * @param {String} key KMS key
364
+ * @param {Object} event An object with a display name and location to be decrypted
365
+ * @returns {Promise} Returns the result of Promise.all
366
+ */
367
+ decryptEvent(ctx, key, event) {
368
+ const promises = [ctx.transform('decryptPropDisplayName', key, event)];
369
+
370
+ if (event.location && event.location.split('.').length === 5) {
371
+ promises.push(ctx.transform('decryptPropLocation', key, event));
372
+ }
373
+
374
+ return Promise.all(promises);
375
+ },
376
+
377
+ /**
378
+ * Decrypts a file and it's transcodedContents if they exist
379
+ * @param {Object} ctx An object containing a webex instance and transform prop
380
+ * @param {String} key KMS key
381
+ * @param {Object} file A file object with file props an optional transcodedCollection to decrypt
382
+ * @returns {Promise} Returns the result of Promise.all
383
+ */
384
+ decryptFile(ctx, key, file) {
385
+ // using object encryption keyUrl for images instead of activity encryptionKeyUrl
386
+ if (file.encryptionKeyUrl && file.encryptionKeyUrl !== key) {
387
+ key = file.encryptionKeyUrl;
388
+ }
389
+
390
+ return Promise.all([
391
+ file.transcodedCollection &&
392
+ Promise.all(
393
+ file.transcodedCollection.items.map((item) => ctx.transform('decryptObject', key, item))
394
+ ),
395
+ ctx.transform('decryptPropScr', key, file),
396
+ ctx.transform('decryptPropDisplayName', key, file),
397
+ ctx.transform('decryptPropContent', key, file),
398
+ file.image && ctx.transform('decryptPropScr', key, file.image),
399
+ ]);
400
+ },
401
+
402
+ /**
403
+ * Decrypts a file and it's transcodedContents if they exist
404
+ * @param {Object} ctx An object containing a webex instance and transform prop
405
+ * @param {String} key KMS key
406
+ * @param {Object} link A link object with a URL to decrypt
407
+ * @returns {Promise} Returns the result of Promise.all
408
+ */
409
+ decryptLink(ctx, key, link) {
410
+ return Promise.all([
411
+ ctx.transform('decryptPropSslr', key, link),
412
+ ctx.transform('decryptPropDisplayName', key, link),
413
+ ]);
414
+ },
415
+
416
+ /**
417
+ * Decrypts transcoded file content. Calls decryptFile
418
+ * @param {Object} ctx An object containing a webex instance and transform prop
419
+ * @param {String} key KMS key
420
+ * @param {Object} transcodedContent Transcoded content with a files prop
421
+ * @returns {Promise} Returns the result of Promise.all
422
+ */
423
+ decryptTranscodedContent(ctx, key, transcodedContent) {
424
+ return Promise.all(
425
+ transcodedContent.files.items.map((item) => ctx.transform('decryptFile', key, item))
426
+ );
427
+ },
428
+
429
+ /**
430
+ * Decrypts an image uri
431
+ * @param {Object} ctx An object containing a webex instance and transform prop
432
+ * @param {String} key KMS key
433
+ * @param {String} imageURI URI of the image to decrypt
434
+ * @returns {Promise} Returns a promise.
435
+ */
436
+ decryptImageURI(ctx, key, imageURI) {
437
+ return ctx.transform('decryptPropLocation', key, imageURI);
438
+ },
439
+
440
+ /**
441
+ * The heart of most decryption logic ends here. Decrypting text.
442
+ * @param {Object} ctx An object containing a webex instance and transform prop
443
+ * @param {String} name Property of an object to be decrypted
444
+ * @param {String} key KMS key
445
+ * @param {Object} object A generic object with text props to be decrypted
446
+ * @returns {Promise} Returns a lonely Promise
447
+ */
448
+ decryptTextProp(ctx, name, key, object) {
449
+ if (!object[name]) {
450
+ return Promise.resolve();
451
+ }
452
+ const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
453
+
454
+ return ctx.webex.internal.encryption
455
+ .decryptText(key, object[name])
456
+ .then((plaintext) => {
457
+ if (ctx.webex.config.conversation.keepEncryptedProperties) {
458
+ const encryptedPropName = camelCase(`encrypted_${name}`);
459
+
460
+ object[encryptedPropName] = object[name]; // eslint-disable-line no-param-reassign
461
+ }
462
+
463
+ object[name] = plaintext; // eslint-disable-line no-param-reassign
464
+ })
465
+ .catch((reason) => {
466
+ ctx.webex.logger.warn(`plugin-conversation: failed to decrypt ${name} `, reason);
467
+ object[name] = decryptionFailureMessage; // eslint-disable-line no-param-reassign
468
+
469
+ return Promise.resolve(decryptionFailureMessage);
470
+ });
471
+ },
472
+
473
+ /**
474
+ * Decrypting an element in an Array.
475
+ * @param {Object} ctx An object containing a webex instance and transform prop
476
+ * @param {Integer} index Property of an object to be decrypted
477
+ * @param {String} key KMS key
478
+ * @param {Array} array An array of Strings to be decrypted
479
+ * @returns {Promise} Returns a lonely Promise
480
+ */
481
+ decryptPropCardItem(ctx, index, key, array) {
482
+ if (
483
+ !Number.isInteger(index) ||
484
+ !array ||
485
+ !Array.isArray(array) ||
486
+ index < 0 ||
487
+ index >= array.length ||
488
+ !(array[index] instanceof String || typeof array[index] === 'string')
489
+ ) {
490
+ return Promise.resolve();
491
+ }
492
+ const {decryptionFailureMessage} = ctx.webex.internal.conversation.config;
493
+
494
+ return ctx.webex.internal.encryption
495
+ .decryptText(key, array[index])
496
+ .then((plaintext) => {
497
+ array[index] = plaintext; // eslint-disable-line no-param-reassign
498
+ })
499
+ .catch((reason) => {
500
+ ctx.webex.logger.warn(`plugin-conversation: failed to decrypt card at ${index} `, reason);
501
+ array[index] = decryptionFailureMessage; // eslint-disable-line no-param-reassign
502
+
503
+ return Promise.resolve(decryptionFailureMessage);
504
+ });
505
+ },
506
+ /**
507
+ * Decrypts the src of an object (for images?)
508
+ * @param {Object} ctx An object containing a webex instance and transform prop
509
+ * @param {String} key KMS key
510
+ * @param {Object} object An object with a scr property to be decrypted
511
+ * @returns {Promise} Returns a promise
512
+ */
513
+ decryptPropScr(ctx, key, object) {
514
+ return ctx.webex.internal.encryption.decryptScr(key, object.scr).then((scr) => {
515
+ object.scr = scr; // eslint-disable-line no-param-reassign
516
+ });
517
+ },
518
+
519
+ /**
520
+ * Decrypts the sslr object
521
+ * @param {Object} ctx An object containing a webex instance and transform prop
522
+ * @param {String} key KMS key
523
+ * @param {Object} object An object with a sslr property to be decrypted
524
+ * @returns {Promise} Returns a promise
525
+ */
526
+ decryptPropSslr(ctx, key, object) {
527
+ return ctx.webex.internal.encryption.decryptScr(key, object.sslr).then((sslr) => {
528
+ object.sslr = sslr; // eslint-disable-line no-param-reassign
529
+ });
530
+ },
531
+
532
+ decryptPropDisplayName: decryptTextProp('displayName'),
533
+
534
+ decryptPropContent: decryptTextProp('content'),
535
+
536
+ decryptPropModel: decryptTextProp('model'),
537
+
538
+ decryptPropLocation: decryptTextProp('location'),
539
+
540
+ decryptPropTopic: decryptTextProp('topic'),
541
+ });