@webex/internal-plugin-encryption 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.
- package/.eslintrc.js +6 -6
- package/README.md +42 -42
- package/babel.config.js +3 -3
- package/dist/config.js +21 -21
- package/dist/config.js.map +1 -1
- package/dist/encryption.js +57 -57
- package/dist/encryption.js.map +1 -1
- package/dist/ensure-buffer.browser.js +7 -7
- package/dist/ensure-buffer.browser.js.map +1 -1
- package/dist/ensure-buffer.js +7 -7
- package/dist/ensure-buffer.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/kms-batcher.js +38 -38
- package/dist/kms-batcher.js.map +1 -1
- package/dist/kms-certificate-validation.js +50 -50
- package/dist/kms-certificate-validation.js.map +1 -1
- package/dist/kms-dry-error-interceptor.js +15 -15
- package/dist/kms-dry-error-interceptor.js.map +1 -1
- package/dist/kms-errors.js +16 -16
- package/dist/kms-errors.js.map +1 -1
- package/dist/kms.js +171 -171
- package/dist/kms.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +20 -19
- package/process +1 -1
- package/src/config.js +50 -50
- package/src/encryption.js +257 -257
- package/src/ensure-buffer.browser.js +37 -37
- package/src/ensure-buffer.js +20 -20
- package/src/index.js +159 -159
- package/src/kms-batcher.js +158 -158
- package/src/kms-certificate-validation.js +232 -232
- package/src/kms-dry-error-interceptor.js +65 -65
- package/src/kms-errors.js +147 -147
- package/src/kms.js +848 -848
- package/test/integration/spec/encryption.js +448 -448
- package/test/integration/spec/kms.js +800 -800
- package/test/integration/spec/payload-transfom.js +97 -97
- package/test/unit/spec/encryption.js +82 -82
- package/test/unit/spec/kms-certificate-validation.js +165 -165
- package/test/unit/spec/kms.js +103 -103
|
@@ -1,448 +1,448 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import '@webex/internal-plugin-encryption';
|
|
6
|
-
|
|
7
|
-
import {isBuffer} from '@webex/common';
|
|
8
|
-
import {assert, expect} from '@webex/test-helper-chai';
|
|
9
|
-
import file from '@webex/test-helper-file';
|
|
10
|
-
import sinon from 'sinon';
|
|
11
|
-
import WebexCore from '@webex/webex-core';
|
|
12
|
-
import testUsers from '@webex/test-helper-test-users';
|
|
13
|
-
import makeLocalUrl from '@webex/test-helper-make-local-url';
|
|
14
|
-
|
|
15
|
-
describe('Encryption', function () {
|
|
16
|
-
this.timeout(30000);
|
|
17
|
-
|
|
18
|
-
let key, user, webex;
|
|
19
|
-
|
|
20
|
-
const PLAINTEXT =
|
|
21
|
-
'Admiral, if we go "by the book". like Lieutenant Saavik, hours could seem like days.';
|
|
22
|
-
let FILE = makeLocalUrl('/sample-image-small-one.png');
|
|
23
|
-
|
|
24
|
-
before('create test user', () =>
|
|
25
|
-
testUsers.create({count: 1}).then((users) => {
|
|
26
|
-
user = users[0];
|
|
27
|
-
webex = new WebexCore({
|
|
28
|
-
credentials: {
|
|
29
|
-
authorization: user.token,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
assert.isTrue(webex.isAuthenticated || webex.canAuthorize);
|
|
33
|
-
})
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
before('create unbound key', () =>
|
|
37
|
-
webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([k]) => {
|
|
38
|
-
key = k;
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
before('fetch file fixture', () =>
|
|
43
|
-
webex
|
|
44
|
-
.request({
|
|
45
|
-
uri: FILE,
|
|
46
|
-
responseType: 'buffer',
|
|
47
|
-
})
|
|
48
|
-
.then((res) => {
|
|
49
|
-
FILE = res.body;
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
after(() => webex && webex.internal.mercury.disconnect());
|
|
54
|
-
|
|
55
|
-
describe('#decryptBinary()', () => {
|
|
56
|
-
it('decrypts a binary file', () =>
|
|
57
|
-
webex.internal.encryption.encryptBinary(FILE).then(({scr, cdata}) => {
|
|
58
|
-
scr.loc = 'file:///file.enc';
|
|
59
|
-
|
|
60
|
-
return webex.internal.encryption
|
|
61
|
-
.encryptScr(key, scr)
|
|
62
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
63
|
-
.then((decryptedScr) => webex.internal.encryption.decryptBinary(decryptedScr, cdata))
|
|
64
|
-
.then((f) => {
|
|
65
|
-
assert.isTrue(isBuffer(f));
|
|
66
|
-
|
|
67
|
-
return assert.equal(f.byteLength, FILE.byteLength);
|
|
68
|
-
});
|
|
69
|
-
}));
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('#decryptScr()', () => {
|
|
73
|
-
it('decrypts an scr', () =>
|
|
74
|
-
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
75
|
-
scr.loc = 'file:///file.enc';
|
|
76
|
-
|
|
77
|
-
return webex.internal.encryption
|
|
78
|
-
.encryptScr(key, scr)
|
|
79
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
80
|
-
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
81
|
-
}));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('#decryptText()', () => {
|
|
85
|
-
it('decrypts text', () =>
|
|
86
|
-
webex.internal.encryption
|
|
87
|
-
.encryptText(key, PLAINTEXT)
|
|
88
|
-
.then((ciphertext) => {
|
|
89
|
-
assert.notEqual(ciphertext, PLAINTEXT);
|
|
90
|
-
|
|
91
|
-
return webex.internal.encryption.decryptText(key, ciphertext);
|
|
92
|
-
})
|
|
93
|
-
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('#getKey()', () => {
|
|
97
|
-
let fetchKeySpy, otherWebex, otherUser, storageGetSpy;
|
|
98
|
-
|
|
99
|
-
before('create test user', () =>
|
|
100
|
-
testUsers.create({count: 1}).then((users) => {
|
|
101
|
-
otherUser = users[0];
|
|
102
|
-
otherWebex = new WebexCore({
|
|
103
|
-
credentials: {
|
|
104
|
-
authorization: otherUser.token,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
assert.isTrue(otherWebex.canAuthorize);
|
|
108
|
-
})
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
before('create kms resource', () =>
|
|
112
|
-
webex.internal.encryption.kms.createResource({
|
|
113
|
-
key,
|
|
114
|
-
userIds: [webex.internal.device.userId, otherUser.id],
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
after(() => otherWebex && otherWebex.internal.mercury.disconnect());
|
|
119
|
-
|
|
120
|
-
beforeEach(() => {
|
|
121
|
-
fetchKeySpy = sinon.spy(otherWebex.internal.encryption.kms, 'fetchKey');
|
|
122
|
-
storageGetSpy = sinon.spy(otherWebex.internal.encryption.unboundedStorage, 'get');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
afterEach(() => {
|
|
126
|
-
fetchKeySpy.restore();
|
|
127
|
-
storageGetSpy.restore();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('shortcircuits if it receives a key instead of a keyUri', () =>
|
|
131
|
-
webex.internal.encryption
|
|
132
|
-
.getKey(key)
|
|
133
|
-
// Reminder: If this starts failing after a node-jose upgrade, it probably
|
|
134
|
-
// implies node-jose stopped shortcircuiting correctly.
|
|
135
|
-
.then((k) => assert.equal(k, key)));
|
|
136
|
-
|
|
137
|
-
it('attempts to retrieve the specified key from the local cache', () =>
|
|
138
|
-
otherWebex.internal.encryption
|
|
139
|
-
.getKey(key.uri)
|
|
140
|
-
.then((k) => assert.calledWith(storageGetSpy, k.uri)));
|
|
141
|
-
|
|
142
|
-
it('fetches the key from the kms', () =>
|
|
143
|
-
otherWebex.internal.encryption.unboundedStorage
|
|
144
|
-
.del(key.uri)
|
|
145
|
-
.then(() => assert.notCalled(fetchKeySpy))
|
|
146
|
-
.then(() => otherWebex.internal.encryption.getKey(key.uri))
|
|
147
|
-
.then(() => assert.calledOnce(fetchKeySpy)));
|
|
148
|
-
|
|
149
|
-
it('stores the newly retrieved key', () =>
|
|
150
|
-
otherWebex.internal.encryption
|
|
151
|
-
.getKey(key.uri)
|
|
152
|
-
.then((k) => otherWebex.internal.encryption.unboundedStorage.get(k.uri))
|
|
153
|
-
.then((str) => JSON.parse(str))
|
|
154
|
-
.then((k2) => {
|
|
155
|
-
assert.property(k2, 'jwk');
|
|
156
|
-
assert.property(k2.jwk, 'k');
|
|
157
|
-
assert.equal(key.jwk.kid, k2.jwk.kid);
|
|
158
|
-
}));
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('#download()', () => {
|
|
162
|
-
it('downloads and decrypts an encrypted file', () =>
|
|
163
|
-
webex.internal.encryption
|
|
164
|
-
.encryptBinary(FILE)
|
|
165
|
-
.then(({scr, cdata}) =>
|
|
166
|
-
webex
|
|
167
|
-
.request({
|
|
168
|
-
method: 'POST',
|
|
169
|
-
uri: makeLocalUrl('/files/upload'),
|
|
170
|
-
body: cdata,
|
|
171
|
-
})
|
|
172
|
-
.then((res) => {
|
|
173
|
-
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
174
|
-
|
|
175
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
176
|
-
})
|
|
177
|
-
)
|
|
178
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
179
|
-
.then((scr) => webex.internal.encryption.download(scr))
|
|
180
|
-
.then((f) =>
|
|
181
|
-
file.isMatchingFile(f, FILE).then((result) => assert.deepEqual(result, true))
|
|
182
|
-
));
|
|
183
|
-
|
|
184
|
-
it('downloads and decrypts an encrypted file with options param', () =>
|
|
185
|
-
webex.internal.encryption
|
|
186
|
-
.encryptBinary(FILE)
|
|
187
|
-
.then(({scr, cdata}) =>
|
|
188
|
-
webex
|
|
189
|
-
.request({
|
|
190
|
-
method: 'POST',
|
|
191
|
-
uri: makeLocalUrl('/files/upload'),
|
|
192
|
-
body: cdata,
|
|
193
|
-
})
|
|
194
|
-
.then((res) => {
|
|
195
|
-
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
196
|
-
|
|
197
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
198
|
-
})
|
|
199
|
-
)
|
|
200
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
201
|
-
.then((scr) => {
|
|
202
|
-
const options = {
|
|
203
|
-
params: {
|
|
204
|
-
allow: 'none',
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
return webex.internal.encryption.download(scr, options);
|
|
209
|
-
})
|
|
210
|
-
.then((f) => file.isMatchingFile(f, FILE))
|
|
211
|
-
.then((result) => assert.deepEqual(result, true)));
|
|
212
|
-
|
|
213
|
-
it('emits progress events', () => {
|
|
214
|
-
const spy = sinon.spy();
|
|
215
|
-
|
|
216
|
-
return webex.internal.encryption
|
|
217
|
-
.encryptBinary(FILE)
|
|
218
|
-
.then(({scr, cdata}) =>
|
|
219
|
-
webex
|
|
220
|
-
.request({
|
|
221
|
-
method: 'POST',
|
|
222
|
-
uri: makeLocalUrl('/files/upload'),
|
|
223
|
-
body: cdata,
|
|
224
|
-
})
|
|
225
|
-
.then((res) => {
|
|
226
|
-
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
227
|
-
|
|
228
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
229
|
-
})
|
|
230
|
-
)
|
|
231
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
232
|
-
.then((scr) => webex.internal.encryption.download(scr).on('progress', spy))
|
|
233
|
-
.then(() => assert.called(spy));
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('checks body of the API call /downloads/endpoints', () =>
|
|
237
|
-
webex.internal.encryption
|
|
238
|
-
.encryptBinary(FILE)
|
|
239
|
-
.then(({scr, cdata}) =>
|
|
240
|
-
webex
|
|
241
|
-
.request({
|
|
242
|
-
method: 'POST',
|
|
243
|
-
uri: makeLocalUrl('/files/upload'),
|
|
244
|
-
body: cdata,
|
|
245
|
-
})
|
|
246
|
-
.then((res) => {
|
|
247
|
-
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
248
|
-
|
|
249
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
250
|
-
})
|
|
251
|
-
)
|
|
252
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
253
|
-
.then((scr) => {
|
|
254
|
-
const options = {
|
|
255
|
-
params: {
|
|
256
|
-
allow: ['unchecked', 'evaluating'],
|
|
257
|
-
},
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
return webex.internal.encryption.download(scr, options);
|
|
261
|
-
})
|
|
262
|
-
.then((f) => file.isMatchingFile(f, FILE))
|
|
263
|
-
.then((result) => assert.deepEqual(result, true)));
|
|
264
|
-
|
|
265
|
-
it('checks _fetchDownloadUrl()', () =>
|
|
266
|
-
webex.internal.encryption
|
|
267
|
-
.encryptBinary(FILE)
|
|
268
|
-
.then(({scr, cdata}) =>
|
|
269
|
-
webex
|
|
270
|
-
.request({
|
|
271
|
-
method: 'POST',
|
|
272
|
-
uri: makeLocalUrl('/files/upload'),
|
|
273
|
-
body: cdata,
|
|
274
|
-
})
|
|
275
|
-
.then((res) => {
|
|
276
|
-
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
277
|
-
|
|
278
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
279
|
-
})
|
|
280
|
-
)
|
|
281
|
-
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
282
|
-
.then((scr) => {
|
|
283
|
-
const options = {
|
|
284
|
-
params: {
|
|
285
|
-
allow: ['unchecked', 'evaluating'],
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
return webex.internal.encryption._fetchDownloadUrl(scr, options);
|
|
290
|
-
})
|
|
291
|
-
.then((result) => assert.isString(result)));
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('#encryptBinary()', () => {
|
|
295
|
-
it('encrypts a binary file', () =>
|
|
296
|
-
webex.internal.encryption.encryptBinary(FILE).then(({scr, cdata}) => {
|
|
297
|
-
assert.property(scr, 'enc');
|
|
298
|
-
assert.property(scr, 'key');
|
|
299
|
-
assert.property(scr, 'iv');
|
|
300
|
-
|
|
301
|
-
return assert.isBufferLike(cdata);
|
|
302
|
-
}));
|
|
303
|
-
|
|
304
|
-
// browserOnly(it)(`accepts an ArrayBuffer`);
|
|
305
|
-
// browserOnly(it)(`accepts a Blob`);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe('#encryptScr()', () => {
|
|
309
|
-
it('encrypts an scr', () =>
|
|
310
|
-
webex.internal.encryption
|
|
311
|
-
.encryptBinary(FILE)
|
|
312
|
-
.then(({scr}) => {
|
|
313
|
-
scr.loc = 'file:///file.enc';
|
|
314
|
-
|
|
315
|
-
return webex.internal.encryption.encryptScr(key, scr);
|
|
316
|
-
})
|
|
317
|
-
.then((cipherScr) => assert.isString(cipherScr)));
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('#encryptText()', () => {
|
|
321
|
-
it('encrypts text', () =>
|
|
322
|
-
webex.internal.encryption
|
|
323
|
-
.encryptText(key, PLAINTEXT)
|
|
324
|
-
.then((ciphertext) => assert.notEqual(ciphertext, PLAINTEXT)));
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe('#onBehalfOf', () => {
|
|
328
|
-
let complianceUser;
|
|
329
|
-
|
|
330
|
-
before('create compliance officer test user', () =>
|
|
331
|
-
testUsers
|
|
332
|
-
.create({
|
|
333
|
-
count: 1,
|
|
334
|
-
config: {
|
|
335
|
-
roles: [{name: 'spark.kms_orgagent'}],
|
|
336
|
-
},
|
|
337
|
-
})
|
|
338
|
-
.then((users) => {
|
|
339
|
-
complianceUser = users[0];
|
|
340
|
-
complianceUser.webex = new WebexCore({
|
|
341
|
-
credentials: {
|
|
342
|
-
authorization: complianceUser.token,
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
assert.isTrue(complianceUser.webex.canAuthorize);
|
|
346
|
-
})
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
after(() => complianceUser && complianceUser.webex.internal.mercury.disconnect());
|
|
350
|
-
|
|
351
|
-
it('decrypt text', () =>
|
|
352
|
-
webex.internal.encryption
|
|
353
|
-
.encryptText(key, PLAINTEXT)
|
|
354
|
-
.then((ciphertext) => {
|
|
355
|
-
assert.notEqual(ciphertext, PLAINTEXT);
|
|
356
|
-
|
|
357
|
-
return complianceUser.webex.internal.encryption.decryptText(key, ciphertext, {
|
|
358
|
-
onBehalfOf: user.id,
|
|
359
|
-
});
|
|
360
|
-
})
|
|
361
|
-
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
362
|
-
|
|
363
|
-
it('encrypt and decrypt text', () =>
|
|
364
|
-
complianceUser.webex.internal.encryption
|
|
365
|
-
.encryptText(key, PLAINTEXT, {onBehalfOf: user.id})
|
|
366
|
-
.then((ciphertext) => {
|
|
367
|
-
assert.notEqual(ciphertext, PLAINTEXT);
|
|
368
|
-
|
|
369
|
-
return complianceUser.webex.internal.encryption.decryptText(key, ciphertext, {
|
|
370
|
-
onBehalfOf: user.id,
|
|
371
|
-
});
|
|
372
|
-
})
|
|
373
|
-
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
374
|
-
|
|
375
|
-
it('decrypt scr', () =>
|
|
376
|
-
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
377
|
-
scr.loc = 'file:///file.enc';
|
|
378
|
-
|
|
379
|
-
return webex.internal.encryption
|
|
380
|
-
.encryptScr(key, scr)
|
|
381
|
-
.then((cipherScr) =>
|
|
382
|
-
complianceUser.webex.internal.encryption.decryptScr(key, cipherScr, {
|
|
383
|
-
onBehalfOf: user.id,
|
|
384
|
-
})
|
|
385
|
-
)
|
|
386
|
-
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
387
|
-
}));
|
|
388
|
-
|
|
389
|
-
it('decrypt scr', () =>
|
|
390
|
-
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
391
|
-
scr.loc = 'file:///file.enc';
|
|
392
|
-
|
|
393
|
-
return complianceUser.webex.internal.encryption
|
|
394
|
-
.encryptScr(key, scr, {onBehalfOf: user.id})
|
|
395
|
-
.then((cipherScr) =>
|
|
396
|
-
complianceUser.webex.internal.encryption.decryptScr(key, cipherScr, {
|
|
397
|
-
onBehalfOf: user.id,
|
|
398
|
-
})
|
|
399
|
-
)
|
|
400
|
-
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
401
|
-
}));
|
|
402
|
-
|
|
403
|
-
it('getKey', () =>
|
|
404
|
-
complianceUser.webex.internal.encryption
|
|
405
|
-
.getKey(key.uri, {onBehalfOf: user.id})
|
|
406
|
-
.then((key2) => {
|
|
407
|
-
assert.property(key2, 'uri');
|
|
408
|
-
assert.property(key2, 'jwk');
|
|
409
|
-
assert.notEqual(key2, key);
|
|
410
|
-
assert.equal(key2.uri, key.uri);
|
|
411
|
-
}));
|
|
412
|
-
|
|
413
|
-
it('getKey forbidden as compliance officer does not have access', () =>
|
|
414
|
-
complianceUser.webex.internal.encryption.getKey(key.uri).then(
|
|
415
|
-
(value) => expect.fail(`Compliance officer has retrieved key without onBehalfOf: ${value}`),
|
|
416
|
-
(error) => expect(error.body.status).to.equal(403)
|
|
417
|
-
));
|
|
418
|
-
|
|
419
|
-
it('getKey forbidden as user does not have access', () =>
|
|
420
|
-
complianceUser.webex.internal.encryption
|
|
421
|
-
.getKey(key.uri, {onBehalfOf: '7851fe79-7c87-40cc-ac36-8b77b011b399'})
|
|
422
|
-
.then(
|
|
423
|
-
(value) =>
|
|
424
|
-
expect.fail(
|
|
425
|
-
`Should not be found as 7851fe79-7c87-40cc-ac36-8b77b011b399 does not have access ${value}`
|
|
426
|
-
),
|
|
427
|
-
(error) => expect(error.body.status).to.equal(403)
|
|
428
|
-
));
|
|
429
|
-
|
|
430
|
-
it('getKey onBehalfOf and then by compliance officer only', () =>
|
|
431
|
-
complianceUser.webex.internal.encryption
|
|
432
|
-
.getKey(key.uri, {onBehalfOf: user.id})
|
|
433
|
-
.then((key2) => {
|
|
434
|
-
assert.property(key2, 'uri');
|
|
435
|
-
assert.property(key2, 'jwk');
|
|
436
|
-
assert.notEqual(key2, key);
|
|
437
|
-
assert.equal(key2.uri, key.uri);
|
|
438
|
-
})
|
|
439
|
-
.then(() => complianceUser.webex.internal.encryption.getKey(key.uri))
|
|
440
|
-
.then(
|
|
441
|
-
(value) =>
|
|
442
|
-
expect.fail(
|
|
443
|
-
`Compliance should no longer be able to retrieve key as onBehalfOf was not set: ${value}`
|
|
444
|
-
),
|
|
445
|
-
(error) => expect(error.body.status).to.equal(403)
|
|
446
|
-
));
|
|
447
|
-
});
|
|
448
|
-
});
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import '@webex/internal-plugin-encryption';
|
|
6
|
+
|
|
7
|
+
import {isBuffer} from '@webex/common';
|
|
8
|
+
import {assert, expect} from '@webex/test-helper-chai';
|
|
9
|
+
import file from '@webex/test-helper-file';
|
|
10
|
+
import sinon from 'sinon';
|
|
11
|
+
import WebexCore from '@webex/webex-core';
|
|
12
|
+
import testUsers from '@webex/test-helper-test-users';
|
|
13
|
+
import makeLocalUrl from '@webex/test-helper-make-local-url';
|
|
14
|
+
|
|
15
|
+
describe('Encryption', function () {
|
|
16
|
+
this.timeout(30000);
|
|
17
|
+
|
|
18
|
+
let key, user, webex;
|
|
19
|
+
|
|
20
|
+
const PLAINTEXT =
|
|
21
|
+
'Admiral, if we go "by the book". like Lieutenant Saavik, hours could seem like days.';
|
|
22
|
+
let FILE = makeLocalUrl('/sample-image-small-one.png');
|
|
23
|
+
|
|
24
|
+
before('create test user', () =>
|
|
25
|
+
testUsers.create({count: 1}).then((users) => {
|
|
26
|
+
user = users[0];
|
|
27
|
+
webex = new WebexCore({
|
|
28
|
+
credentials: {
|
|
29
|
+
authorization: user.token,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
assert.isTrue(webex.isAuthenticated || webex.canAuthorize);
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
before('create unbound key', () =>
|
|
37
|
+
webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([k]) => {
|
|
38
|
+
key = k;
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
before('fetch file fixture', () =>
|
|
43
|
+
webex
|
|
44
|
+
.request({
|
|
45
|
+
uri: FILE,
|
|
46
|
+
responseType: 'buffer',
|
|
47
|
+
})
|
|
48
|
+
.then((res) => {
|
|
49
|
+
FILE = res.body;
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
after(() => webex && webex.internal.mercury.disconnect());
|
|
54
|
+
|
|
55
|
+
describe('#decryptBinary()', () => {
|
|
56
|
+
it('decrypts a binary file', () =>
|
|
57
|
+
webex.internal.encryption.encryptBinary(FILE).then(({scr, cdata}) => {
|
|
58
|
+
scr.loc = 'file:///file.enc';
|
|
59
|
+
|
|
60
|
+
return webex.internal.encryption
|
|
61
|
+
.encryptScr(key, scr)
|
|
62
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
63
|
+
.then((decryptedScr) => webex.internal.encryption.decryptBinary(decryptedScr, cdata))
|
|
64
|
+
.then((f) => {
|
|
65
|
+
assert.isTrue(isBuffer(f));
|
|
66
|
+
|
|
67
|
+
return assert.equal(f.byteLength, FILE.byteLength);
|
|
68
|
+
});
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('#decryptScr()', () => {
|
|
73
|
+
it('decrypts an scr', () =>
|
|
74
|
+
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
75
|
+
scr.loc = 'file:///file.enc';
|
|
76
|
+
|
|
77
|
+
return webex.internal.encryption
|
|
78
|
+
.encryptScr(key, scr)
|
|
79
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
80
|
+
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('#decryptText()', () => {
|
|
85
|
+
it('decrypts text', () =>
|
|
86
|
+
webex.internal.encryption
|
|
87
|
+
.encryptText(key, PLAINTEXT)
|
|
88
|
+
.then((ciphertext) => {
|
|
89
|
+
assert.notEqual(ciphertext, PLAINTEXT);
|
|
90
|
+
|
|
91
|
+
return webex.internal.encryption.decryptText(key, ciphertext);
|
|
92
|
+
})
|
|
93
|
+
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('#getKey()', () => {
|
|
97
|
+
let fetchKeySpy, otherWebex, otherUser, storageGetSpy;
|
|
98
|
+
|
|
99
|
+
before('create test user', () =>
|
|
100
|
+
testUsers.create({count: 1}).then((users) => {
|
|
101
|
+
otherUser = users[0];
|
|
102
|
+
otherWebex = new WebexCore({
|
|
103
|
+
credentials: {
|
|
104
|
+
authorization: otherUser.token,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
assert.isTrue(otherWebex.canAuthorize);
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
before('create kms resource', () =>
|
|
112
|
+
webex.internal.encryption.kms.createResource({
|
|
113
|
+
key,
|
|
114
|
+
userIds: [webex.internal.device.userId, otherUser.id],
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
after(() => otherWebex && otherWebex.internal.mercury.disconnect());
|
|
119
|
+
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
fetchKeySpy = sinon.spy(otherWebex.internal.encryption.kms, 'fetchKey');
|
|
122
|
+
storageGetSpy = sinon.spy(otherWebex.internal.encryption.unboundedStorage, 'get');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
fetchKeySpy.restore();
|
|
127
|
+
storageGetSpy.restore();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('shortcircuits if it receives a key instead of a keyUri', () =>
|
|
131
|
+
webex.internal.encryption
|
|
132
|
+
.getKey(key)
|
|
133
|
+
// Reminder: If this starts failing after a node-jose upgrade, it probably
|
|
134
|
+
// implies node-jose stopped shortcircuiting correctly.
|
|
135
|
+
.then((k) => assert.equal(k, key)));
|
|
136
|
+
|
|
137
|
+
it('attempts to retrieve the specified key from the local cache', () =>
|
|
138
|
+
otherWebex.internal.encryption
|
|
139
|
+
.getKey(key.uri)
|
|
140
|
+
.then((k) => assert.calledWith(storageGetSpy, k.uri)));
|
|
141
|
+
|
|
142
|
+
it('fetches the key from the kms', () =>
|
|
143
|
+
otherWebex.internal.encryption.unboundedStorage
|
|
144
|
+
.del(key.uri)
|
|
145
|
+
.then(() => assert.notCalled(fetchKeySpy))
|
|
146
|
+
.then(() => otherWebex.internal.encryption.getKey(key.uri))
|
|
147
|
+
.then(() => assert.calledOnce(fetchKeySpy)));
|
|
148
|
+
|
|
149
|
+
it('stores the newly retrieved key', () =>
|
|
150
|
+
otherWebex.internal.encryption
|
|
151
|
+
.getKey(key.uri)
|
|
152
|
+
.then((k) => otherWebex.internal.encryption.unboundedStorage.get(k.uri))
|
|
153
|
+
.then((str) => JSON.parse(str))
|
|
154
|
+
.then((k2) => {
|
|
155
|
+
assert.property(k2, 'jwk');
|
|
156
|
+
assert.property(k2.jwk, 'k');
|
|
157
|
+
assert.equal(key.jwk.kid, k2.jwk.kid);
|
|
158
|
+
}));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('#download()', () => {
|
|
162
|
+
it('downloads and decrypts an encrypted file', () =>
|
|
163
|
+
webex.internal.encryption
|
|
164
|
+
.encryptBinary(FILE)
|
|
165
|
+
.then(({scr, cdata}) =>
|
|
166
|
+
webex
|
|
167
|
+
.request({
|
|
168
|
+
method: 'POST',
|
|
169
|
+
uri: makeLocalUrl('/files/upload'),
|
|
170
|
+
body: cdata,
|
|
171
|
+
})
|
|
172
|
+
.then((res) => {
|
|
173
|
+
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
174
|
+
|
|
175
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
176
|
+
})
|
|
177
|
+
)
|
|
178
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
179
|
+
.then((scr) => webex.internal.encryption.download(scr))
|
|
180
|
+
.then((f) =>
|
|
181
|
+
file.isMatchingFile(f, FILE).then((result) => assert.deepEqual(result, true))
|
|
182
|
+
));
|
|
183
|
+
|
|
184
|
+
it('downloads and decrypts an encrypted file with options param', () =>
|
|
185
|
+
webex.internal.encryption
|
|
186
|
+
.encryptBinary(FILE)
|
|
187
|
+
.then(({scr, cdata}) =>
|
|
188
|
+
webex
|
|
189
|
+
.request({
|
|
190
|
+
method: 'POST',
|
|
191
|
+
uri: makeLocalUrl('/files/upload'),
|
|
192
|
+
body: cdata,
|
|
193
|
+
})
|
|
194
|
+
.then((res) => {
|
|
195
|
+
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
196
|
+
|
|
197
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
201
|
+
.then((scr) => {
|
|
202
|
+
const options = {
|
|
203
|
+
params: {
|
|
204
|
+
allow: 'none',
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return webex.internal.encryption.download(scr, options);
|
|
209
|
+
})
|
|
210
|
+
.then((f) => file.isMatchingFile(f, FILE))
|
|
211
|
+
.then((result) => assert.deepEqual(result, true)));
|
|
212
|
+
|
|
213
|
+
it('emits progress events', () => {
|
|
214
|
+
const spy = sinon.spy();
|
|
215
|
+
|
|
216
|
+
return webex.internal.encryption
|
|
217
|
+
.encryptBinary(FILE)
|
|
218
|
+
.then(({scr, cdata}) =>
|
|
219
|
+
webex
|
|
220
|
+
.request({
|
|
221
|
+
method: 'POST',
|
|
222
|
+
uri: makeLocalUrl('/files/upload'),
|
|
223
|
+
body: cdata,
|
|
224
|
+
})
|
|
225
|
+
.then((res) => {
|
|
226
|
+
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
227
|
+
|
|
228
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
229
|
+
})
|
|
230
|
+
)
|
|
231
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
232
|
+
.then((scr) => webex.internal.encryption.download(scr).on('progress', spy))
|
|
233
|
+
.then(() => assert.called(spy));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('checks body of the API call /downloads/endpoints', () =>
|
|
237
|
+
webex.internal.encryption
|
|
238
|
+
.encryptBinary(FILE)
|
|
239
|
+
.then(({scr, cdata}) =>
|
|
240
|
+
webex
|
|
241
|
+
.request({
|
|
242
|
+
method: 'POST',
|
|
243
|
+
uri: makeLocalUrl('/files/upload'),
|
|
244
|
+
body: cdata,
|
|
245
|
+
})
|
|
246
|
+
.then((res) => {
|
|
247
|
+
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
248
|
+
|
|
249
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
250
|
+
})
|
|
251
|
+
)
|
|
252
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
253
|
+
.then((scr) => {
|
|
254
|
+
const options = {
|
|
255
|
+
params: {
|
|
256
|
+
allow: ['unchecked', 'evaluating'],
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return webex.internal.encryption.download(scr, options);
|
|
261
|
+
})
|
|
262
|
+
.then((f) => file.isMatchingFile(f, FILE))
|
|
263
|
+
.then((result) => assert.deepEqual(result, true)));
|
|
264
|
+
|
|
265
|
+
it('checks _fetchDownloadUrl()', () =>
|
|
266
|
+
webex.internal.encryption
|
|
267
|
+
.encryptBinary(FILE)
|
|
268
|
+
.then(({scr, cdata}) =>
|
|
269
|
+
webex
|
|
270
|
+
.request({
|
|
271
|
+
method: 'POST',
|
|
272
|
+
uri: makeLocalUrl('/files/upload'),
|
|
273
|
+
body: cdata,
|
|
274
|
+
})
|
|
275
|
+
.then((res) => {
|
|
276
|
+
scr.loc = makeLocalUrl(res.body.loc, {full: true});
|
|
277
|
+
|
|
278
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
279
|
+
})
|
|
280
|
+
)
|
|
281
|
+
.then((cipherScr) => webex.internal.encryption.decryptScr(key, cipherScr))
|
|
282
|
+
.then((scr) => {
|
|
283
|
+
const options = {
|
|
284
|
+
params: {
|
|
285
|
+
allow: ['unchecked', 'evaluating'],
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return webex.internal.encryption._fetchDownloadUrl(scr, options);
|
|
290
|
+
})
|
|
291
|
+
.then((result) => assert.isString(result)));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('#encryptBinary()', () => {
|
|
295
|
+
it('encrypts a binary file', () =>
|
|
296
|
+
webex.internal.encryption.encryptBinary(FILE).then(({scr, cdata}) => {
|
|
297
|
+
assert.property(scr, 'enc');
|
|
298
|
+
assert.property(scr, 'key');
|
|
299
|
+
assert.property(scr, 'iv');
|
|
300
|
+
|
|
301
|
+
return assert.isBufferLike(cdata);
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
// browserOnly(it)(`accepts an ArrayBuffer`);
|
|
305
|
+
// browserOnly(it)(`accepts a Blob`);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('#encryptScr()', () => {
|
|
309
|
+
it('encrypts an scr', () =>
|
|
310
|
+
webex.internal.encryption
|
|
311
|
+
.encryptBinary(FILE)
|
|
312
|
+
.then(({scr}) => {
|
|
313
|
+
scr.loc = 'file:///file.enc';
|
|
314
|
+
|
|
315
|
+
return webex.internal.encryption.encryptScr(key, scr);
|
|
316
|
+
})
|
|
317
|
+
.then((cipherScr) => assert.isString(cipherScr)));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('#encryptText()', () => {
|
|
321
|
+
it('encrypts text', () =>
|
|
322
|
+
webex.internal.encryption
|
|
323
|
+
.encryptText(key, PLAINTEXT)
|
|
324
|
+
.then((ciphertext) => assert.notEqual(ciphertext, PLAINTEXT)));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('#onBehalfOf', () => {
|
|
328
|
+
let complianceUser;
|
|
329
|
+
|
|
330
|
+
before('create compliance officer test user', () =>
|
|
331
|
+
testUsers
|
|
332
|
+
.create({
|
|
333
|
+
count: 1,
|
|
334
|
+
config: {
|
|
335
|
+
roles: [{name: 'spark.kms_orgagent'}],
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
.then((users) => {
|
|
339
|
+
complianceUser = users[0];
|
|
340
|
+
complianceUser.webex = new WebexCore({
|
|
341
|
+
credentials: {
|
|
342
|
+
authorization: complianceUser.token,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
assert.isTrue(complianceUser.webex.canAuthorize);
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
after(() => complianceUser && complianceUser.webex.internal.mercury.disconnect());
|
|
350
|
+
|
|
351
|
+
it('decrypt text', () =>
|
|
352
|
+
webex.internal.encryption
|
|
353
|
+
.encryptText(key, PLAINTEXT)
|
|
354
|
+
.then((ciphertext) => {
|
|
355
|
+
assert.notEqual(ciphertext, PLAINTEXT);
|
|
356
|
+
|
|
357
|
+
return complianceUser.webex.internal.encryption.decryptText(key, ciphertext, {
|
|
358
|
+
onBehalfOf: user.id,
|
|
359
|
+
});
|
|
360
|
+
})
|
|
361
|
+
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
362
|
+
|
|
363
|
+
it('encrypt and decrypt text', () =>
|
|
364
|
+
complianceUser.webex.internal.encryption
|
|
365
|
+
.encryptText(key, PLAINTEXT, {onBehalfOf: user.id})
|
|
366
|
+
.then((ciphertext) => {
|
|
367
|
+
assert.notEqual(ciphertext, PLAINTEXT);
|
|
368
|
+
|
|
369
|
+
return complianceUser.webex.internal.encryption.decryptText(key, ciphertext, {
|
|
370
|
+
onBehalfOf: user.id,
|
|
371
|
+
});
|
|
372
|
+
})
|
|
373
|
+
.then((plaintext) => assert.equal(plaintext, PLAINTEXT)));
|
|
374
|
+
|
|
375
|
+
it('decrypt scr', () =>
|
|
376
|
+
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
377
|
+
scr.loc = 'file:///file.enc';
|
|
378
|
+
|
|
379
|
+
return webex.internal.encryption
|
|
380
|
+
.encryptScr(key, scr)
|
|
381
|
+
.then((cipherScr) =>
|
|
382
|
+
complianceUser.webex.internal.encryption.decryptScr(key, cipherScr, {
|
|
383
|
+
onBehalfOf: user.id,
|
|
384
|
+
})
|
|
385
|
+
)
|
|
386
|
+
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
it('decrypt scr', () =>
|
|
390
|
+
webex.internal.encryption.encryptBinary(FILE).then(({scr}) => {
|
|
391
|
+
scr.loc = 'file:///file.enc';
|
|
392
|
+
|
|
393
|
+
return complianceUser.webex.internal.encryption
|
|
394
|
+
.encryptScr(key, scr, {onBehalfOf: user.id})
|
|
395
|
+
.then((cipherScr) =>
|
|
396
|
+
complianceUser.webex.internal.encryption.decryptScr(key, cipherScr, {
|
|
397
|
+
onBehalfOf: user.id,
|
|
398
|
+
})
|
|
399
|
+
)
|
|
400
|
+
.then((decryptedScr) => assert.deepEqual(decryptedScr, scr));
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
it('getKey', () =>
|
|
404
|
+
complianceUser.webex.internal.encryption
|
|
405
|
+
.getKey(key.uri, {onBehalfOf: user.id})
|
|
406
|
+
.then((key2) => {
|
|
407
|
+
assert.property(key2, 'uri');
|
|
408
|
+
assert.property(key2, 'jwk');
|
|
409
|
+
assert.notEqual(key2, key);
|
|
410
|
+
assert.equal(key2.uri, key.uri);
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
it('getKey forbidden as compliance officer does not have access', () =>
|
|
414
|
+
complianceUser.webex.internal.encryption.getKey(key.uri).then(
|
|
415
|
+
(value) => expect.fail(`Compliance officer has retrieved key without onBehalfOf: ${value}`),
|
|
416
|
+
(error) => expect(error.body.status).to.equal(403)
|
|
417
|
+
));
|
|
418
|
+
|
|
419
|
+
it('getKey forbidden as user does not have access', () =>
|
|
420
|
+
complianceUser.webex.internal.encryption
|
|
421
|
+
.getKey(key.uri, {onBehalfOf: '7851fe79-7c87-40cc-ac36-8b77b011b399'})
|
|
422
|
+
.then(
|
|
423
|
+
(value) =>
|
|
424
|
+
expect.fail(
|
|
425
|
+
`Should not be found as 7851fe79-7c87-40cc-ac36-8b77b011b399 does not have access ${value}`
|
|
426
|
+
),
|
|
427
|
+
(error) => expect(error.body.status).to.equal(403)
|
|
428
|
+
));
|
|
429
|
+
|
|
430
|
+
it('getKey onBehalfOf and then by compliance officer only', () =>
|
|
431
|
+
complianceUser.webex.internal.encryption
|
|
432
|
+
.getKey(key.uri, {onBehalfOf: user.id})
|
|
433
|
+
.then((key2) => {
|
|
434
|
+
assert.property(key2, 'uri');
|
|
435
|
+
assert.property(key2, 'jwk');
|
|
436
|
+
assert.notEqual(key2, key);
|
|
437
|
+
assert.equal(key2.uri, key.uri);
|
|
438
|
+
})
|
|
439
|
+
.then(() => complianceUser.webex.internal.encryption.getKey(key.uri))
|
|
440
|
+
.then(
|
|
441
|
+
(value) =>
|
|
442
|
+
expect.fail(
|
|
443
|
+
`Compliance should no longer be able to retrieve key as onBehalfOf was not set: ${value}`
|
|
444
|
+
),
|
|
445
|
+
(error) => expect(error.body.status).to.equal(403)
|
|
446
|
+
));
|
|
447
|
+
});
|
|
448
|
+
});
|