Skip to content

Commit 1ff5c7f

Browse files
authored
fix: pass unrecognised security schemes (#111)
1 parent 84e96ba commit 1ff5c7f

File tree

4 files changed

+231
-183
lines changed

4 files changed

+231
-183
lines changed

src/compare/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { compareReqPath } from "#compare/requestPath";
99
import { compareReqQuery } from "#compare/requestQuery";
1010
import { compareReqBody } from "#compare/requestBody";
1111
import { compareReqHeader } from "#compare/requestHeader";
12+
import { compareReqSecurity } from "#compare/requestSecurity";
1213
import { compareResBody } from "#compare/responseBody";
1314
import { compareResHeader } from "#compare/responseHeader";
1415
import { parse as parseOas } from "#documents/oas";
@@ -130,6 +131,13 @@ export class Comparator {
130131
continue;
131132
}
132133

134+
yield* compareReqSecurity(
135+
this.#ajvCoerce,
136+
route,
137+
interaction,
138+
index,
139+
this.#config,
140+
);
133141
yield* compareReqHeader(
134142
this.#ajvCoerce,
135143
route,

src/compare/requestHeader.ts

Lines changed: 21 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -178,122 +178,16 @@ export function* compareReqHeader(
178178

179179
// security headers
180180
// ----------------
181+
const securityHeaders = [];
181182
if (isValidRequest(interaction)) {
182-
let isSecured = false;
183-
const maybeResults: Result[] = [];
184183
for (const scheme of operation.security || []) {
185-
if (Object.keys(scheme).length === 0) {
186-
isSecured = true;
187-
break;
188-
}
189184
for (const schemeName of Object.keys(scheme)) {
190185
const scheme = securitySchemes[schemeName];
191-
switch (scheme?.type) {
192-
case "apiKey":
193-
switch (scheme.in) {
194-
case "header":
195-
if (requestHeaders.has(scheme.name)) {
196-
isSecured = true;
197-
} else {
198-
maybeResults.push({
199-
code: "request.authorization.missing",
200-
message:
201-
"Request Authorization header is missing but is required by the spec file",
202-
mockDetails: {
203-
...baseMockDetails(interaction),
204-
location: `[root].interactions[${index}].request.headers`,
205-
value: get(interaction, "request.headers"),
206-
},
207-
specDetails: {
208-
location: `[root].paths.${path}.${method}`,
209-
pathMethod: method,
210-
pathName: path,
211-
value: operation,
212-
},
213-
type: "error",
214-
});
215-
}
216-
requestHeaders.delete(scheme.name);
217-
break;
218-
case "cookie":
219-
case "query":
220-
}
221-
break;
222-
case "basic": {
223-
const basicAuth = requestHeaders.get("authorization") || "";
224-
if (basicAuth.startsWith("Basic ")) {
225-
isSecured = true;
226-
} else {
227-
maybeResults.push({
228-
code: "request.authorization.missing",
229-
message:
230-
"Request Authorization header is missing but is required by the spec file",
231-
mockDetails: {
232-
...baseMockDetails(interaction),
233-
location: `[root].interactions[${index}].request.headers`,
234-
value: get(interaction, "request.headers"),
235-
},
236-
specDetails: {
237-
location: `[root].paths.${path}.${method}`,
238-
pathMethod: method,
239-
pathName: path,
240-
value: operation,
241-
},
242-
type: "error",
243-
});
244-
}
245-
break;
246-
}
247-
case "http": {
248-
const auth = requestHeaders.get("authorization") || "";
249-
let isValid = false;
250-
switch (scheme.scheme) {
251-
case "basic":
252-
isValid = auth.toLowerCase().startsWith("basic ");
253-
break;
254-
case "bearer":
255-
isValid = auth.toLowerCase().startsWith("bearer ");
256-
break;
257-
}
258-
259-
if (config.get("no-authorization-schema")) {
260-
isValid = requestHeaders.get("authorization") !== null;
261-
}
262-
263-
if (isValid) {
264-
isSecured = true;
265-
} else {
266-
maybeResults.push({
267-
code: "request.authorization.missing",
268-
message:
269-
"Request Authorization header is missing but is required by the spec file",
270-
mockDetails: {
271-
...baseMockDetails(interaction),
272-
location: `[root].interactions[${index}].request.headers`,
273-
value: get(interaction, "request.headers"),
274-
},
275-
specDetails: {
276-
location: `[root].paths.${path}.${method}`,
277-
pathMethod: method,
278-
pathName: path,
279-
value: operation,
280-
},
281-
type: "error",
282-
});
283-
}
284-
break;
285-
}
286-
case "mutualTLS":
287-
case "oauth2":
288-
case "openIdConnect":
289-
// ignore
186+
if (scheme && scheme.type === "apiKey" && scheme.in === "header") {
187+
securityHeaders.push(scheme.name.toLowerCase());
290188
}
291189
}
292190
}
293-
294-
if (!isSecured) {
295-
yield* maybeResults;
296-
}
297191
}
298192

299193
// specified headers
@@ -396,22 +290,24 @@ export function* compareReqHeader(
396290
// -----------------
397291
if (isValidRequest(interaction)) {
398292
for (const [headerName, headerValue] of requestHeaders.entries()) {
399-
yield {
400-
code: "request.header.unknown",
401-
message: `Request header is not defined in the spec file: ${headerName}`,
402-
mockDetails: {
403-
...baseMockDetails(interaction),
404-
location: `[root].interactions[${index}].request.headers.${headerName}`,
405-
value: headerValue,
406-
},
407-
specDetails: {
408-
location: `[root].paths.${path}.${method}`,
409-
pathMethod: method,
410-
pathName: path,
411-
value: operation,
412-
},
413-
type: "warning",
414-
};
293+
if (!securityHeaders.includes(headerName)) {
294+
yield {
295+
code: "request.header.unknown",
296+
message: `Request header is not defined in the spec file: ${headerName}`,
297+
mockDetails: {
298+
...baseMockDetails(interaction),
299+
location: `[root].interactions[${index}].request.headers.${headerName}`,
300+
value: headerValue,
301+
},
302+
specDetails: {
303+
location: `[root].paths.${path}.${method}`,
304+
pathMethod: method,
305+
pathName: path,
306+
value: operation,
307+
},
308+
type: "warning",
309+
};
310+
}
415311
}
416312
}
417313
}

src/compare/requestQuery.ts

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -110,74 +110,38 @@ export function* compareReqQuery(
110110
delete searchParamsParsed[dereferencedParameter.name];
111111
}
112112

113+
const securityQueries = [];
113114
if (isValidRequest(interaction)) {
114115
for (const scheme of operation.security || []) {
115116
for (const schemeName of Object.keys(scheme)) {
116117
const scheme = securitySchemes[schemeName];
117-
switch (scheme?.type) {
118-
case "apiKey":
119-
switch (scheme.in) {
120-
case "query":
121-
if (!searchParamsParsed[scheme.name]) {
122-
yield {
123-
code: "request.authorization.missing",
124-
message:
125-
"Request Authorization query is missing but is required by the spec file",
126-
mockDetails: {
127-
...baseMockDetails(interaction),
128-
location: `[root].interactions[${index}].request.query`,
129-
value: get(interaction, "request.query"),
130-
},
131-
specDetails: {
132-
location: `[root].paths.${path}.${method}`,
133-
pathMethod: method,
134-
pathName: path,
135-
value: operation,
136-
},
137-
type: "error",
138-
};
139-
}
140-
delete searchParamsParsed[scheme.name];
141-
break;
142-
case "cookie":
143-
case "header":
144-
// ignore
145-
}
146-
break;
147-
case "http":
148-
switch (scheme.scheme) {
149-
case "basic":
150-
case "bearer":
151-
// ignore
152-
}
153-
break;
154-
case "mutualTLS":
155-
case "oauth2":
156-
case "openIdConnect":
157-
// ignore
118+
if (scheme?.type === "apiKey" && scheme?.in === "query") {
119+
securityQueries.push(scheme.name);
158120
}
159121
}
160122
}
161123
}
162124

163125
if (isValidRequest(interaction)) {
164-
for (const [key, value] of Object.entries(searchParamsParsed)) {
165-
yield {
166-
code: "request.query.unknown",
167-
message: `Query parameter is not defined in the spec file: ${key}`,
168-
mockDetails: {
169-
...baseMockDetails(interaction),
170-
location: `[root].interactions[${index}].request.query.${key}`,
171-
value: value,
172-
},
173-
specDetails: {
174-
location: `[root].paths.${path}.${method}`,
175-
pathMethod: method,
176-
pathName: path,
177-
value: operation,
178-
},
179-
type: "warning",
180-
};
126+
for (const [queryName, queryValue] of Object.entries(searchParamsParsed)) {
127+
if (!securityQueries.includes(queryName)) {
128+
yield {
129+
code: "request.query.unknown",
130+
message: `Query parameter is not defined in the spec file: ${queryName}`,
131+
mockDetails: {
132+
...baseMockDetails(interaction),
133+
location: `[root].interactions[${index}].request.query.${queryName}`,
134+
value: queryValue,
135+
},
136+
specDetails: {
137+
location: `[root].paths.${path}.${method}`,
138+
pathMethod: method,
139+
pathName: path,
140+
value: operation,
141+
},
142+
type: "warning",
143+
};
144+
}
181145
}
182146
}
183147
}

0 commit comments

Comments
 (0)