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

Commit c520cdd

Browse files
klokanekylef
authored andcommitted
feat(oas3): validate parameter name according to location (#213)
1 parent 49e2828 commit c520cdd

File tree

5 files changed

+149
-38
lines changed

5 files changed

+149
-38
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Fury OAS3 Parser Changelog
22

3+
## master
4+
5+
### Enhancements
6+
7+
- validate 'Parameter object' 'name' according to location ('in' parmaeter)
8+
39
## 0.7.3 (2019-04-05)
410

511
### Bug Fixes

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@ const isSupportedIn = R.anyPass([
2929
]);
3030

3131
const unreservedCharacterRegex = /^[A-z0-9\\.\\_\\~\\-]+$/;
32-
function nameContainsReservedCharacter(member) {
33-
return !unreservedCharacterRegex.test(member.value.toValue());
32+
const reservedHeaderNamesRegex = /Accept|Content-Type|Authorization/i;
33+
34+
const encodeQueryName = name => encodeURIComponent(name)
35+
.replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`) // as sugested by https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
36+
.replace(/%25([0-9a-f]{2})/gi, (match, hex) => `%${hex}`); // revert double encoded sequences
37+
38+
39+
function nameContainsReservedCharacter(parameter) {
40+
return !unreservedCharacterRegex.test(parameter.get('name').toValue());
3441
}
3542

3643
function validateRequiredForPathParameter(context, object, parameter) {
@@ -81,14 +88,7 @@ function parseParameterObject(context, object) {
8188
validateIn,
8289
ensureSupportedIn);
8390

84-
const createUnsupportedNameError = R.compose(
85-
createError(namespace, `'${name}' 'name' contains unsupported characters. Only alphanumeric characters are currently supported`),
86-
getValue
87-
);
88-
const validateName = R.when(nameContainsReservedCharacter, createUnsupportedNameError);
89-
const parseName = pipeParseResult(namespace,
90-
parseString(context, name, true),
91-
validateName);
91+
const parseName = pipeParseResult(namespace, parseString(context, name, true));
9292

9393
const parseMember = R.cond([
9494
[hasKey('name'), parseName],
@@ -106,11 +106,34 @@ function parseParameterObject(context, object) {
106106
[R.T, createInvalidMemberWarning(namespace, name)],
107107
]);
108108

109-
const isPathParameter = parameter => parameter.getValue('in') === 'path';
109+
const createUnsupportedNameError = createError(namespace, `'${name}' 'name' contains unsupported characters. Only alphanumeric characters are currently supported`);
110+
111+
const createReservedHeaderNamesWarning = createWarning(namespace, `'${name}' 'name' in location 'header' should not be 'Accept', 'Content-Type' or 'Authorization'`);
112+
113+
const hasLocation = R.curry((location, parameter) => parameter.getValue('in') === location);
114+
115+
const validatePathName = R.when(nameContainsReservedCharacter, createUnsupportedNameError);
116+
117+
const nameContainsReservedHeaderName = parameter => parameter.get('name')
118+
.toValue()
119+
.match(reservedHeaderNamesRegex);
120+
121+
const sanitizeQueryName = (parameter) => {
122+
const name = parameter.get('name');
123+
name.content = encodeQueryName(name.toValue());
124+
return parameter;
125+
};
126+
127+
const validateHeaderName = pipeParseResult(namespace,
128+
R.when(nameContainsReservedCharacter, createUnsupportedNameError),
129+
R.when(nameContainsReservedHeaderName, createReservedHeaderNamesWarning));
110130

111131
const parseParameter = pipeParseResult(namespace,
112132
parseObject(context, name, parseMember, requiredKeys),
113-
R.when(isPathParameter, R.curry(validateRequiredForPathParameter)(context, object)),
133+
R.when(hasLocation('path'), R.curry(validateRequiredForPathParameter)(context, object)),
134+
R.when(hasLocation('path'), validatePathName),
135+
R.when(hasLocation('query'), sanitizeQueryName),
136+
R.when(hasLocation('header'), validateHeaderName),
114137
(parameter) => {
115138
const example = parameter.get('example');
116139
const member = new namespace.elements.Member(parameter.get('name'), example);

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,9 @@ describe('Operation Object', () => {
384384
const operation = new namespace.elements.Member('get', {
385385
parameters: [
386386
{
387-
name: 'Accept',
387+
name: 'X-RateLimit-Limit',
388388
in: 'header',
389-
example: 'application/json',
389+
example: 3,
390390
},
391391
],
392392
responses: {
@@ -410,8 +410,8 @@ describe('Operation Object', () => {
410410
expect(request.headers).to.be.instanceof(namespace.elements.HttpHeaders);
411411
expect(request.headers.toValue()).to.deep.equal([
412412
{
413-
key: 'Accept',
414-
value: 'application/json',
413+
key: 'X-RateLimit-Limit',
414+
value: 3,
415415
},
416416
]);
417417
});
@@ -439,7 +439,9 @@ describe('Operation Object', () => {
439439

440440
const parseResult = parse(context, path, operation);
441441

442-
expect(parseResult.length).to.equal(1);
442+
expect(parseResult.length).to.equal(2);
443+
444+
expect(parseResult).to.contain.warning('\'Parameter Object\' \'name\' in location \'header\' should not be \'Accept\', \'Content-Type\' or \'Authorization\'');
443445

444446
const transition = parseResult.get(0);
445447
expect(transition).to.be.instanceof(namespace.elements.Transition);
@@ -459,11 +461,6 @@ describe('Operation Object', () => {
459461
it('merges headers with operation headers', () => {
460462
const operation = new namespace.elements.Member('post', {
461463
parameters: [
462-
{
463-
name: 'Content-Type',
464-
in: 'header',
465-
example: 'application/json',
466-
},
467464
{
468465
name: 'Link',
469466
in: 'header',

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

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@ describe('Parameter Object', () => {
3636
it('provides an error when name contains unsupported characters', () => {
3737
const parameter = new namespace.elements.Object({
3838
name: 'hello!',
39-
in: 'query',
39+
in: 'path',
4040
});
4141

4242
const parseResult = parse(context, parameter);
4343

44-
expect(parseResult.length).to.equal(1);
4544
expect(parseResult).to.contain.error("'Parameter Object' 'name' contains unsupported characters. Only alphanumeric characters are currently supported");
4645
});
4746

@@ -62,6 +61,92 @@ describe('Parameter Object', () => {
6261
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Member);
6362
expect(parseResult.get(0).key.toValue()).to.equal(unreserved);
6463
});
64+
65+
it('auto pct-encode reserved chars in query name', () => {
66+
const reserved = '!*\'();:@&=+$,?%#[]';
67+
const encoded = '%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%3F%25%23%5B%5D';
68+
69+
const parameter = new namespace.elements.Object({
70+
name: reserved,
71+
in: 'query',
72+
});
73+
74+
const parseResult = parse(context, parameter);
75+
76+
expect(parseResult.length).to.equal(1);
77+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Member);
78+
expect(parseResult.get(0).key.toValue()).to.equal(encoded);
79+
});
80+
81+
it('avoid double encoding pct-encode in query name', () => {
82+
const input = 'user%5bname%5d';
83+
84+
const parameter = new namespace.elements.Object({
85+
name: input,
86+
in: 'query',
87+
});
88+
89+
const parseResult = parse(context, parameter);
90+
91+
expect(parseResult.length).to.equal(1);
92+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Member);
93+
expect(parseResult.get(0).key.toValue()).to.equal(input);
94+
});
95+
96+
it('allows header name to contain unreserved characters', () => {
97+
const alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
98+
const digit = '0123456789';
99+
const unreserved = `${alpha}${digit}-._~`;
100+
101+
const parameter = new namespace.elements.Object({
102+
name: unreserved,
103+
in: 'header',
104+
});
105+
106+
const parseResult = parse(context, parameter);
107+
108+
expect(parseResult.length).to.equal(1);
109+
expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Member);
110+
expect(parseResult.get(0).key.toValue()).to.equal(unreserved);
111+
});
112+
113+
describe('headers name do not allows reserved words', () => {
114+
it('does not allow `Accept`', () => {
115+
const parameter = new namespace.elements.Object({
116+
name: 'accept',
117+
in: 'header',
118+
});
119+
120+
const parseResult = parse(context, parameter);
121+
122+
expect(parseResult.length).to.equal(1);
123+
expect(parseResult).to.contain.warning('\'Parameter Object\' \'name\' in location \'header\' should not be \'Accept\', \'Content-Type\' or \'Authorization\'');
124+
});
125+
126+
it('does not allow `Content-Type`', () => {
127+
const parameter = new namespace.elements.Object({
128+
name: 'Content-Type',
129+
in: 'header',
130+
});
131+
132+
const parseResult = parse(context, parameter);
133+
134+
expect(parseResult.length).to.equal(1);
135+
expect(parseResult).to.contain.warning('\'Parameter Object\' \'name\' in location \'header\' should not be \'Accept\', \'Content-Type\' or \'Authorization\'');
136+
});
137+
138+
it('does not allow `Authorization', () => {
139+
const parameter = new namespace.elements.Object({
140+
name: 'AUTHORIZATION',
141+
in: 'header',
142+
});
143+
144+
const parseResult = parse(context, parameter);
145+
146+
expect(parseResult.length).to.equal(1);
147+
expect(parseResult).to.contain.warning('\'Parameter Object\' \'name\' in location \'header\' should not be \'Accept\', \'Content-Type\' or \'Authorization\'');
148+
});
149+
});
65150
});
66151

67152
describe('#in', () => {

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ describe('Path Item Object', () => {
290290
const path = new namespace.elements.Member('/', {
291291
parameters: [
292292
{
293-
name: 'Accept',
293+
name: 'X-RateLimit-Limit',
294294
in: 'header',
295-
example: 'application/json',
295+
example: 3,
296296
},
297297
],
298298
get: {
@@ -318,8 +318,8 @@ describe('Path Item Object', () => {
318318
expect(request.headers).to.be.instanceof(namespace.elements.HttpHeaders);
319319
expect(request.headers.toValue()).to.deep.equal([
320320
{
321-
key: 'Accept',
322-
value: 'application/json',
321+
key: 'X-RateLimit-Limit',
322+
value: 3,
323323
},
324324
]);
325325
});
@@ -328,17 +328,17 @@ describe('Path Item Object', () => {
328328
const path = new namespace.elements.Member('/', {
329329
parameters: [
330330
{
331-
name: 'Accept',
331+
name: 'X-RateLimit-Limit',
332332
in: 'header',
333-
example: 'application/json',
333+
example: 3,
334334
},
335335
],
336336
get: {
337337
parameters: [
338338
{
339-
name: 'Accept',
339+
name: 'X-RateLimit-Limit',
340340
in: 'header',
341-
example: 'application/problem+json',
341+
example: 5,
342342
},
343343
],
344344
responses: {
@@ -363,8 +363,8 @@ describe('Path Item Object', () => {
363363
expect(request.headers).to.be.instanceof(namespace.elements.HttpHeaders);
364364
expect(request.headers.toValue()).to.deep.equal([
365365
{
366-
key: 'Accept',
367-
value: 'application/problem+json',
366+
key: 'X-RateLimit-Limit',
367+
value: 5,
368368
},
369369
]);
370370
});
@@ -373,9 +373,9 @@ describe('Path Item Object', () => {
373373
const path = new namespace.elements.Member('/', {
374374
parameters: [
375375
{
376-
name: 'Accept',
376+
name: 'X-RateLimit-Limit',
377377
in: 'header',
378-
example: 'application/json',
378+
example: 3,
379379
},
380380
],
381381
get: {
@@ -412,8 +412,8 @@ describe('Path Item Object', () => {
412412
value: '<https://api.github.com/user/repos?page=3&per_page=100>; rel="next"',
413413
},
414414
{
415-
key: 'Accept',
416-
value: 'application/json',
415+
key: 'X-RateLimit-Limit',
416+
value: 3,
417417
},
418418
]);
419419
});

0 commit comments

Comments
 (0)