@contrail/flexplm 1.7.2 → 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.
@@ -14,6 +14,7 @@ export interface FCConfig {
14
14
  urlContext: string;
15
15
  vibeEventEndpoint: string;
16
16
  csrfEndpoint: string;
17
+ useDistinctRestEndPointForImages?: boolean;
17
18
  itemPreDevelopmentLifecycleStages: string[];
18
19
  identifierAtts?: {
19
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;
@@ -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
- //List will be comma separated list in UI, so convert to array
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
  });
@@ -24,6 +24,7 @@ const createConfig = (overrides) => ({
24
24
  password: () => 'pass',
25
25
  itemPreDevelopmentLifecycleStages: [],
26
26
  payloadDefaultAsArray: true,
27
+ useDistinctRestEndPointForImages: false,
27
28
  ...overrides,
28
29
  });
29
30
  describe('FlexPLMConnect.getRequest', () => {
@@ -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
- const response = await flexPLMConnect.getRequest({
185
- urlPath: encodedUrl,
186
- includeUrlContext: false,
187
- returnFullResponse: true,
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.2",
3
+ "version": "1.7.3-alpha.2a853f5",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",