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

Commit 38fdacc

Browse files
authored
Merge pull request #334 from apiaryio/pksunkara/security
Further implementation of Security
2 parents 3c00de4 + b5ee894 commit 38fdacc

23 files changed

+1903
-134
lines changed

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Key:
2020
| servers | [](https://github.com/apiaryio/api-elements.js/issues/76) |
2121
| paths | [~](#paths-object) |
2222
| components | ~ |
23-
| security | [](https://github.com/apiaryio/api-elements.js/issues/77) |
23+
| security | |
2424
| tags | [](https://github.com/apiaryio/api-elements.js/issues/75) |
2525
| externalDocs | [](https://github.com/apiaryio/api-elements.js/issues/82) |
2626

@@ -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/329) |
75+
| security | |
7676
| servers | [](https://github.com/apiaryio/api-elements.js/issues/76) |
7777

7878
## Parameter Object
@@ -211,15 +211,17 @@ support.
211211

212212
## Security Scheme Object
213213

214+
See https://github.com/apiaryio/api-elements.js/issues/329 to track things left to do.
215+
214216
| Field Name | Support |
215217
|:--|:--|
216218
| type | [~](#security-scheme-type) |
217219
| description ||
218220
| name ||
219-
| in | ~ |
221+
| in | |
220222
| scheme | ~ |
221223
| bearerFormat ||
222-
| flows | |
224+
| flows | |
223225
| openIdConnectUrl ||
224226

225227
## Security Scheme Type
@@ -228,7 +230,7 @@ support.
228230
|:--|:--|
229231
| apiKey ||
230232
| http ||
231-
| oauth2 | |
233+
| oauth2 | |
232234
| openIdConnect ||
233235

234236
## Example Object

packages/fury-adapter-oas3-parser/lib/context.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ class Context {
1616
registerId(id) {
1717
return this.state.registerId(id);
1818
}
19+
20+
oauthFlow(id, flow) {
21+
return this.state.oauthFlow(id, flow);
22+
}
23+
24+
registerScheme(id) {
25+
return this.state.registerScheme(id);
26+
}
27+
28+
hasScheme(id) {
29+
return this.state.hasScheme(id);
30+
}
1931
}
2032

2133
module.exports = Context;

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

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,28 +152,47 @@ function parseComponentsObject(context, element) {
152152
const parseSecuritySchemes = pipeParseResult(namespace,
153153
parseComponentObjectMember(parseSecuritySchemeObject),
154154
(object) => {
155+
const parseResult = new namespace.elements.ParseResult([]);
155156
const array = new namespace.elements.Array([]);
156157

157158
object.forEach((value, key) => {
159+
const keyValue = key.toValue();
160+
158161
if (value) {
159162
if (value instanceof namespace.elements.AuthScheme) {
160-
// eslint-disable-next-line no-param-reassign
161-
value.id = key.clone();
162-
array.push(value);
163+
if (!context.registerScheme(keyValue)) {
164+
parseResult.push(createWarning(namespace,
165+
`'${keyValue}' security scheme is already defined`, key));
166+
} else {
167+
// eslint-disable-next-line no-param-reassign
168+
value.id = key.clone();
169+
array.push(value);
170+
}
163171

164172
return;
165173
}
166174

167175
// append oauth2 flow names
168176
value.forEach((flow) => {
169-
// eslint-disable-next-line no-param-reassign
170-
flow.id = `${key.toValue()} ${flow.grantTypeValue}`;
171-
array.push(flow);
177+
const flowSchemeName = `${keyValue} ${flow.grantTypeValue}`;
178+
179+
if (!context.oauthFlow(keyValue, flowSchemeName)) {
180+
parseResult.push(createWarning(namespace,
181+
`'${flowSchemeName}' security scheme can't be created from '${keyValue}' security scheme because it is already defined`, key));
182+
} else {
183+
// eslint-disable-next-line no-param-reassign
184+
flow.id = flowSchemeName;
185+
array.push(flow);
186+
}
172187
});
173188
}
174189
});
175190

176-
return array;
191+
if (!array.isEmpty) {
192+
parseResult.push(array);
193+
}
194+
195+
return parseResult;
177196
});
178197

179198
const parseMember = R.cond([

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ const parseOpenAPI = require('../openapi');
1515
const parseInfoObject = require('./parseInfoObject');
1616
const parsePathsObject = require('./parsePathsObject');
1717
const parseComponentsObject = require('./parseComponentsObject');
18+
const parseSecurityRequirementsArray = require('./parseSecurityRequirementsArray');
1819

1920
const name = 'OpenAPI Object';
2021
const requiredKeys = ['openapi', 'info', 'paths'];
21-
const unsupportedKeys = ['servers', 'security', 'tags', 'externalDocs'];
22+
const unsupportedKeys = ['servers', 'tags', 'externalDocs'];
2223

2324
/**
2425
* Returns whether the given member element is unsupported
@@ -109,8 +110,9 @@ function parseOASObject(context, object) {
109110
const parseMember = R.cond([
110111
[hasKey('openapi'), parseOpenAPI(context)],
111112
[hasKey('info'), R.compose(parseInfoObject(context), getValue)],
112-
[hasKey('paths'), R.compose(asArray, parsePathsObject(context), getValue)],
113113
[hasKey('components'), R.compose(parseComponentsObject(context), getValue)],
114+
[hasKey('paths'), R.compose(asArray, parsePathsObject(context), getValue)],
115+
[hasKey('security'), R.compose(parseSecurityRequirementsArray(context), getValue)],
114116

115117
// FIXME Support exposing extensions into parse result
116118
[isExtension, () => new namespace.elements.ParseResult()],
@@ -126,6 +128,7 @@ function parseOASObject(context, object) {
126128
(object) => {
127129
const api = object.get('info');
128130
const components = object.get('components');
131+
const security = object.get('security');
129132

130133
if (components) {
131134
const schemes = R.or(components.get('securitySchemes'), new namespace.elements.Array());
@@ -142,6 +145,20 @@ function parseOASObject(context, object) {
142145
api.content = api.content.concat(resources.content);
143146
}
144147

148+
api.resources.forEach((resource) => {
149+
resource.transitions.forEach((transition) => {
150+
transition.transactions.forEach((transaction) => {
151+
if (!transaction.authSchemes && security && !security.isEmpty) {
152+
transaction.attributes.set('authSchemes', security.clone());
153+
}
154+
155+
if (transaction.authSchemes && transaction.authSchemes.isEmpty) {
156+
transaction.attributes.remove('authSchemes');
157+
}
158+
});
159+
});
160+
});
161+
145162
if (components) {
146163
const schemas = R.or(components.get('schemas'), new namespace.elements.Array())
147164
.content

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

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
const R = require('ramda');
22
const {
3-
isArray, isMember, isExtension, hasKey, getValue,
3+
isMember, isExtension, hasKey, getValue,
44
} = require('../../predicates');
55
const {
6-
createWarning,
76
createUnsupportedMemberWarning,
87
createInvalidMemberWarning,
98
createIdentifierNotUniqueWarning,
@@ -14,8 +13,8 @@ const parseObject = require('../parseObject');
1413
const parseString = require('../parseString');
1514
const parseResponsesObject = require('./parseResponsesObject');
1615
const parseParameterObjects = require('./parseParameterObjects');
17-
const parseSecurityRequirementObject = require('./parseSecurityRequirementObject');
1816
const parseRequestBodyObject = require('./parseRequestBodyObject');
17+
const parseSecurityRequirementsArray = require('./parseSecurityRequirementsArray');
1918
const parseReference = require('../parseReference');
2019

2120
const parseRequestBodyObjectOrRef = parseReference('requestBodies', parseRequestBodyObject);
@@ -104,32 +103,14 @@ function parseOperationObject(context, path, member) {
104103
),
105104
]));
106105

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 link = new namespace.elements.Link();
116-
link.relation = 'profile';
117-
link.href = 'https://github.com/refractproject/rfcs/issues/39';
118-
119-
const allOf = new namespace.elements.Extension(requirement.content);
120-
allOf.meta.set('links', new namespace.elements.Array([link]));
121-
122-
return allOf;
123-
}));
124-
125106
const parseMember = R.cond([
126107
[hasKey('summary'), parseString(context, name, false)],
127108
[hasKey('description'), parseCopy(context, name, false)],
128109
[hasKey('operationId'), pipeParseResult(namespace, parseString(context, name, false), parseOperationId)],
129110
[hasKey('responses'), R.compose(parseResponsesObject(context), getValue)],
130111
[hasKey('requestBody'), R.compose(parseRequestBodyObjectOrRef(context), getValue)],
131112
[hasKey('parameters'), R.compose(parseParameterObjects(context, name), getValue)],
132-
[hasKey('security'), R.compose(parseSecurity, getValue)],
113+
[hasKey('security'), R.compose(parseSecurityRequirementsArray(context), getValue)],
133114

134115
[isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)],
135116

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ 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
44-
const arr = new namespace.elements.Array([]);
43+
const parseResult = new namespace.elements.ParseResult([]);
44+
const array = new namespace.elements.Array([]);
4545

4646
securityRequirement.forEach((value, key) => {
4747
let e;
48+
const schemeName = key.toValue();
49+
4850
const scopes = value.map(scope => scope.toValue());
4951

5052
if (scopes.length) {
@@ -53,11 +55,32 @@ function parseSecurityRequirementObject(context, object) {
5355
e = new namespace.elements.AuthScheme({});
5456
}
5557

56-
e.element = key.toValue();
57-
arr.push(e);
58+
// Expand oauth2 flows
59+
const hasFlows = context.state.oauthFlows[schemeName] || [];
60+
61+
if (hasFlows.length !== 0) {
62+
hasFlows.forEach((flow) => {
63+
const element = e.clone();
64+
element.element = flow;
65+
array.push(element);
66+
});
67+
68+
return;
69+
}
70+
71+
if (!context.hasScheme(schemeName)) {
72+
parseResult.push(createWarning(namespace, `'${schemeName}' security scheme not found`, key));
73+
} else {
74+
e.element = schemeName;
75+
array.push(e);
76+
}
5877
});
5978

60-
return arr;
79+
if (!array.isEmpty) {
80+
parseResult.push(array);
81+
}
82+
83+
return parseResult;
6184
});
6285

6386
return parseSecurityRequirement(object);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const R = require('ramda');
2+
const pipeParseResult = require('../../pipeParseResult');
3+
const parseArray = require('../parseArray');
4+
const parseSecurityRequirementObject = require('./parseSecurityRequirementObject');
5+
6+
const name = 'Security Requirements Array';
7+
8+
/**
9+
* Parse Security Requirements Array
10+
*
11+
* @param namespace {Namespace}
12+
* @param element {Element}
13+
* @returns ParseResult
14+
*
15+
* @private
16+
*/
17+
function parseSecurityRequirementsArray(context, element) {
18+
const { namespace } = context;
19+
20+
const parseSecurityRequirements = pipeParseResult(namespace,
21+
parseArray(context, name, R.curry(parseSecurityRequirementObject)(context)),
22+
requirements => requirements.map((requirement) => {
23+
if (requirement.length === 1) {
24+
return requirement.get(0);
25+
}
26+
27+
const link = new namespace.elements.Link();
28+
link.relation = 'profile';
29+
link.href = 'https://github.com/refractproject/rfcs/issues/39';
30+
31+
const allOf = new namespace.elements.Extension(requirement.content);
32+
allOf.meta.set('links', new namespace.elements.Array([link]));
33+
34+
return allOf;
35+
}));
36+
37+
return parseSecurityRequirements(element);
38+
}
39+
40+
module.exports = R.curry(parseSecurityRequirementsArray);

packages/fury-adapter-oas3-parser/lib/state.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
class State {
22
constructor() {
33
this.registeredIds = new Set();
4+
5+
this.registeredSchemes = new Set();
6+
this.oauthFlows = {};
47
}
58

69
registerId(id) {
@@ -11,6 +14,26 @@ class State {
1114
this.registeredIds.add(id);
1215
return true;
1316
}
17+
18+
oauthFlow(id, flow) {
19+
this.oauthFlows[id] = this.oauthFlows[id] || new Set();
20+
this.oauthFlows[id].add(flow);
21+
22+
return this.registerScheme(flow);
23+
}
24+
25+
registerScheme(id) {
26+
if (this.registeredSchemes.has(id)) {
27+
return false;
28+
}
29+
30+
this.registeredSchemes.add(id);
31+
return true;
32+
}
33+
34+
hasScheme(id) {
35+
return this.registeredSchemes.has(id);
36+
}
1437
}
1538

1639
module.exports = State;

0 commit comments

Comments
 (0)