@contrail/flexplm 1.7.3-alpha.b33e995 → 1.7.3

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.
@@ -14,7 +14,7 @@ export interface FCConfig {
14
14
  urlContext: string;
15
15
  vibeEventEndpoint: string;
16
16
  csrfEndpoint: string;
17
- useDistinctRestEndPointForImages: boolean;
17
+ useDistinctRestEndPointForImages?: boolean;
18
18
  itemPreDevelopmentLifecycleStages: string[];
19
19
  identifierAtts?: {
20
20
  [key: string]: string[];
@@ -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;
@@ -5,16 +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') {
14
- Object.assign(config, config.complexConfig);
27
+ Object.assign(config, ConfigDefaults.stripProtoKeys(config.complexConfig));
15
28
  delete config.complexConfig;
16
29
  }
17
- //List will be comma separated list in UI, so convert to array
30
+ // List will be comma separated list in UI, so convert to array
18
31
  if (config?.itemPreDevelopmentLifecycleStages && !(config?.itemPreDevelopmentLifecycleStages instanceof Array)) {
19
32
  config.itemPreDevelopmentLifecycleStages = config.itemPreDevelopmentLifecycleStages.split(',');
20
33
  }
@@ -32,7 +45,7 @@ class ConfigDefaults {
32
45
  const pass = outputConfig.password;
33
46
  outputConfig.userName = () => uName;
34
47
  outputConfig.password = () => pass;
35
- //Don't allow overwriting this.
48
+ // Don't allow overwriting this.
36
49
  outputConfig['OOBvibeEventEndpoint'] = '/rfa/vibeiq/vibeEvents';
37
50
  type_defaults_1.TypeDefaults.applyConfig(outputConfig);
38
51
  console.log('outputConfig: ' + JSON.stringify(outputConfig));
@@ -42,21 +55,21 @@ class ConfigDefaults {
42
55
  return {
43
56
  urlContext: '/Windchill',
44
57
  sendMode: {
45
- ASYNC_PUBLISH_SEASON: 'vibeiqfile'
58
+ ASYNC_PUBLISH_SEASON: 'vibeiqfile',
46
59
  },
47
60
  itemPreDevelopmentLifecycleStages: ['concept'],
48
61
  identifierAtts: {
49
62
  LCSProduct: ['itemNumber'],
50
63
  LCSSeason: ['flexPLMSeasonName'],
51
- LCSSKU: ['itemNumber']
64
+ LCSSKU: ['itemNumber'],
52
65
  },
53
66
  LCSMaterial: {
54
- processAsItem: false
67
+ processAsItem: false,
55
68
  },
56
69
  useDistinctRestEndPointForImages: false,
57
70
  csrfEndpoint: '/servlet/rest/security/csrf',
58
71
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
59
- payloadDefaultAsArray: true
72
+ payloadDefaultAsArray: true,
60
73
  };
61
74
  }
62
75
  static async getConfigFile(fileId) {
@@ -101,3 +114,4 @@ class ConfigDefaults {
101
114
  exports.ConfigDefaults = ConfigDefaults;
102
115
  ConfigDefaults.NEED_CONFIG_VALUES = 'To connect to FlexPLM all these APP values need to be set apiHost, userName, and password';
103
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
  });
@@ -38,8 +38,7 @@ describe('getFlexPLMValue multi_select', () => {
38
38
  urlContext: 'xxx',
39
39
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
40
40
  csrfEndpoint: '/servlet/rest/security/csrf',
41
- itemPreDevelopmentLifecycleStages: ['concept'],
42
- useDistinctRestEndPointForImages: false
41
+ itemPreDevelopmentLifecycleStages: ['concept']
43
42
  };
44
43
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
45
44
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -97,8 +96,7 @@ describe('getEnumerationValue', () => {
97
96
  urlContext: 'xxx',
98
97
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
99
98
  csrfEndpoint: '/servlet/rest/security/csrf',
100
- itemPreDevelopmentLifecycleStages: ['concept'],
101
- useDistinctRestEndPointForImages: false
99
+ itemPreDevelopmentLifecycleStages: ['concept']
102
100
  };
103
101
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
104
102
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -155,8 +153,7 @@ describe('getEnumerationValue multi_select', () => {
155
153
  urlContext: 'xxx',
156
154
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
157
155
  csrfEndpoint: '/servlet/rest/security/csrf',
158
- itemPreDevelopmentLifecycleStages: ['concept'],
159
- useDistinctRestEndPointForImages: false
156
+ itemPreDevelopmentLifecycleStages: ['concept']
160
157
  };
161
158
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
162
159
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -216,8 +213,7 @@ describe('getPersistableChanges', () => {
216
213
  urlContext: 'xxx',
217
214
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
218
215
  csrfEndpoint: '/servlet/rest/security/csrf',
219
- itemPreDevelopmentLifecycleStages: ['concept'],
220
- useDistinctRestEndPointForImages: false
216
+ itemPreDevelopmentLifecycleStages: ['concept']
221
217
  };
222
218
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
223
219
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -302,8 +298,7 @@ describe('getObjectReferenceValue cache', () => {
302
298
  urlContext: 'xxx',
303
299
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
304
300
  csrfEndpoint: '/servlet/rest/security/csrf',
305
- itemPreDevelopmentLifecycleStages: ['concept'],
306
- useDistinctRestEndPointForImages: false
301
+ itemPreDevelopmentLifecycleStages: ['concept']
307
302
  };
308
303
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
309
304
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -361,8 +356,7 @@ describe('getObjectReferenceValue bad value', () => {
361
356
  urlContext: 'xxx',
362
357
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
363
358
  csrfEndpoint: '/servlet/rest/security/csrf',
364
- itemPreDevelopmentLifecycleStages: ['concept'],
365
- useDistinctRestEndPointForImages: false
359
+ itemPreDevelopmentLifecycleStages: ['concept']
366
360
  };
367
361
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
368
362
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -468,8 +462,7 @@ describe('setObjectReferenceValue - identity service', () => {
468
462
  urlContext: 'xxx',
469
463
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
470
464
  csrfEndpoint: '/servlet/rest/security/csrf',
471
- itemPreDevelopmentLifecycleStages: ['concept'],
472
- useDistinctRestEndPointForImages: false
465
+ itemPreDevelopmentLifecycleStages: ['concept']
473
466
  });
474
467
  const refProp = {
475
468
  id: 'cJoZQvoj7dkfCBJq',
@@ -927,8 +920,7 @@ describe('getEntityValues', () => {
927
920
  urlContext: 'xxx',
928
921
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
929
922
  csrfEndpoint: '/servlet/rest/security/csrf',
930
- itemPreDevelopmentLifecycleStages: ['concept'],
931
- useDistinctRestEndPointForImages: false
923
+ itemPreDevelopmentLifecycleStages: ['concept']
932
924
  };
933
925
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
934
926
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -969,8 +961,7 @@ describe('setEnumerationKeys', () => {
969
961
  urlContext: 'xxx',
970
962
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
971
963
  csrfEndpoint: '/servlet/rest/security/csrf',
972
- itemPreDevelopmentLifecycleStages: ['concept'],
973
- useDistinctRestEndPointForImages: false
964
+ itemPreDevelopmentLifecycleStages: ['concept']
974
965
  };
975
966
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
976
967
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -1034,8 +1025,7 @@ describe('checkKeysAndValues', () => {
1034
1025
  urlContext: 'xxx',
1035
1026
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1036
1027
  csrfEndpoint: '/servlet/rest/security/csrf',
1037
- itemPreDevelopmentLifecycleStages: ['concept'],
1038
- useDistinctRestEndPointForImages: false
1028
+ itemPreDevelopmentLifecycleStages: ['concept']
1039
1029
  };
1040
1030
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1041
1031
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -1112,8 +1102,7 @@ describe('filterOutArchivedAndTrashedEntities', () => {
1112
1102
  urlContext: 'xxx',
1113
1103
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1114
1104
  csrfEndpoint: '/servlet/rest/security/csrf',
1115
- itemPreDevelopmentLifecycleStages: ['concept'],
1116
- useDistinctRestEndPointForImages: false
1105
+ itemPreDevelopmentLifecycleStages: ['concept']
1117
1106
  };
1118
1107
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1119
1108
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -1153,8 +1142,7 @@ describe('setUserListValue', () => {
1153
1142
  urlContext: 'xxx',
1154
1143
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1155
1144
  csrfEndpoint: '/servlet/rest/security/csrf',
1156
- itemPreDevelopmentLifecycleStages: ['concept'],
1157
- useDistinctRestEndPointForImages: false
1145
+ itemPreDevelopmentLifecycleStages: ['concept']
1158
1146
  };
1159
1147
  const userListProp1 = {};
1160
1148
  const userEmailMapping = [
@@ -1250,8 +1238,7 @@ describe('getUserListValue', () => {
1250
1238
  urlContext: 'xxx',
1251
1239
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1252
1240
  csrfEndpoint: '/servlet/rest/security/csrf',
1253
- itemPreDevelopmentLifecycleStages: ['concept'],
1254
- useDistinctRestEndPointForImages: false
1241
+ itemPreDevelopmentLifecycleStages: ['concept']
1255
1242
  };
1256
1243
  const userListProp1 = {
1257
1244
  slug: 'userList1'
@@ -1353,8 +1340,7 @@ describe('getFlexPLMValue size_range', () => {
1353
1340
  urlContext: 'xxx',
1354
1341
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1355
1342
  csrfEndpoint: '/servlet/rest/security/csrf',
1356
- itemPreDevelopmentLifecycleStages: ['concept'],
1357
- useDistinctRestEndPointForImages: false
1343
+ itemPreDevelopmentLifecycleStages: ['concept']
1358
1344
  };
1359
1345
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1360
1346
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -1387,8 +1373,7 @@ describe('getEntityValue size_range', () => {
1387
1373
  urlContext: 'xxx',
1388
1374
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
1389
1375
  csrfEndpoint: '/servlet/rest/security/csrf',
1390
- itemPreDevelopmentLifecycleStages: ['concept'],
1391
- useDistinctRestEndPointForImages: false
1376
+ itemPreDevelopmentLifecycleStages: ['concept']
1392
1377
  };
1393
1378
  const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1394
1379
  const dc = new data_converter_1.DataConverter(config, mapFileUtil);
@@ -181,7 +181,7 @@ 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
- console.log('the useDistinctRestEndPointForImages is --> ' + this.config.useDistinctRestEndPointForImages);
184
+ console.debug('the useDistinctRestEndPointForImages is --> ' + this.config.useDistinctRestEndPointForImages);
185
185
  let response;
186
186
  if (this.config.useDistinctRestEndPointForImages) {
187
187
  // Route through the connector endpoint (Basic auth + VIBEIQGROUP enforced there).
@@ -200,17 +200,15 @@ class ThumbnailUtil {
200
200
  returnFullResponse: true,
201
201
  });
202
202
  }
203
- // Ensure we actually receive an image content type.
204
- const contTypeHeader = response.headers.get('content-type');
205
- const contType = contTypeHeader ? contTypeHeader.split(';')[0] : 'application/octet-stream';
206
- if (!contType.startsWith('image/')) {
207
- const message = `Expected image content from FlexPLM but received '${contType}' for ${thumbnailUrl}`;
208
- throw new Error(message);
209
- }
210
203
  const fileBuffer = await response.arrayBuffer();
211
204
  const buffer = Buffer.from(fileBuffer);
212
205
  const contentTypeHeader = response.headers.get('content-type');
213
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
+ }
214
212
  const contentHolderReference = `${entityName}:${entityId}`;
215
213
  const content = await new sdk_1.Content().create({
216
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.3-alpha.b33e995",
3
+ "version": "1.7.3",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",