@webex/internal-plugin-conversation 2.59.1 → 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.
- package/.eslintrc.js +6 -6
- package/README.md +47 -47
- package/babel.config.js +3 -3
- package/dist/activities.js +4 -4
- package/dist/activities.js.map +1 -1
- package/dist/activity-thread-ordering.js +34 -34
- package/dist/activity-thread-ordering.js.map +1 -1
- package/dist/config.js +12 -12
- package/dist/config.js.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/conversation.js +474 -474
- package/dist/conversation.js.map +1 -1
- package/dist/convo-error.js +4 -4
- package/dist/convo-error.js.map +1 -1
- package/dist/decryption-transforms.js +155 -155
- package/dist/decryption-transforms.js.map +1 -1
- package/dist/encryption-transforms.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/share-activity.js +57 -57
- package/dist/share-activity.js.map +1 -1
- package/dist/to-array.js +7 -7
- package/dist/to-array.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +21 -20
- package/process +1 -1
- package/src/activities.js +157 -157
- package/src/activity-thread-ordering.js +283 -283
- package/src/activity-threading.md +282 -282
- package/src/config.js +37 -37
- package/src/constants.js +3 -3
- package/src/conversation.js +2535 -2535
- package/src/convo-error.js +15 -15
- package/src/decryption-transforms.js +541 -541
- package/src/encryption-transforms.js +345 -345
- package/src/index.js +327 -327
- package/src/share-activity.js +436 -436
- package/src/to-array.js +29 -29
- package/test/integration/spec/create.js +290 -290
- package/test/integration/spec/encryption.js +333 -333
- package/test/integration/spec/get.js +1255 -1255
- package/test/integration/spec/mercury.js +94 -94
- package/test/integration/spec/share.js +537 -537
- package/test/integration/spec/verbs.js +1041 -1041
- package/test/unit/spec/conversation.js +823 -823
- package/test/unit/spec/decrypt-transforms.js +460 -460
- package/test/unit/spec/encryption-transforms.js +93 -93
- package/test/unit/spec/share-activity.js +178 -178
|
@@ -1,345 +1,345 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {capitalize, curry, get, has, isArray} from 'lodash';
|
|
6
|
-
|
|
7
|
-
import toArray from './to-array';
|
|
8
|
-
|
|
9
|
-
const KEY = Symbol('KEY');
|
|
10
|
-
|
|
11
|
-
const encryptTextProp = curry((name, ctx, key, object) =>
|
|
12
|
-
ctx.transform('encryptTextProp', name, key, object)
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
const encryptJsonProp = curry((name, ctx, key, object) =>
|
|
16
|
-
ctx.transform('encryptJsonProp', name, key, object)
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
// eslint-disable-next-line import/prefer-default-export
|
|
20
|
-
export const transforms = toArray('outbound', {
|
|
21
|
-
encryptObject(ctx, key, object) {
|
|
22
|
-
if (!object) {
|
|
23
|
-
object = key;
|
|
24
|
-
key = undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!object) {
|
|
28
|
-
return Promise.resolve();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!object.objectType) {
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (key === false) {
|
|
36
|
-
return Promise.resolve();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return ctx.transform(`encrypt${capitalize(object.objectType)}`, key, object);
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
encryptReaction2(ctx, key, reaction2) {
|
|
43
|
-
return ctx.transform('encryptPropDisplayName', key, reaction2);
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
encryptConversation(ctx, key, conversation) {
|
|
47
|
-
if (key === false) {
|
|
48
|
-
return Promise.resolve();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return Promise.resolve(
|
|
52
|
-
key || ctx.webex.internal.encryption.kms.createUnboundKeys({count: 1})
|
|
53
|
-
).then((keys) => {
|
|
54
|
-
const k = isArray(keys) ? keys[0] : keys;
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
has(conversation, 'kmsMessage.keyUris') &&
|
|
58
|
-
!conversation.kmsMessage.keyUris.includes(k.uri)
|
|
59
|
-
) {
|
|
60
|
-
conversation.kmsMessage.keyUris.push(k.uri);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return Promise.all([
|
|
64
|
-
// too many implicit returns on the same line is difficult to interpret
|
|
65
|
-
// eslint-disable-next-line arrow-body-style
|
|
66
|
-
has(conversation, 'activities.items') &&
|
|
67
|
-
conversation.activities.items.reduce((p, activity) => {
|
|
68
|
-
// eslint-disable-next-line max-nested-callbacks
|
|
69
|
-
return p.then(() => ctx.transform('encryptObject', k, activity));
|
|
70
|
-
}, Promise.resolve()),
|
|
71
|
-
ctx.transform('encryptPropDisplayName', k, conversation),
|
|
72
|
-
]).then(() => {
|
|
73
|
-
conversation.encryptionKeyUrl = k.uri || k;
|
|
74
|
-
// we only want to set the defaultActivityEncryptionKeyUrl if we've
|
|
75
|
-
// bound a new key
|
|
76
|
-
if (!key) {
|
|
77
|
-
conversation.defaultActivityEncryptionKeyUrl =
|
|
78
|
-
conversation.defaultActivityEncryptionKeyUrl || k.uri || k;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
encryptActivity(ctx, key, activity) {
|
|
85
|
-
// Activity is already encrypted
|
|
86
|
-
if (activity.encryptionKeyUrl || activity.object?.created === 'True') {
|
|
87
|
-
return Promise.resolve();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return ctx.transform(`encrypt${capitalize(activity.verb)}Activity`, key, activity).then(() => {
|
|
91
|
-
key = key || activity[KEY];
|
|
92
|
-
|
|
93
|
-
return ctx.transform('prepareActivityKmsMessage', key, activity);
|
|
94
|
-
});
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
encryptVerbActivity(ctx, key, activity) {
|
|
98
|
-
return ctx
|
|
99
|
-
.transform('maybeEncryptTarget', key, activity)
|
|
100
|
-
.then(() => {
|
|
101
|
-
key = key || activity[KEY];
|
|
102
|
-
})
|
|
103
|
-
.then(() => ctx.transform('encryptObject', key, activity.object));
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
maybeEncryptTarget(ctx, key, activity) {
|
|
107
|
-
// This isn't quite right; if we just go by key, we have no guarantee that
|
|
108
|
-
// we have a proper KRO available for add activities
|
|
109
|
-
if (key) {
|
|
110
|
-
return Promise.resolve();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
has(activity, 'target.defaultActivityEncryptionKeyUrl') &&
|
|
115
|
-
activity.target.defaultActivityEncryptionKeyUrl &&
|
|
116
|
-
has(activity, 'target.kmsResourceObjectUrl')
|
|
117
|
-
) {
|
|
118
|
-
activity[KEY] = key || activity.target.defaultActivityEncryptionKeyUrl;
|
|
119
|
-
|
|
120
|
-
return Promise.resolve();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const conversationUrl = activity.target && activity.target.url;
|
|
124
|
-
|
|
125
|
-
if (!conversationUrl) {
|
|
126
|
-
return Promise.reject(
|
|
127
|
-
new Error(
|
|
128
|
-
"Cannot determine encryption key for activity's conversation; no key url or conversation url provided"
|
|
129
|
-
)
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return ctx.webex.internal.conversation.get({url: conversationUrl}).then((conversation) => {
|
|
134
|
-
if (!conversation.defaultActivityEncryptionKeyUrl) {
|
|
135
|
-
return ctx.webex.internal.conversation.updateKey(conversation).then((updateKeyActivity) => {
|
|
136
|
-
if (updateKeyActivity.kmsMessage.resource) {
|
|
137
|
-
activity.target.kmsResourceObjectUrl = updateKeyActivity.kmsMessage.resource.uri;
|
|
138
|
-
}
|
|
139
|
-
activity[KEY] = activity.target.defaultActivityEncryptionKeyUrl =
|
|
140
|
-
updateKeyActivity.object.defaultActivityEncryptionKeyUrl;
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!activity.target.defaultActivityEncryptionKeyUrl) {
|
|
145
|
-
ctx.webex.logger.warn(
|
|
146
|
-
'plugin-conversation: downloaded conversation to determine its defaultActivityEncryptionKeyUrl; make sure to pass all encryption related properties when calling Webex.conversation methods.'
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!activity.target.kmsResourceObjectUrl) {
|
|
151
|
-
ctx.webex.logger.warn(
|
|
152
|
-
'plugin-conversation: downloaded conversation to determine its kmsResourceObjectUrl; make sure to pass all encryption related properties when calling Webex.conversation methods.'
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
activity[KEY] = activity.target.defaultActivityEncryptionKeyUrl =
|
|
157
|
-
conversation.defaultActivityEncryptionKeyUrl;
|
|
158
|
-
activity.target.kmsResourceObjectUrl = conversation.kmsResourceObjectUrl;
|
|
159
|
-
|
|
160
|
-
return Promise.resolve();
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
prepareActivityKmsMessage(ctx, key, activity) {
|
|
165
|
-
if (activity.kmsMessage) {
|
|
166
|
-
if (!key && activity.verb === 'delete') {
|
|
167
|
-
key = get(activity, 'target.defaultActivityEncryptionKeyUrl');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
!key &&
|
|
172
|
-
activity.verb === 'updateKey' &&
|
|
173
|
-
has(activity, 'object.defaultActivityEncryptionKeyUrl')
|
|
174
|
-
) {
|
|
175
|
-
key = get(activity, 'object.defaultActivityEncryptionKeyUrl');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
!key &&
|
|
180
|
-
activity.verb === 'leave' &&
|
|
181
|
-
has(activity, 'target.defaultActivityEncryptionKeyUrl')
|
|
182
|
-
) {
|
|
183
|
-
key = get(activity, 'target.defaultActivityEncryptionKeyUrl');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (key) {
|
|
187
|
-
const kro = activity.target.kmsResourceObjectUrl;
|
|
188
|
-
|
|
189
|
-
['uri', 'resourceUri'].forEach((k) => {
|
|
190
|
-
if (activity.kmsMessage[k] && !kro && activity.kmsMessage[k].includes('<KRO>')) {
|
|
191
|
-
throw new Error('encrypter: cannot determine kro');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (activity.kmsMessage[k]) {
|
|
195
|
-
activity.kmsMessage[k] = activity.kmsMessage[k].replace('<KRO>', kro);
|
|
196
|
-
// key may be a key or a key url
|
|
197
|
-
activity.kmsMessage[k] = activity.kmsMessage[k].replace('<KEYURL>', key.keyUrl || key);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
// If we made it this far and still don't have an encryption key, assume
|
|
202
|
-
// this is a conversation that is not encrypted and we're performing an
|
|
203
|
-
// action that should not encrypt it (e.g. `leave`)
|
|
204
|
-
else {
|
|
205
|
-
Reflect.deleteProperty(activity, 'kmsMessage');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
encryptVerbActivityWithKey: {
|
|
211
|
-
direction: 'outbound',
|
|
212
|
-
fn(ctx, key, activity) {
|
|
213
|
-
return ctx.transform('encryptVerbActivity', key, activity).then(() => {
|
|
214
|
-
key = key || activity[KEY];
|
|
215
|
-
activity.encryptionKeyUrl = key.uri || key;
|
|
216
|
-
});
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
encryptAddActivity: {
|
|
221
|
-
direction: 'outbound',
|
|
222
|
-
fn(ctx, key, activity) {
|
|
223
|
-
if (has(activity, 'object.objectType') && activity.object.objectType === 'reaction2') {
|
|
224
|
-
return ctx.transform('encryptVerbActivityWithKey', key, activity);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return ctx.transform('encryptVerbActivity', key, activity);
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
encryptAssignActivity: {
|
|
232
|
-
direction: 'outbound',
|
|
233
|
-
alias: 'encryptVerbActivityWithKey',
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
encryptCreateActivity: {
|
|
237
|
-
direction: 'outbound',
|
|
238
|
-
alias: 'encryptVerbActivity',
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
encryptPostActivity: {
|
|
242
|
-
direction: 'outbound',
|
|
243
|
-
alias: 'encryptVerbActivityWithKey',
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
encryptShareActivity: {
|
|
247
|
-
direction: 'outbound',
|
|
248
|
-
alias: 'encryptVerbActivityWithKey',
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
encryptCardactionActivity: {
|
|
252
|
-
direction: 'outbound',
|
|
253
|
-
alias: 'encryptVerbActivityWithKey',
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
encryptUpdateActivity: {
|
|
257
|
-
direction: 'outbound',
|
|
258
|
-
alias: 'encryptVerbActivityWithKey',
|
|
259
|
-
},
|
|
260
|
-
|
|
261
|
-
encryptUpdateKeyActivity: {
|
|
262
|
-
direction: 'outbound',
|
|
263
|
-
alias: 'encryptVerbActivity',
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
encryptComment(ctx, key, comment) {
|
|
267
|
-
return Promise.all([
|
|
268
|
-
ctx.transform('encryptPropDisplayName', key, comment),
|
|
269
|
-
ctx.transform('encryptPropContent', key, comment),
|
|
270
|
-
]);
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
encryptContent(ctx, key, content) {
|
|
274
|
-
const promises = content.files.items.map((item) => ctx.transform('encryptObject', key, item));
|
|
275
|
-
|
|
276
|
-
promises.push(ctx.transform('encryptPropContent', key, content));
|
|
277
|
-
promises.push(ctx.transform('encryptPropDisplayName', key, content));
|
|
278
|
-
|
|
279
|
-
return Promise.all(promises);
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
encryptFile(ctx, key, file) {
|
|
283
|
-
if (file.image && !file.image.scr) {
|
|
284
|
-
return Promise.reject(new Error('`file.image` must have an `scr`'));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return Promise.all([
|
|
288
|
-
ctx.transform('encryptPropScr', key, file),
|
|
289
|
-
ctx.transform('encryptPropDisplayName', key, file),
|
|
290
|
-
ctx.transform('encryptPropContent', key, file),
|
|
291
|
-
file.image && ctx.transform('encryptPropScr', key, file.image),
|
|
292
|
-
]);
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
encryptSubmit(ctx, key, submit) {
|
|
296
|
-
return ctx.transform('encryptPropInputs', key, submit);
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
// TODO is this used for anything other than the now-removed stickies service?
|
|
300
|
-
encryptImageURI(ctx, key, imageURI) {
|
|
301
|
-
return ctx.transform('encryptPropLocation', key, imageURI);
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
encryptPropContent: encryptTextProp('content'),
|
|
305
|
-
|
|
306
|
-
encryptPropDisplayName: encryptTextProp('displayName'),
|
|
307
|
-
|
|
308
|
-
encryptPropInputs: encryptJsonProp('inputs'),
|
|
309
|
-
|
|
310
|
-
encryptPropLocation: encryptTextProp('location'),
|
|
311
|
-
|
|
312
|
-
encryptPropScr(ctx, key, object) {
|
|
313
|
-
if (!object.scr) {
|
|
314
|
-
return Promise.resolve();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return ctx.webex.internal.encryption.encryptScr(key, object.scr).then((scr) => {
|
|
318
|
-
object.scr = scr;
|
|
319
|
-
});
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
encryptJsonProp(ctx, name, key, object) {
|
|
323
|
-
if (!object[name]) {
|
|
324
|
-
return Promise.resolve();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return ctx.webex.internal.encryption
|
|
328
|
-
.encryptText(key.uri || key, JSON.stringify(object[name]))
|
|
329
|
-
.then((ciphertext) => {
|
|
330
|
-
object[name] = ciphertext;
|
|
331
|
-
});
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
encryptTextProp(ctx, name, key, object) {
|
|
335
|
-
if (!object[name]) {
|
|
336
|
-
return Promise.resolve();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return ctx.webex.internal.encryption
|
|
340
|
-
.encryptText(key.uri || key, object[name])
|
|
341
|
-
.then((ciphertext) => {
|
|
342
|
-
object[name] = ciphertext;
|
|
343
|
-
});
|
|
344
|
-
},
|
|
345
|
-
});
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {capitalize, curry, get, has, isArray} from 'lodash';
|
|
6
|
+
|
|
7
|
+
import toArray from './to-array';
|
|
8
|
+
|
|
9
|
+
const KEY = Symbol('KEY');
|
|
10
|
+
|
|
11
|
+
const encryptTextProp = curry((name, ctx, key, object) =>
|
|
12
|
+
ctx.transform('encryptTextProp', name, key, object)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const encryptJsonProp = curry((name, ctx, key, object) =>
|
|
16
|
+
ctx.transform('encryptJsonProp', name, key, object)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
20
|
+
export const transforms = toArray('outbound', {
|
|
21
|
+
encryptObject(ctx, key, object) {
|
|
22
|
+
if (!object) {
|
|
23
|
+
object = key;
|
|
24
|
+
key = undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!object) {
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!object.objectType) {
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (key === false) {
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return ctx.transform(`encrypt${capitalize(object.objectType)}`, key, object);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
encryptReaction2(ctx, key, reaction2) {
|
|
43
|
+
return ctx.transform('encryptPropDisplayName', key, reaction2);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
encryptConversation(ctx, key, conversation) {
|
|
47
|
+
if (key === false) {
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Promise.resolve(
|
|
52
|
+
key || ctx.webex.internal.encryption.kms.createUnboundKeys({count: 1})
|
|
53
|
+
).then((keys) => {
|
|
54
|
+
const k = isArray(keys) ? keys[0] : keys;
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
has(conversation, 'kmsMessage.keyUris') &&
|
|
58
|
+
!conversation.kmsMessage.keyUris.includes(k.uri)
|
|
59
|
+
) {
|
|
60
|
+
conversation.kmsMessage.keyUris.push(k.uri);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Promise.all([
|
|
64
|
+
// too many implicit returns on the same line is difficult to interpret
|
|
65
|
+
// eslint-disable-next-line arrow-body-style
|
|
66
|
+
has(conversation, 'activities.items') &&
|
|
67
|
+
conversation.activities.items.reduce((p, activity) => {
|
|
68
|
+
// eslint-disable-next-line max-nested-callbacks
|
|
69
|
+
return p.then(() => ctx.transform('encryptObject', k, activity));
|
|
70
|
+
}, Promise.resolve()),
|
|
71
|
+
ctx.transform('encryptPropDisplayName', k, conversation),
|
|
72
|
+
]).then(() => {
|
|
73
|
+
conversation.encryptionKeyUrl = k.uri || k;
|
|
74
|
+
// we only want to set the defaultActivityEncryptionKeyUrl if we've
|
|
75
|
+
// bound a new key
|
|
76
|
+
if (!key) {
|
|
77
|
+
conversation.defaultActivityEncryptionKeyUrl =
|
|
78
|
+
conversation.defaultActivityEncryptionKeyUrl || k.uri || k;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
encryptActivity(ctx, key, activity) {
|
|
85
|
+
// Activity is already encrypted
|
|
86
|
+
if (activity.encryptionKeyUrl || activity.object?.created === 'True') {
|
|
87
|
+
return Promise.resolve();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return ctx.transform(`encrypt${capitalize(activity.verb)}Activity`, key, activity).then(() => {
|
|
91
|
+
key = key || activity[KEY];
|
|
92
|
+
|
|
93
|
+
return ctx.transform('prepareActivityKmsMessage', key, activity);
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
encryptVerbActivity(ctx, key, activity) {
|
|
98
|
+
return ctx
|
|
99
|
+
.transform('maybeEncryptTarget', key, activity)
|
|
100
|
+
.then(() => {
|
|
101
|
+
key = key || activity[KEY];
|
|
102
|
+
})
|
|
103
|
+
.then(() => ctx.transform('encryptObject', key, activity.object));
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
maybeEncryptTarget(ctx, key, activity) {
|
|
107
|
+
// This isn't quite right; if we just go by key, we have no guarantee that
|
|
108
|
+
// we have a proper KRO available for add activities
|
|
109
|
+
if (key) {
|
|
110
|
+
return Promise.resolve();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
has(activity, 'target.defaultActivityEncryptionKeyUrl') &&
|
|
115
|
+
activity.target.defaultActivityEncryptionKeyUrl &&
|
|
116
|
+
has(activity, 'target.kmsResourceObjectUrl')
|
|
117
|
+
) {
|
|
118
|
+
activity[KEY] = key || activity.target.defaultActivityEncryptionKeyUrl;
|
|
119
|
+
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const conversationUrl = activity.target && activity.target.url;
|
|
124
|
+
|
|
125
|
+
if (!conversationUrl) {
|
|
126
|
+
return Promise.reject(
|
|
127
|
+
new Error(
|
|
128
|
+
"Cannot determine encryption key for activity's conversation; no key url or conversation url provided"
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return ctx.webex.internal.conversation.get({url: conversationUrl}).then((conversation) => {
|
|
134
|
+
if (!conversation.defaultActivityEncryptionKeyUrl) {
|
|
135
|
+
return ctx.webex.internal.conversation.updateKey(conversation).then((updateKeyActivity) => {
|
|
136
|
+
if (updateKeyActivity.kmsMessage.resource) {
|
|
137
|
+
activity.target.kmsResourceObjectUrl = updateKeyActivity.kmsMessage.resource.uri;
|
|
138
|
+
}
|
|
139
|
+
activity[KEY] = activity.target.defaultActivityEncryptionKeyUrl =
|
|
140
|
+
updateKeyActivity.object.defaultActivityEncryptionKeyUrl;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!activity.target.defaultActivityEncryptionKeyUrl) {
|
|
145
|
+
ctx.webex.logger.warn(
|
|
146
|
+
'plugin-conversation: downloaded conversation to determine its defaultActivityEncryptionKeyUrl; make sure to pass all encryption related properties when calling Webex.conversation methods.'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!activity.target.kmsResourceObjectUrl) {
|
|
151
|
+
ctx.webex.logger.warn(
|
|
152
|
+
'plugin-conversation: downloaded conversation to determine its kmsResourceObjectUrl; make sure to pass all encryption related properties when calling Webex.conversation methods.'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
activity[KEY] = activity.target.defaultActivityEncryptionKeyUrl =
|
|
157
|
+
conversation.defaultActivityEncryptionKeyUrl;
|
|
158
|
+
activity.target.kmsResourceObjectUrl = conversation.kmsResourceObjectUrl;
|
|
159
|
+
|
|
160
|
+
return Promise.resolve();
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
prepareActivityKmsMessage(ctx, key, activity) {
|
|
165
|
+
if (activity.kmsMessage) {
|
|
166
|
+
if (!key && activity.verb === 'delete') {
|
|
167
|
+
key = get(activity, 'target.defaultActivityEncryptionKeyUrl');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
!key &&
|
|
172
|
+
activity.verb === 'updateKey' &&
|
|
173
|
+
has(activity, 'object.defaultActivityEncryptionKeyUrl')
|
|
174
|
+
) {
|
|
175
|
+
key = get(activity, 'object.defaultActivityEncryptionKeyUrl');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
!key &&
|
|
180
|
+
activity.verb === 'leave' &&
|
|
181
|
+
has(activity, 'target.defaultActivityEncryptionKeyUrl')
|
|
182
|
+
) {
|
|
183
|
+
key = get(activity, 'target.defaultActivityEncryptionKeyUrl');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (key) {
|
|
187
|
+
const kro = activity.target.kmsResourceObjectUrl;
|
|
188
|
+
|
|
189
|
+
['uri', 'resourceUri'].forEach((k) => {
|
|
190
|
+
if (activity.kmsMessage[k] && !kro && activity.kmsMessage[k].includes('<KRO>')) {
|
|
191
|
+
throw new Error('encrypter: cannot determine kro');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (activity.kmsMessage[k]) {
|
|
195
|
+
activity.kmsMessage[k] = activity.kmsMessage[k].replace('<KRO>', kro);
|
|
196
|
+
// key may be a key or a key url
|
|
197
|
+
activity.kmsMessage[k] = activity.kmsMessage[k].replace('<KEYURL>', key.keyUrl || key);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// If we made it this far and still don't have an encryption key, assume
|
|
202
|
+
// this is a conversation that is not encrypted and we're performing an
|
|
203
|
+
// action that should not encrypt it (e.g. `leave`)
|
|
204
|
+
else {
|
|
205
|
+
Reflect.deleteProperty(activity, 'kmsMessage');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
encryptVerbActivityWithKey: {
|
|
211
|
+
direction: 'outbound',
|
|
212
|
+
fn(ctx, key, activity) {
|
|
213
|
+
return ctx.transform('encryptVerbActivity', key, activity).then(() => {
|
|
214
|
+
key = key || activity[KEY];
|
|
215
|
+
activity.encryptionKeyUrl = key.uri || key;
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
encryptAddActivity: {
|
|
221
|
+
direction: 'outbound',
|
|
222
|
+
fn(ctx, key, activity) {
|
|
223
|
+
if (has(activity, 'object.objectType') && activity.object.objectType === 'reaction2') {
|
|
224
|
+
return ctx.transform('encryptVerbActivityWithKey', key, activity);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return ctx.transform('encryptVerbActivity', key, activity);
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
encryptAssignActivity: {
|
|
232
|
+
direction: 'outbound',
|
|
233
|
+
alias: 'encryptVerbActivityWithKey',
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
encryptCreateActivity: {
|
|
237
|
+
direction: 'outbound',
|
|
238
|
+
alias: 'encryptVerbActivity',
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
encryptPostActivity: {
|
|
242
|
+
direction: 'outbound',
|
|
243
|
+
alias: 'encryptVerbActivityWithKey',
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
encryptShareActivity: {
|
|
247
|
+
direction: 'outbound',
|
|
248
|
+
alias: 'encryptVerbActivityWithKey',
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
encryptCardactionActivity: {
|
|
252
|
+
direction: 'outbound',
|
|
253
|
+
alias: 'encryptVerbActivityWithKey',
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
encryptUpdateActivity: {
|
|
257
|
+
direction: 'outbound',
|
|
258
|
+
alias: 'encryptVerbActivityWithKey',
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
encryptUpdateKeyActivity: {
|
|
262
|
+
direction: 'outbound',
|
|
263
|
+
alias: 'encryptVerbActivity',
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
encryptComment(ctx, key, comment) {
|
|
267
|
+
return Promise.all([
|
|
268
|
+
ctx.transform('encryptPropDisplayName', key, comment),
|
|
269
|
+
ctx.transform('encryptPropContent', key, comment),
|
|
270
|
+
]);
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
encryptContent(ctx, key, content) {
|
|
274
|
+
const promises = content.files.items.map((item) => ctx.transform('encryptObject', key, item));
|
|
275
|
+
|
|
276
|
+
promises.push(ctx.transform('encryptPropContent', key, content));
|
|
277
|
+
promises.push(ctx.transform('encryptPropDisplayName', key, content));
|
|
278
|
+
|
|
279
|
+
return Promise.all(promises);
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
encryptFile(ctx, key, file) {
|
|
283
|
+
if (file.image && !file.image.scr) {
|
|
284
|
+
return Promise.reject(new Error('`file.image` must have an `scr`'));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return Promise.all([
|
|
288
|
+
ctx.transform('encryptPropScr', key, file),
|
|
289
|
+
ctx.transform('encryptPropDisplayName', key, file),
|
|
290
|
+
ctx.transform('encryptPropContent', key, file),
|
|
291
|
+
file.image && ctx.transform('encryptPropScr', key, file.image),
|
|
292
|
+
]);
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
encryptSubmit(ctx, key, submit) {
|
|
296
|
+
return ctx.transform('encryptPropInputs', key, submit);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// TODO is this used for anything other than the now-removed stickies service?
|
|
300
|
+
encryptImageURI(ctx, key, imageURI) {
|
|
301
|
+
return ctx.transform('encryptPropLocation', key, imageURI);
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
encryptPropContent: encryptTextProp('content'),
|
|
305
|
+
|
|
306
|
+
encryptPropDisplayName: encryptTextProp('displayName'),
|
|
307
|
+
|
|
308
|
+
encryptPropInputs: encryptJsonProp('inputs'),
|
|
309
|
+
|
|
310
|
+
encryptPropLocation: encryptTextProp('location'),
|
|
311
|
+
|
|
312
|
+
encryptPropScr(ctx, key, object) {
|
|
313
|
+
if (!object.scr) {
|
|
314
|
+
return Promise.resolve();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return ctx.webex.internal.encryption.encryptScr(key, object.scr).then((scr) => {
|
|
318
|
+
object.scr = scr;
|
|
319
|
+
});
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
encryptJsonProp(ctx, name, key, object) {
|
|
323
|
+
if (!object[name]) {
|
|
324
|
+
return Promise.resolve();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return ctx.webex.internal.encryption
|
|
328
|
+
.encryptText(key.uri || key, JSON.stringify(object[name]))
|
|
329
|
+
.then((ciphertext) => {
|
|
330
|
+
object[name] = ciphertext;
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
encryptTextProp(ctx, name, key, object) {
|
|
335
|
+
if (!object[name]) {
|
|
336
|
+
return Promise.resolve();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return ctx.webex.internal.encryption
|
|
340
|
+
.encryptText(key.uri || key, object[name])
|
|
341
|
+
.then((ciphertext) => {
|
|
342
|
+
object[name] = ciphertext;
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
});
|