Skip to content

Commit 526fcab

Browse files
authored
Support Importing Collection Level Auth from Postman (Merge pull request usebruno#4475)
2 parents 75ff31f + 46dab6e commit 526fcab

File tree

9 files changed

+956
-850
lines changed

9 files changed

+956
-850
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
},
3636
"scripts": {
3737
"setup": "node ./scripts/setup.js",
38+
"watch:converters": "npm run watch --workspace=packages/bruno-converters",
3839
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
3940
"dev:web": "npm run dev --workspace=packages/bruno-app",
4041
"build:web": "npm run build --workspace=packages/bruno-app",

packages/bruno-converters/src/postman/postman-to-bruno.js

Lines changed: 148 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@ const convertV21Auth = (array) => {
4343
};
4444

4545
const constructUrlFromParts = (url) => {
46+
if (!url) return '';
47+
4648
const { protocol = 'http', host, path, port, query, hash } = url || {};
4749
const hostStr = Array.isArray(host) ? host.filter(Boolean).join('.') : host || '';
4850
const pathStr = Array.isArray(path) ? path.filter(Boolean).join('/') : path || '';
4951
const portStr = port ? `:${port}` : '';
5052
const queryStr =
5153
query && Array.isArray(query) && query.length > 0
5254
? `?${query
53-
.filter((q) => q.key)
55+
.filter((q) => q && q.key)
5456
.map((q) => `${q.key}=${q.value || ''}`)
5557
.join('&')}`
5658
: '';
@@ -140,6 +142,110 @@ const importCollectionLevelVariables = (variables, requestObject) => {
140142
requestObject.vars.req = vars;
141143
};
142144

145+
const processAuth = (auth, requestObject) => {
146+
if (!auth || !auth.type || auth.type === 'noauth') {
147+
return;
148+
}
149+
150+
let authValues = auth[auth.type];
151+
if (Array.isArray(authValues)) {
152+
authValues = convertV21Auth(authValues);
153+
}
154+
155+
if (auth.type === 'basic') {
156+
requestObject.auth.mode = 'basic';
157+
requestObject.auth.basic = {
158+
username: authValues.username || '',
159+
password: authValues.password || ''
160+
};
161+
} else if (auth.type === 'bearer') {
162+
requestObject.auth.mode = 'bearer';
163+
requestObject.auth.bearer = {
164+
token: authValues.token || ''
165+
};
166+
} else if (auth.type === 'awsv4') {
167+
requestObject.auth.mode = 'awsv4';
168+
requestObject.auth.awsv4 = {
169+
accessKeyId: authValues.accessKey || '',
170+
secretAccessKey: authValues.secretKey || '',
171+
sessionToken: authValues.sessionToken || '',
172+
service: authValues.service || '',
173+
region: authValues.region || '',
174+
profileName: ''
175+
};
176+
} else if (auth.type === 'apikey') {
177+
requestObject.auth.mode = 'apikey';
178+
requestObject.auth.apikey = {
179+
key: authValues.key || '',
180+
value: authValues.value?.toString() || '', // Convert the value to a string as Postman's schema does not rigidly define the type of it,
181+
placement: 'header' //By default we are placing the apikey values in headers!
182+
};
183+
} else if (auth.type === 'digest') {
184+
requestObject.auth.mode = 'digest';
185+
requestObject.auth.digest = {
186+
username: authValues.username || '',
187+
password: authValues.password || ''
188+
};
189+
} else if (auth.type === 'oauth2') {
190+
const findValueUsingKey = (key) => {
191+
return authValues[key] || '';
192+
};
193+
const oauth2GrantTypeMaps = {
194+
authorization_code_with_pkce: 'authorization_code',
195+
authorization_code: 'authorization_code',
196+
client_credentials: 'client_credentials',
197+
password_credentials: 'password_credentials'
198+
};
199+
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
200+
201+
requestObject.auth.mode = 'oauth2';
202+
if (grantType === 'authorization_code') {
203+
requestObject.auth.oauth2 = {
204+
grantType: 'authorization_code',
205+
authorizationUrl: findValueUsingKey('authUrl'),
206+
callbackUrl: findValueUsingKey('redirect_uri'),
207+
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
208+
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
209+
clientId: findValueUsingKey('clientId'),
210+
clientSecret: findValueUsingKey('clientSecret'),
211+
scope: findValueUsingKey('scope'),
212+
state: findValueUsingKey('state'),
213+
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
214+
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
215+
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
216+
};
217+
} else if (grantType === 'password_credentials') {
218+
requestObject.auth.oauth2 = {
219+
grantType: 'password',
220+
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
221+
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
222+
username: findValueUsingKey('username'),
223+
password: findValueUsingKey('password'),
224+
clientId: findValueUsingKey('clientId'),
225+
clientSecret: findValueUsingKey('clientSecret'),
226+
scope: findValueUsingKey('scope'),
227+
state: findValueUsingKey('state'),
228+
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
229+
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
230+
};
231+
} else if (grantType === 'client_credentials') {
232+
requestObject.auth.oauth2 = {
233+
grantType: 'client_credentials',
234+
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
235+
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
236+
clientId: findValueUsingKey('clientId'),
237+
clientSecret: findValueUsingKey('clientSecret'),
238+
scope: findValueUsingKey('scope'),
239+
state: findValueUsingKey('state'),
240+
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
241+
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
242+
};
243+
}
244+
} else {
245+
console.warn('Unexpected auth.type', auth.type);
246+
}
247+
};
248+
143249
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
144250
brunoParent.items = brunoParent.items || [];
145251
const folderMap = {};
@@ -172,7 +278,10 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
172278
mode: 'none',
173279
basic: null,
174280
bearer: null,
175-
awsv4: null
281+
awsv4: null,
282+
apikey: null,
283+
oauth2: null,
284+
digest: null
176285
},
177286
headers: [],
178287
script: {},
@@ -181,6 +290,15 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
181290
}
182291
}
183292
};
293+
294+
// Folder level auth
295+
if (i.auth) {
296+
processAuth(i.auth, brunoFolderItem.root.request);
297+
} else if (parentAuth) {
298+
// Inherit parent auth if folder doesn't define its own
299+
processAuth(parentAuth, brunoFolderItem.root.request);
300+
}
301+
184302
if (i.item && i.item.length) {
185303
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
186304
}
@@ -194,8 +312,8 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
194312

195313
} else {
196314
if (i.request) {
197-
if(!requestMethods.includes(i?.request?.method.toUpperCase())){
198-
console.warn("Unexpected request.method")
315+
if (!requestMethods.includes(i?.request?.method.toUpperCase())) {
316+
console.warn('Unexpected request.method', i?.request?.method);
199317
return;
200318
}
201319

@@ -221,7 +339,10 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
221339
mode: 'none',
222340
basic: null,
223341
bearer: null,
224-
awsv4: null
342+
awsv4: null,
343+
apikey: null,
344+
oauth2: null,
345+
digest: null
225346
},
226347
headers: [],
227348
params: [],
@@ -356,102 +477,9 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
356477
});
357478
});
358479

480+
// Handle request-level auth or inherit from parent
359481
const auth = i.request.auth ?? parentAuth;
360-
if (auth?.[auth.type] && auth.type !== 'noauth') {
361-
let authValues = auth[auth.type];
362-
if (Array.isArray(authValues)) {
363-
authValues = convertV21Auth(authValues);
364-
}
365-
if (auth.type === 'basic') {
366-
brunoRequestItem.request.auth.mode = 'basic';
367-
brunoRequestItem.request.auth.basic = {
368-
username: authValues.username,
369-
password: authValues.password
370-
};
371-
} else if (auth.type === 'bearer') {
372-
brunoRequestItem.request.auth.mode = 'bearer';
373-
brunoRequestItem.request.auth.bearer = {
374-
token: authValues.token
375-
};
376-
} else if (auth.type === 'awsv4') {
377-
brunoRequestItem.request.auth.mode = 'awsv4';
378-
brunoRequestItem.request.auth.awsv4 = {
379-
accessKeyId: authValues.accessKey,
380-
secretAccessKey: authValues.secretKey,
381-
sessionToken: authValues.sessionToken,
382-
service: authValues.service,
383-
region: authValues.region,
384-
profileName: ''
385-
};
386-
} else if (auth.type === 'apikey'){
387-
brunoRequestItem.request.auth.mode = 'apikey';
388-
brunoRequestItem.request.auth.apikey = {
389-
key: authValues.key,
390-
value: authValues.value?.toString(), // Convert the value to a string as Postman's schema does not rigidly define the type of it,
391-
placement: "header" //By default we are placing the apikey values in headers!
392-
}
393-
} else if (auth.type === 'oauth2'){
394-
const findValueUsingKey = (key) => {
395-
return auth?.oauth2?.find(v => v?.key == key)?.value || ''
396-
}
397-
const oauth2GrantTypeMaps = {
398-
'authorization_code_with_pkce': 'authorization_code',
399-
'authorization_code': 'authorization_code',
400-
'client_credentials': 'client_credentials',
401-
'password_credentials': 'password_credentials'
402-
}
403-
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
404-
if (grantType) {
405-
brunoRequestItem.request.auth.mode = 'oauth2';
406-
switch(grantType) {
407-
case 'authorization_code':
408-
brunoRequestItem.request.auth.oauth2 = {
409-
grantType: 'authorization_code',
410-
authorizationUrl: findValueUsingKey('authUrl'),
411-
callbackUrl: findValueUsingKey('redirect_uri'),
412-
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
413-
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
414-
clientId: findValueUsingKey('clientId'),
415-
clientSecret: findValueUsingKey('clientSecret'),
416-
scope: findValueUsingKey('scope'),
417-
state: findValueUsingKey('state'),
418-
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
419-
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
420-
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
421-
};
422-
break;
423-
case 'password_credentials':
424-
brunoRequestItem.request.auth.oauth2 = {
425-
grantType: 'password',
426-
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
427-
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
428-
username: findValueUsingKey('username'),
429-
password: findValueUsingKey('password'),
430-
clientId: findValueUsingKey('clientId'),
431-
clientSecret: findValueUsingKey('clientSecret'),
432-
scope: findValueUsingKey('scope'),
433-
state: findValueUsingKey('state'),
434-
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
435-
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
436-
};
437-
break;
438-
case 'client_credentials':
439-
brunoRequestItem.request.auth.oauth2 = {
440-
grantType: 'client_credentials',
441-
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
442-
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
443-
clientId: findValueUsingKey('clientId'),
444-
clientSecret: findValueUsingKey('clientSecret'),
445-
scope: findValueUsingKey('scope'),
446-
state: findValueUsingKey('state'),
447-
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
448-
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
449-
};
450-
break;
451-
}
452-
}
453-
}
454-
}
482+
processAuth(auth, brunoRequestItem.request);
455483

456484
each(get(i, 'request.url.query'), (param) => {
457485
brunoRequestItem.request.params.push({
@@ -519,7 +547,10 @@ const importPostmanV2Collection = (collection) => {
519547
mode: 'none',
520548
basic: null,
521549
bearer: null,
522-
awsv4: null
550+
awsv4: null,
551+
apikey: null,
552+
oauth2: null,
553+
digest: null
523554
},
524555
headers: [],
525556
script: {},
@@ -533,10 +564,13 @@ const importPostmanV2Collection = (collection) => {
533564
importScriptsFromEvents(collection.event, brunoCollection.root.request);
534565
}
535566

536-
if (collection?.variable){
567+
if (collection?.variable) {
537568
importCollectionLevelVariables(collection.variable, brunoCollection.root.request);
538569
}
539570

571+
// Collection level auth
572+
processAuth(collection.auth, brunoCollection.root.request);
573+
540574
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
541575

542576
return brunoCollection;
@@ -557,28 +591,28 @@ const parsePostmanCollection = (collection) => {
557591
return importPostmanV2Collection(collection);
558592
}
559593

560-
throw new Error('Unknown postman schema');
594+
throw new Error('Unsupported Postman schema version. Only Postman Collection v2.0 and v2.1 are supported.');
561595
} catch (err) {
562596
console.log(err);
563597
if (err instanceof Error) {
564598
throw err;
565599
}
566600

567-
throw new Error('Unable to parse the postman collection json file');
601+
throw new Error('Invalid Postman collection format. Please check your JSON file.');
568602
}
569603
};
570604

571605
const postmanToBruno = (postmanCollection) => {
572-
try {
573-
const parsedPostmanCollection = parsePostmanCollection(postmanCollection);
574-
const transformedCollection = transformItemsInCollection(parsedPostmanCollection);
575-
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
576-
const validatedCollection = validateSchema(hydratedCollection);
577-
return validatedCollection;
578-
} catch(err) {
579-
console.log(err);
580-
throw new Error('Import collection failed');
581-
}
606+
try {
607+
const parsedPostmanCollection = parsePostmanCollection(postmanCollection);
608+
const transformedCollection = transformItemsInCollection(parsedPostmanCollection);
609+
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
610+
const validatedCollection = validateSchema(hydratedCollection);
611+
return validatedCollection;
612+
} catch (err) {
613+
console.log(err);
614+
throw new Error(`Import collection failed: ${err.message}`);
615+
}
582616
};
583617

584618
export default postmanToBruno;

0 commit comments

Comments
 (0)