Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 1dc1247

Browse files
committed
feat(oas3): finished security support
1 parent c2cf258 commit 1dc1247

13 files changed

+530
-32
lines changed

packages/fury-adapter-oas3-parser/STATUS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Key:
7272
| responses | [~](#responses-object) |
7373
| callbacks | [](https://github.com/apiaryio/api-elements.js/issues/74) |
7474
| deprecated ||
75-
| security | [](https://github.com/apiaryio/api-elements.js/issues/77) |
75+
| security | |
7676
| servers | [](https://github.com/apiaryio/api-elements.js/issues/76) |
7777

7878
## Parameter Object

packages/fury-adapter-oas3-parser/lib/parser/oas/parseComponentsObject.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,20 @@ function parseComponentsObject(context, element) {
156156

157157
object.forEach((value, key) => {
158158
if (value) {
159-
// eslint-disable-next-line no-param-reassign
160-
value.meta.id = key.clone();
161-
array.push(value);
159+
if (value instanceof namespace.elements.AuthScheme) {
160+
// eslint-disable-next-line no-param-reassign
161+
value.meta.id = key.clone();
162+
array.push(value);
163+
164+
return;
165+
}
166+
167+
// append oauth2 flow names
168+
value.forEach((flow) => {
169+
// eslint-disable-next-line no-param-reassign
170+
flow.meta.id = `${key.toValue()} ${flow.grantType}`;
171+
array.push(flow);
172+
});
162173
}
163174
});
164175

packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowObject.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,22 @@ function parseOauthFlowObject(context, object) {
4040
return scope;
4141
}));
4242

43+
const parseUrl = pipeParseResult(namespace,
44+
parseString(context, name, false),
45+
(url) => {
46+
const transition = new namespace.elements.Transition();
47+
48+
transition.relation = url.key.toValue().slice(0, -3); // remove 'Url' from key
49+
transition.href = url.value.clone();
50+
51+
return transition;
52+
});
53+
4354
const parseMember = R.cond([
4455
[hasKey('scopes'), R.compose(parseScopes, getValue)],
45-
[hasKey('refreshUrl'), parseString(context, name, false)],
46-
[hasKey('authorizationUrl'), parseString(context, name, false)],
47-
[hasKey('tokenUrl'), parseString(context, name, false)],
56+
[hasKey('refreshUrl'), parseUrl],
57+
[hasKey('authorizationUrl'), parseUrl],
58+
[hasKey('tokenUrl'), parseUrl],
4859

4960
// FIXME Support exposing extensions into parse result
5061
[isExtension, () => new namespace.elements.ParseResult()],

packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowsObject.js

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ function parseOauthFlowsObject(context, object) {
7272
authScheme.push(new namespace.elements.Member('grantType', grantTypes[member.key.toValue()]));
7373
authScheme.push(member.value.getMember('scopes'));
7474

75+
R.filter(R.is(namespace.elements.Transition), member.value).forEach((item) => {
76+
authScheme.push(item);
77+
});
78+
7579
return authScheme;
7680
}));
7781

packages/fury-adapter-oas3-parser/lib/parser/oas/parseOperationObject.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const R = require('ramda');
22
const {
3-
isMember, isExtension, hasKey, getValue,
3+
isArray, isMember, isExtension, hasKey, getValue,
44
} = require('../../predicates');
55
const {
6+
createWarning,
67
createUnsupportedMemberWarning,
78
createInvalidMemberWarning,
89
createIdentifierNotUniqueWarning,
@@ -13,6 +14,7 @@ const parseObject = require('../parseObject');
1314
const parseString = require('../parseString');
1415
const parseResponsesObject = require('./parseResponsesObject');
1516
const parseParameterObjects = require('./parseParameterObjects');
17+
const parseSecurityRequirementObject = require('./parseSecurityRequirementObject');
1618
const parseRequestBodyObject = require('./parseRequestBodyObject');
1719
const parseReference = require('../parseReference');
1820

@@ -21,7 +23,7 @@ const parseRequestBodyObjectOrRef = parseReference('requestBodies', parseRequest
2123
const name = 'Operation Object';
2224
const requiredKeys = ['responses'];
2325
const unsupportedKeys = [
24-
'tags', 'externalDocs', 'callbacks', 'deprecated', 'security',
26+
'tags', 'externalDocs', 'callbacks', 'deprecated',
2527
];
2628
const isUnsupportedKey = R.anyPass(R.map(hasKey, unsupportedKeys));
2729

@@ -102,13 +104,26 @@ function parseOperationObject(context, path, member) {
102104
),
103105
]));
104106

107+
const parseSecurity = pipeParseResult(namespace,
108+
R.unless(isArray, createWarning(namespace, `'${name}' 'security' is not an array`)),
109+
R.compose(R.chain(parseSecurityRequirementObject(context)), R.constructN(1, namespace.elements.Array)),
110+
requirements => requirements.map((requirement) => {
111+
if (requirement.length === 1) {
112+
return requirement.get(0);
113+
}
114+
115+
const authSchemeRequirement = new namespace.elements.AuthSchemeRequirement(requirement.content);
116+
return authSchemeRequirement;
117+
}));
118+
105119
const parseMember = R.cond([
106120
[hasKey('summary'), parseString(context, name, false)],
107121
[hasKey('description'), parseCopy(context, name, false)],
108122
[hasKey('operationId'), pipeParseResult(namespace, parseString(context, name, false), parseOperationId)],
109123
[hasKey('responses'), R.compose(parseResponsesObject(context), getValue)],
110124
[hasKey('requestBody'), R.compose(parseRequestBodyObjectOrRef(context), getValue)],
111125
[hasKey('parameters'), R.compose(parseParameterObjects(context, name), getValue)],
126+
[hasKey('security'), R.compose(parseSecurity, getValue)],
112127

113128
[isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)],
114129

@@ -161,6 +176,13 @@ function parseOperationObject(context, path, member) {
161176
}
162177
}
163178

179+
const security = operation.get('security');
180+
if (security) {
181+
transactions.forEach((transaction) => {
182+
transaction.attributes.set('authSchemes', security);
183+
});
184+
}
185+
164186
return transition;
165187
});
166188

packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecurityRequirementObject.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ function parseSecurityRequirementObject(context, object) {
4040
const parseSecurityRequirement = pipeParseResult(namespace,
4141
parseObject(context, name, parseMember),
4242
(securityRequirement) => {
43+
// TODO: expand oauth requirements into multiples depending on flows
4344
const arr = new namespace.elements.Array([]);
4445

4546
securityRequirement.forEach((value, key) => {
4647
let e;
4748
const scopes = value.map(scope => scope.toValue());
4849

4950
if (scopes.length) {
50-
e = new namespace.elements.Object({ scopes });
51+
e = new namespace.elements.AuthScheme({ scopes });
5152
} else {
52-
e = new namespace.elements.Object({});
53+
e = new namespace.elements.AuthScheme({});
5354
}
5455

5556
e.element = key.toValue();

packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecuritySchemeObject.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
const pipeParseResult = require('../../pipeParseResult');
1111
const parseObject = require('../parseObject');
1212
const parseString = require('../parseString');
13+
const parseOauthFlowsObject = require('./parseOauthFlowsObject');
1314

1415
const name = 'Security Scheme Object';
1516
const requiredKeys = ['type'];
@@ -19,7 +20,7 @@ const passThrough = R.anyPass(R.map(hasKey, ['name', 'in', 'scheme', 'flows']));
1920

2021
const isApiKeyScheme = securityScheme => securityScheme.getValue('type') === 'apiKey';
2122
const isHttpScheme = securityScheme => securityScheme.getValue('type') === 'http';
22-
// const isOauth2Scheme = securityScheme => securityScheme.getValue('type') === 'oauth2';
23+
const isOauth2Scheme = securityScheme => securityScheme.getValue('type') === 'oauth2';
2324

2425
const isValidTypeValue = R.anyPass(R.map(hasValue, ['apiKey', 'http', 'oauth2', 'openIdConnect']));
2526
const isSupportedType = R.anyPass(R.map(hasValue, ['apiKey', 'http', 'oauth2']));
@@ -58,6 +59,16 @@ function validateHttpScheme(context, securityScheme) {
5859
return parseObject(context, name, parseMember, ['scheme'], [], true)(securityScheme);
5960
}
6061

62+
function validateOauth2Scheme(context, securityScheme) {
63+
const parseMember = R.cond([
64+
[hasKey('flows'), R.compose(parseOauthFlowsObject(context), getValue)],
65+
66+
[R.T, e => e],
67+
]);
68+
69+
return parseObject(context, name, parseMember, ['flows'], [], true)(securityScheme);
70+
}
71+
6172
/**
6273
* Parse Security Scheme Object
6374
*
@@ -104,20 +115,33 @@ function parseSecuritySchemeObject(context, object) {
104115
parseObject(context, name, parseMember, requiredKeys, [], true),
105116
R.when(isApiKeyScheme, R.curry(validateApiKeyScheme)(context)),
106117
R.when(isHttpScheme, R.curry(validateHttpScheme)(context)),
107-
// R.when(isOauth2Scheme, parseSecuritySchemeFlowsObject),
118+
R.when(isOauth2Scheme, R.curry(validateOauth2Scheme)(context)),
108119
(securityScheme) => {
109120
const authScheme = new namespace.elements.AuthScheme();
110121

111122
const type = securityScheme.getValue('type');
112123
const scheme = securityScheme.getValue('scheme');
124+
const description = securityScheme.get('description');
125+
126+
if (type === 'oauth2') {
127+
const flows = securityScheme.get('flows');
128+
129+
if (description) {
130+
flows.forEach((flow) => {
131+
// eslint-disable-next-line no-param-reassign
132+
flow.description = description;
133+
});
134+
}
135+
136+
return flows;
137+
}
113138

114139
if (type === 'apiKey' || (type === 'http' && scheme === 'bearer')) {
115140
authScheme.element = 'Token Authentication Scheme';
116141
} else if (type === 'http' && scheme === 'basic') {
117142
authScheme.element = 'Basic Authentication Scheme';
118143
}
119144

120-
const description = securityScheme.get('description');
121145
if (description) {
122146
authScheme.description = description;
123147
}

packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseComponentsObject-test.js

+33
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,39 @@ describe('Components Object', () => {
343343
expect(securitySchemes.get(0).meta.id.toValue()).to.equal('token');
344344
});
345345

346+
it('parses oauth2 securityScheme with multiple flows', () => {
347+
const components = new namespace.elements.Object({
348+
securitySchemes: {
349+
oauth: {
350+
type: 'oauth2',
351+
flows: {
352+
password: {
353+
tokenUrl: '/token',
354+
scopes: {},
355+
},
356+
implicit: {
357+
authorizationUrl: '/authorization',
358+
scopes: {},
359+
},
360+
},
361+
},
362+
},
363+
});
364+
365+
const parseResult = parse(context, components);
366+
expect(parseResult.length).to.equal(1);
367+
368+
const parsedComponents = parseResult.get(0);
369+
expect(parsedComponents).to.be.instanceof(namespace.elements.Object);
370+
371+
const securitySchemes = parsedComponents.get('securitySchemes');
372+
expect(securitySchemes).to.be.instanceof(namespace.elements.Array);
373+
expect(securitySchemes.get(0)).to.be.instanceof(namespace.elements.AuthScheme);
374+
expect(securitySchemes.get(0).meta.id.toValue()).to.equal('oauth resource owner password credentials');
375+
expect(securitySchemes.get(1)).to.be.instanceof(namespace.elements.AuthScheme);
376+
expect(securitySchemes.get(1).meta.id.toValue()).to.equal('oauth implicit');
377+
});
378+
346379
it('handles invalid security scheme', () => {
347380
const components = new namespace.elements.Object({
348381
securitySchemes: {

packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowObject-test.js

+54
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ describe('Oauth Flow Object', () => {
9999
expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array);
100100
expect(parseResult.get(0).get('scopes').length).to.equal(0);
101101
});
102+
103+
it('parses it correctly', () => {
104+
const oauthFlow = new namespace.elements.Object({
105+
scopes: {},
106+
refreshUrl: '/refresh',
107+
});
108+
109+
const parseResult = parse(context, oauthFlow);
110+
111+
expect(parseResult.length).to.equal(1);
112+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object);
113+
114+
const refreshUrl = parseResult.get(0).get('refreshUrl');
115+
116+
expect(refreshUrl).to.be.instanceof(namespace.elements.Transition);
117+
expect(refreshUrl.relation.toValue()).to.equal('refresh');
118+
expect(refreshUrl.href.toValue()).to.equal('/refresh');
119+
});
102120
});
103121

104122
describe('#tokenUrl', () => {
@@ -117,6 +135,24 @@ describe('Oauth Flow Object', () => {
117135
expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array);
118136
expect(parseResult.get(0).get('scopes').length).to.equal(0);
119137
});
138+
139+
it('parses it correctly', () => {
140+
const oauthFlow = new namespace.elements.Object({
141+
scopes: {},
142+
tokenUrl: '/token',
143+
});
144+
145+
const parseResult = parse(context, oauthFlow);
146+
147+
expect(parseResult.length).to.equal(1);
148+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object);
149+
150+
const tokenUrl = parseResult.get(0).get('tokenUrl');
151+
152+
expect(tokenUrl).to.be.instanceof(namespace.elements.Transition);
153+
expect(tokenUrl.relation.toValue()).to.equal('token');
154+
expect(tokenUrl.href.toValue()).to.equal('/token');
155+
});
120156
});
121157

122158
describe('#authorizationUrl', () => {
@@ -135,6 +171,24 @@ describe('Oauth Flow Object', () => {
135171
expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array);
136172
expect(parseResult.get(0).get('scopes').length).to.equal(0);
137173
});
174+
175+
it('parses it correctly', () => {
176+
const oauthFlow = new namespace.elements.Object({
177+
scopes: {},
178+
authorizationUrl: '/authorization',
179+
});
180+
181+
const parseResult = parse(context, oauthFlow);
182+
183+
expect(parseResult.length).to.equal(1);
184+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object);
185+
186+
const authorizationUrl = parseResult.get(0).get('authorizationUrl');
187+
188+
expect(authorizationUrl).to.be.instanceof(namespace.elements.Transition);
189+
expect(authorizationUrl.relation.toValue()).to.equal('authorization');
190+
expect(authorizationUrl.href.toValue()).to.equal('/authorization');
191+
});
138192
});
139193

140194
it('provides warning for invalid keys', () => {

0 commit comments

Comments
 (0)