@contrail/flexplm 1.7.2-alpha.f5ce754 → 1.7.3-alpha.2a853f5
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/lib/interfaces/interfaces.d.ts +1 -0
- package/lib/util/config-defaults.d.ts +3 -0
- package/lib/util/config-defaults.js +26 -7
- package/lib/util/config-defaults.spec.js +14 -0
- package/lib/util/flexplm-connect.spec.js +1 -0
- package/lib/util/thumbnail-util.js +24 -5
- package/lib/util/thumbnail-util.spec.js +35 -0
- package/package.json +1 -1
|
@@ -2,6 +2,8 @@ import { FCConfig } from '../interfaces/interfaces';
|
|
|
2
2
|
export declare class ConfigDefaults {
|
|
3
3
|
static NEED_CONFIG_VALUES: string;
|
|
4
4
|
static STATIC_CONFIG_CACHE: {};
|
|
5
|
+
static PROTO_KEYS: string[];
|
|
6
|
+
static stripProtoKeys(obj: any): any;
|
|
5
7
|
static setConfigDefaults(config: any): Promise<FCConfig>;
|
|
6
8
|
static getDefaultConfig(): {
|
|
7
9
|
urlContext: string;
|
|
@@ -17,6 +19,7 @@ export declare class ConfigDefaults {
|
|
|
17
19
|
LCSMaterial: {
|
|
18
20
|
processAsItem: boolean;
|
|
19
21
|
};
|
|
22
|
+
useDistinctRestEndPointForImages: boolean;
|
|
20
23
|
csrfEndpoint: string;
|
|
21
24
|
vibeEventEndpoint: string;
|
|
22
25
|
payloadDefaultAsArray: boolean;
|
|
@@ -5,12 +5,29 @@ const sdk_1 = require("@contrail/sdk");
|
|
|
5
5
|
const util_1 = require("@contrail/util");
|
|
6
6
|
const type_defaults_1 = require("./type-defaults");
|
|
7
7
|
class ConfigDefaults {
|
|
8
|
+
static stripProtoKeys(obj) {
|
|
9
|
+
if (obj && typeof obj === 'object' && !(obj instanceof Date)) {
|
|
10
|
+
for (const key of Object.keys(obj)) {
|
|
11
|
+
if (ConfigDefaults.PROTO_KEYS.includes(key)) {
|
|
12
|
+
delete obj[key];
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
ConfigDefaults.stripProtoKeys(obj[key]); // recurse into nested payloads
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return obj;
|
|
20
|
+
}
|
|
8
21
|
static async setConfigDefaults(config) {
|
|
9
|
-
//Validate config
|
|
22
|
+
// Validate config
|
|
10
23
|
if (!config.apiHost || !config.userName || !config.password) {
|
|
11
24
|
throw new Error(ConfigDefaults.NEED_CONFIG_VALUES);
|
|
12
25
|
}
|
|
13
|
-
|
|
26
|
+
if (config.complexConfig && typeof config.complexConfig === 'object') {
|
|
27
|
+
Object.assign(config, ConfigDefaults.stripProtoKeys(config.complexConfig));
|
|
28
|
+
delete config.complexConfig;
|
|
29
|
+
}
|
|
30
|
+
// List will be comma separated list in UI, so convert to array
|
|
14
31
|
if (config?.itemPreDevelopmentLifecycleStages && !(config?.itemPreDevelopmentLifecycleStages instanceof Array)) {
|
|
15
32
|
config.itemPreDevelopmentLifecycleStages = config.itemPreDevelopmentLifecycleStages.split(',');
|
|
16
33
|
}
|
|
@@ -28,7 +45,7 @@ class ConfigDefaults {
|
|
|
28
45
|
const pass = outputConfig.password;
|
|
29
46
|
outputConfig.userName = () => uName;
|
|
30
47
|
outputConfig.password = () => pass;
|
|
31
|
-
//Don't allow overwriting this.
|
|
48
|
+
// Don't allow overwriting this.
|
|
32
49
|
outputConfig['OOBvibeEventEndpoint'] = '/rfa/vibeiq/vibeEvents';
|
|
33
50
|
type_defaults_1.TypeDefaults.applyConfig(outputConfig);
|
|
34
51
|
console.log('outputConfig: ' + JSON.stringify(outputConfig));
|
|
@@ -38,20 +55,21 @@ class ConfigDefaults {
|
|
|
38
55
|
return {
|
|
39
56
|
urlContext: '/Windchill',
|
|
40
57
|
sendMode: {
|
|
41
|
-
ASYNC_PUBLISH_SEASON: 'vibeiqfile'
|
|
58
|
+
ASYNC_PUBLISH_SEASON: 'vibeiqfile',
|
|
42
59
|
},
|
|
43
60
|
itemPreDevelopmentLifecycleStages: ['concept'],
|
|
44
61
|
identifierAtts: {
|
|
45
62
|
LCSProduct: ['itemNumber'],
|
|
46
63
|
LCSSeason: ['flexPLMSeasonName'],
|
|
47
|
-
LCSSKU: ['itemNumber']
|
|
64
|
+
LCSSKU: ['itemNumber'],
|
|
48
65
|
},
|
|
49
66
|
LCSMaterial: {
|
|
50
|
-
processAsItem: false
|
|
67
|
+
processAsItem: false,
|
|
51
68
|
},
|
|
69
|
+
useDistinctRestEndPointForImages: false,
|
|
52
70
|
csrfEndpoint: '/servlet/rest/security/csrf',
|
|
53
71
|
vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
|
|
54
|
-
payloadDefaultAsArray: true
|
|
72
|
+
payloadDefaultAsArray: true,
|
|
55
73
|
};
|
|
56
74
|
}
|
|
57
75
|
static async getConfigFile(fileId) {
|
|
@@ -96,3 +114,4 @@ class ConfigDefaults {
|
|
|
96
114
|
exports.ConfigDefaults = ConfigDefaults;
|
|
97
115
|
ConfigDefaults.NEED_CONFIG_VALUES = 'To connect to FlexPLM all these APP values need to be set apiHost, userName, and password';
|
|
98
116
|
ConfigDefaults.STATIC_CONFIG_CACHE = {};
|
|
117
|
+
ConfigDefaults.PROTO_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
@@ -378,4 +378,18 @@ describe('all tests', () => {
|
|
|
378
378
|
expect(Object.keys(config).length).toEqual(0);
|
|
379
379
|
});
|
|
380
380
|
});
|
|
381
|
+
describe('prototype pollution', () => {
|
|
382
|
+
const config = {
|
|
383
|
+
apiHost: 'http://test.com',
|
|
384
|
+
userName: 'vibeiq',
|
|
385
|
+
password: 'vibeiq'
|
|
386
|
+
};
|
|
387
|
+
it('does not allow prototype pollution via complexConfig', async () => {
|
|
388
|
+
const startConfig = Object.assign({}, config, {
|
|
389
|
+
complexConfig: JSON.parse('{"__proto__":{"polluted":true}}')
|
|
390
|
+
});
|
|
391
|
+
await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
392
|
+
expect({}.polluted).toBeUndefined();
|
|
393
|
+
});
|
|
394
|
+
});
|
|
381
395
|
});
|
|
@@ -181,15 +181,34 @@ class ThumbnailUtil {
|
|
|
181
181
|
const fileName = urlParts[urlParts.length - 1] || 'thumbnail';
|
|
182
182
|
const encodedUrl = urlParts.map(part => encodeURIComponent(part)).join('/');
|
|
183
183
|
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
console.debug('the useDistinctRestEndPointForImages is --> ' + this.config.useDistinctRestEndPointForImages);
|
|
185
|
+
let response;
|
|
186
|
+
if (this.config.useDistinctRestEndPointForImages) {
|
|
187
|
+
// Route through the connector endpoint (Basic auth + VIBEIQGROUP enforced there).
|
|
188
|
+
const imageEndpoint = '/rfa/vibeiq/image';
|
|
189
|
+
const urlPath = '/servlet/rest' + imageEndpoint + '?path=' + encodeURIComponent(thumbnailUrl);
|
|
190
|
+
response = await flexPLMConnect.getRequest({
|
|
191
|
+
urlPath,
|
|
192
|
+
includeUrlContext: true,
|
|
193
|
+
returnFullResponse: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
response = await flexPLMConnect.getRequest({
|
|
198
|
+
urlPath: encodedUrl,
|
|
199
|
+
includeUrlContext: false,
|
|
200
|
+
returnFullResponse: true,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
189
203
|
const fileBuffer = await response.arrayBuffer();
|
|
190
204
|
const buffer = Buffer.from(fileBuffer);
|
|
191
205
|
const contentTypeHeader = response.headers.get('content-type');
|
|
192
206
|
const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : 'application/octet-stream';
|
|
207
|
+
// Ensure we actually receive an image content type.
|
|
208
|
+
if (!contentType.startsWith('image/')) {
|
|
209
|
+
const message = `Expected image content from FlexPLM but received '${contentType}' for ${thumbnailUrl}`;
|
|
210
|
+
throw new Error(message);
|
|
211
|
+
}
|
|
193
212
|
const contentHolderReference = `${entityName}:${entityId}`;
|
|
194
213
|
const content = await new sdk_1.Content().create({
|
|
195
214
|
fileBuffer: buffer,
|
|
@@ -437,6 +437,41 @@ describe('ThumbnailUtil Tests', () => {
|
|
|
437
437
|
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
438
438
|
});
|
|
439
439
|
});
|
|
440
|
+
describe('syncThumbnailToVibeIQ - useDistinctRestEndPointForImages enabled', () => {
|
|
441
|
+
let tu;
|
|
442
|
+
const distinctConfig = { useDistinctRestEndPointForImages: true };
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
jest.clearAllMocks();
|
|
445
|
+
tu = new thumbnail_util_1.ThumbnailUtil(distinctConfig);
|
|
446
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
447
|
+
if (opts.entityName === 'content-custom-size')
|
|
448
|
+
return Promise.resolve([]);
|
|
449
|
+
return Promise.resolve({});
|
|
450
|
+
});
|
|
451
|
+
mockEntitiesUpdate.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
452
|
+
mockEntitiesDelete.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
453
|
+
});
|
|
454
|
+
it('routes through the distinct image endpoint with includeUrlContext true', async () => {
|
|
455
|
+
const mockResponse = {
|
|
456
|
+
arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(8)),
|
|
457
|
+
headers: { get: jest.fn().mockReturnValue('image/png') },
|
|
458
|
+
};
|
|
459
|
+
mockGetRequest.mockResolvedValue(mockResponse);
|
|
460
|
+
mockContentCreate.mockResolvedValue({
|
|
461
|
+
id: 'distinctContent1', contentType: 'image/png', fileName: 'thumb.png',
|
|
462
|
+
primaryFileUrl: 'https://files/primary.png', largeViewableUrl: null,
|
|
463
|
+
mediumLargeViewableUrl: null, mediumViewableUrl: null, smallViewableUrl: null, tinyViewableUrl: null,
|
|
464
|
+
});
|
|
465
|
+
const thumbnailUrl = '/rest/thumbnail/thumb.png';
|
|
466
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.NEW_THUMBNAIL_ID]: thumbnailUrl } };
|
|
467
|
+
await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
468
|
+
expect(mockGetRequest).toHaveBeenCalledWith({
|
|
469
|
+
urlPath: '/servlet/rest/rfa/vibeiq/image?path=' + encodeURIComponent(thumbnailUrl),
|
|
470
|
+
includeUrlContext: true,
|
|
471
|
+
returnFullResponse: true,
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|
|
440
475
|
describe('ThumbnailUtil - iteratedThumbnailId (THUMBNAIL key)', () => {
|
|
441
476
|
const config = {};
|
|
442
477
|
let tu;
|