Skip to content

Commit 7e8f26c

Browse files
Merge pull request #614 from postmanlabs/feature/oauth2-flows-support
Added OAuth2 flows and configuration support
2 parents 0e6ce7c + fa0c910 commit 7e8f26c

File tree

2 files changed

+285
-1
lines changed

2 files changed

+285
-1
lines changed

lib/schemaUtils.js

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/sc
7575
VALIDATION: 'VALIDATION',
7676
CONVERSION: 'CONVERSION'
7777
},
78+
FLOW_TYPE = {
79+
authorizationCode: 'authorization_code',
80+
implicit: 'implicit',
81+
password: 'password_credentials',
82+
clientCredentials: 'client_credentials'
83+
},
7884

7985
// These are the methods supported in the PathItem schema
8086
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject
@@ -1109,9 +1115,81 @@ module.exports = {
11091115
}
11101116
}
11111117
else if (securityDef.type === 'oauth2') {
1118+
let flowObj, currentFlowType;
1119+
11121120
helper = {
1113-
type: 'oauth2'
1121+
type: 'oauth2',
1122+
oauth2: []
11141123
};
1124+
1125+
if (_.isObject(securityDef.flows) && FLOW_TYPE[Object.keys(securityDef.flows)[0]]) {
1126+
/*
1127+
1128+
//===================[]========================\\
1129+
|| OAuth2 Flow Name || Key name in collection ||
1130+
|]===================[]========================[|
1131+
|| clientCredentials || client_credentials ||
1132+
|| password || password_credentials ||
1133+
|| implicit || implicit ||
1134+
|| authorizationCode || authorization_code ||
1135+
\\===================[]========================//
1136+
Ref : https://swagger.io/docs/specification/authentication/oauth2/
1137+
1138+
In case of multiple flow types, the first one will be preferred
1139+
and passed on to the collection.
1140+
1141+
Other flow types in collection which are not explicitly present in OA 3
1142+
• "authorization_code_with_pkce"
1143+
1144+
*/
1145+
currentFlowType = FLOW_TYPE[Object.keys(securityDef.flows)[0]];
1146+
flowObj = _.get(securityDef, `flows.${Object.keys(securityDef.flows)[0]}`);
1147+
}
1148+
1149+
if (currentFlowType) { // Means the flow is of supported type
1150+
1151+
// Fields supported by all flows -> refreshUrl, scopes
1152+
if (!_.isEmpty(flowObj.scopes)) {
1153+
helper.oauth2.push({
1154+
key: 'scope',
1155+
value: Object.keys(flowObj.scopes).join(' ')
1156+
});
1157+
}
1158+
1159+
/* refreshURL is indicated by key 'redirect_uri' in collection
1160+
Ref : https://stackoverflow.com/a/42131366/19078409 */
1161+
if (!_.isEmpty(flowObj.refreshUrl)) {
1162+
helper.oauth2.push({
1163+
key: 'redirect_uri',
1164+
value: _.isString(flowObj.refreshUrl) ? flowObj.refreshUrl : '{{OAuth2_CallbackURL}}'
1165+
});
1166+
}
1167+
1168+
// Fields supported by all flows except implicit -> tokenUrl
1169+
if (currentFlowType !== FLOW_TYPE.implicit) {
1170+
if (!_.isEmpty(flowObj.tokenUrl)) {
1171+
helper.oauth2.push({
1172+
key: 'accessTokenUrl',
1173+
value: _.isString(flowObj.tokenUrl) ? flowObj.tokenUrl : '{{OAuth2_AccessTokenURL}}'
1174+
});
1175+
}
1176+
}
1177+
1178+
// Fields supported by all flows all except password, clientCredentials -> authorizationUrl
1179+
if (currentFlowType !== FLOW_TYPE.password && currentFlowType !== FLOW_TYPE.clientCredentials) {
1180+
if (!_.isEmpty(flowObj.authorizationUrl)) {
1181+
helper.oauth2.push({
1182+
key: 'authUrl',
1183+
value: _.isString(flowObj.authorizationUrl) ? flowObj.authorizationUrl : '{{OAuth2_AuthURL}}'
1184+
});
1185+
}
1186+
}
1187+
1188+
helper.oauth2.push({
1189+
key: 'grant_type',
1190+
value: currentFlowType
1191+
});
1192+
}
11151193
}
11161194
else if (securityDef.type === 'apiKey') {
11171195
helper = {

test/unit/util.test.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,3 +2957,209 @@ describe('findCommonSubpath method', function () {
29572957
});
29582958

29592959
});
2960+
2961+
describe('getAuthHelper method - OAuth2 Flows', function() {
2962+
it('Should parse OAuth2 configuration to collection (Single Flow) - Type 1', function() {
2963+
const openAPISpec = {
2964+
'components': {
2965+
'responses': {},
2966+
'schemas': {},
2967+
'securitySchemes': {
2968+
'oauth2': {
2969+
'flows': {
2970+
'clientCredentials': {
2971+
'scopes': {},
2972+
'tokenUrl': 'https://example.com/oauth2/token'
2973+
}
2974+
},
2975+
'type': 'oauth2'
2976+
}
2977+
}
2978+
},
2979+
'info': {
2980+
'title': 'API',
2981+
'version': '0.2'
2982+
},
2983+
'openapi': '3.0.0',
2984+
'paths': {},
2985+
'security': [
2986+
{
2987+
'oauth2': []
2988+
}
2989+
],
2990+
'servers': [
2991+
{
2992+
'url': 'https://example.com',
2993+
'variables': {}
2994+
}
2995+
],
2996+
'tags': [],
2997+
'securityDefs': {
2998+
'oauth2': {
2999+
'flows': {
3000+
'clientCredentials': {
3001+
'scopes': {},
3002+
'tokenUrl': 'https://example.com/oauth2/token'
3003+
}
3004+
},
3005+
'type': 'oauth2'
3006+
}
3007+
},
3008+
'baseUrl': 'https://example.com',
3009+
'baseUrlVariables': {}
3010+
},
3011+
securitySet = [{ oauth2: [] }],
3012+
helperData = SchemaUtils.getAuthHelper(openAPISpec, securitySet);
3013+
3014+
expect(helperData.type).to.be.equal('oauth2');
3015+
expect(helperData).to.have.property('oauth2').with.lengthOf(2);
3016+
expect(helperData.oauth2[0]).to.be.an('object');
3017+
expect(helperData).to.deep.equal({
3018+
type: 'oauth2',
3019+
oauth2: [
3020+
{
3021+
key: 'accessTokenUrl',
3022+
value: 'https://example.com/oauth2/token'
3023+
},
3024+
{ key: 'grant_type', value: 'client_credentials' }
3025+
]
3026+
});
3027+
});
3028+
3029+
it('Should parse OAuth2 configuration to collection (Multiple Flow types)- Type 2', function() {
3030+
const openAPISpec = {
3031+
components: {
3032+
responses: {},
3033+
schemas: {},
3034+
securitySchemes: {
3035+
oauth2: {
3036+
type: 'oauth2',
3037+
flows: {
3038+
implicit: {
3039+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3040+
scopes: {
3041+
'write:pets': 'modify pets in your account',
3042+
'read:pets': 'read your pets'
3043+
}
3044+
},
3045+
authorizationCode: {
3046+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3047+
tokenUrl: 'https://example.com/api/oauth/token',
3048+
scopes: {
3049+
'write:pets': 'modify pets in your account',
3050+
'read:pets': 'read your pets'
3051+
}
3052+
}
3053+
}
3054+
}
3055+
}
3056+
},
3057+
info: { title: 'API', version: '0.2' },
3058+
openapi: '3.0.0',
3059+
paths: {},
3060+
security: [{ oauth2: [] }],
3061+
servers: [{ url: 'https://myserver.com', variables: {} }],
3062+
tags: [],
3063+
securityDefs: {
3064+
oauth2: {
3065+
type: 'oauth2',
3066+
flows: {
3067+
implicit: {
3068+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3069+
scopes: {
3070+
'write:pets': 'modify pets in your account',
3071+
'read:pets': 'read your pets'
3072+
}
3073+
},
3074+
authorizationCode: {
3075+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3076+
tokenUrl: 'https://example.com/api/oauth/token',
3077+
scopes: {
3078+
'write:pets': 'modify pets in your account',
3079+
'read:pets': 'read your pets'
3080+
}
3081+
}
3082+
}
3083+
}
3084+
},
3085+
baseUrl: 'https://myserver.com',
3086+
baseUrlVariables: {}
3087+
},
3088+
securitySet = [{ oauth2: [] }],
3089+
helperData = SchemaUtils.getAuthHelper(openAPISpec, securitySet);
3090+
3091+
expect(helperData.type).to.be.equal('oauth2');
3092+
expect(helperData).to.have.property('oauth2').with.lengthOf(3);
3093+
expect(helperData.oauth2[0]).to.be.an('object');
3094+
expect(helperData).to.deep.equal({
3095+
'type': 'oauth2',
3096+
'oauth2': [
3097+
{
3098+
'key': 'scope',
3099+
'value': 'write:pets read:pets'
3100+
},
3101+
{
3102+
'key': 'authUrl',
3103+
'value': 'https://example.com/api/oauth/dialog'
3104+
},
3105+
{
3106+
'key': 'grant_type',
3107+
'value': 'implicit'
3108+
}
3109+
]
3110+
});
3111+
});
3112+
3113+
it('Scopes are parsed as sequence of strings', function() {
3114+
const openAPISpec = {
3115+
components: {
3116+
responses: {},
3117+
schemas: {},
3118+
securitySchemes: {
3119+
oauth2: {
3120+
type: 'oauth2',
3121+
flows: {
3122+
implicit: {
3123+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3124+
scopes: {
3125+
'write:pets': 'modify pets in your account',
3126+
'read:pets': 'read your pets'
3127+
}
3128+
}
3129+
}
3130+
}
3131+
}
3132+
},
3133+
info: { title: 'API', version: '0.2' },
3134+
openapi: '3.0.0',
3135+
paths: {},
3136+
security: [{ oauth2: [] }],
3137+
servers: [{ url: 'https://myserver.com', variables: {} }],
3138+
tags: [],
3139+
securityDefs: {
3140+
oauth2: {
3141+
type: 'oauth2',
3142+
flows: {
3143+
implicit: {
3144+
authorizationUrl: 'https://example.com/api/oauth/dialog',
3145+
scopes: {
3146+
'write:pets': 'modify pets in your account',
3147+
'read:pets': 'read your pets'
3148+
}
3149+
}
3150+
}
3151+
}
3152+
},
3153+
baseUrl: 'https://myserver.com',
3154+
baseUrlVariables: {}
3155+
},
3156+
securitySet = [{ oauth2: [] }],
3157+
helperData = SchemaUtils.getAuthHelper(openAPISpec, securitySet);
3158+
3159+
expect(helperData.type).to.be.equal('oauth2');
3160+
expect(helperData).to.have.property('oauth2').with.lengthOf(3);
3161+
expect(helperData.oauth2[0]).to.be.an('object');
3162+
expect(helperData.oauth2[0].key).to.be.equal('scope');
3163+
expect(helperData.oauth2[0].value).to.be.equal('write:pets read:pets');
3164+
});
3165+
});

0 commit comments

Comments
 (0)