Skip to content

Commit

Permalink
(feature) add additional parameters errors for response body
Browse files Browse the repository at this point in the history
  • Loading branch information
NikhilShahi committed Sep 2, 2022
1 parent ed3ae30 commit 5738db8
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 73 deletions.
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@aws-sdk/client-ec2": "^3.142.0",
"@aws-sdk/client-pricing": "^3.142.0",
"@google-cloud/compute": "^3.4.0",
"@leoscope/openapi-response-validator": "^12.0.3",
"@types/async-retry": "^1.4.4",
"@types/newman": "^5.3.0",
"@types/ssh2": "^1.11.5",
Expand All @@ -39,8 +41,8 @@
"node-schedule": "^2.1.0",
"node-ssh": "^13.0.0",
"openapi-request-validator": "^12.0.0",
"openapi-response-validator": "^12.0.0",
"openapi-schema-validator": "^12.0.0",
"openapi-types": "^12.0.0",
"pg": "^8.7.3",
"pg-protocol": "^1.5.0",
"reflect-metadata": "^0.1.13",
Expand Down
109 changes: 64 additions & 45 deletions backend/src/services/spec/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { v4 as uuidv4 } from "uuid"
import { Not } from "typeorm"
import SwaggerParser from "@apidevtools/swagger-parser"
import yaml from "js-yaml"
import OpenAPIRequestValidator, {
OpenAPIRequestValidatorError,
} from "openapi-request-validator"
import OpenAPIResponseValidator, {
OpenAPIResponseValidatorError,
OpenAPIResponseValidatorValidationError,
} from "openapi-response-validator"
} from "@leoscope/openapi-response-validator"
import { AlertType, RestMethod, SpecExtension } from "@common/enums"
import { ApiEndpoint, ApiTrace, DataField, OpenApiSpec, Alert } from "models"
import Error400BadRequest from "errors/error-400-bad-request"
Expand All @@ -25,10 +26,12 @@ import {
generateAlertMessageFromReqErrors,
generateAlertMessageFromRespErrors,
getHostsFromServer,
getOpenAPISpecVersion,
getSpecRequestParameters,
getSpecResponses,
parsePathParameter,
SpecValue,
AjvError,
validateSpecSchema,
} from "./utils"
import { AlertService } from "services/alert"
Expand All @@ -55,8 +58,14 @@ export class SpecService {
extension: SpecExtension,
specString: string,
): Promise<void> {
const specVersion = getOpenAPISpecVersion(specObject)
if (!specVersion) {
throw new Error422UnprocessableEntity(
"Invalid OpenAPI Spec: No 'swagger' or 'openapi' field defined.",
)
}
const validationErrors = validateSpecSchema(specObject)
if (validationErrors.length > 0) {
if (validationErrors?.length > 0) {
throw new Error422UnprocessableEntity("Invalid OpenAPI Spec", {
message: "Invalid OpenAPI Spec",
errors: validationErrors,
Expand Down Expand Up @@ -88,7 +97,10 @@ export class SpecService {
const endpoint = specEndpoints[i]
endpoint.openapiSpecName = null
}
await DatabaseService.executeTransactions([[...specEndpoints]], [[openApiSpec]])
await DatabaseService.executeTransactions(
[[...specEndpoints]],
[[openApiSpec]],
)
}

static async uploadNewSpec(
Expand All @@ -97,8 +109,14 @@ export class SpecService {
extension: SpecExtension,
specString: string,
): Promise<void> {
const validationErrors = validateSpecSchema(specObject)
if (validationErrors.length > 0) {
const specVersion = getOpenAPISpecVersion(specObject)
if (!specVersion) {
throw new Error422UnprocessableEntity(
"Invalid OpenAPI Spec: No 'swagger' or 'openapi' field defined.",
)
}
const validationErrors = validateSpecSchema(specObject, specVersion)
if (validationErrors?.length > 0) {
throw new Error422UnprocessableEntity("Invalid OpenAPI Spec", {
message: "Invalid OpenAPI Spec",
errors: validationErrors,
Expand Down Expand Up @@ -209,35 +227,37 @@ export class SpecService {
riskScore: "ASC",
},
})
similarEndpoints.forEach(async endpoint => {
apiEndpoint.totalCalls += endpoint.totalCalls
apiEndpoint.riskScore = endpoint.riskScore
const traces = await apiTraceRepository.findBy({
apiEndpointUuid: endpoint.uuid,
})
endpoint.dataFields.forEach(dataField => {
dataField.apiEndpointUuid = apiEndpoint.uuid
})
traces.forEach(trace => {
trace.apiEndpointUuid = apiEndpoint.uuid
})
endpoint.alerts.forEach(alert => {
switch (alert.type) {
case AlertType.NEW_ENDPOINT:
case AlertType.OPEN_API_SPEC_DIFF:
endpoints.alertsToRemove.push(alert)
break
case AlertType.PII_DATA_DETECTED:
case AlertType.UNDOCUMENTED_ENDPOINT:
default:
alert.apiEndpointUuid = apiEndpoint.uuid
endpoints.alertsToKeep.push(alert)
}
})
endpoints.traces.push(...traces)
endpoints.dataFields.push(...endpoint.dataFields)
})
endpoints.similarEndpoints.push(...similarEndpoints)
if (similarEndpoints) {
for (const endpoint of similarEndpoints) {
apiEndpoint.totalCalls += endpoint.totalCalls
apiEndpoint.riskScore = endpoint.riskScore
const traces = await apiTraceRepository.findBy({
apiEndpointUuid: endpoint.uuid,
})
endpoint.dataFields.forEach(dataField => {
dataField.apiEndpointUuid = apiEndpoint.uuid
})
traces.forEach(trace => {
trace.apiEndpointUuid = apiEndpoint.uuid
})
endpoint.alerts.forEach(alert => {
switch (alert.type) {
case AlertType.NEW_ENDPOINT:
case AlertType.OPEN_API_SPEC_DIFF:
endpoints.alertsToRemove.push(alert)
break
case AlertType.PII_DATA_DETECTED:
case AlertType.UNDOCUMENTED_ENDPOINT:
default:
alert.apiEndpointUuid = apiEndpoint.uuid
endpoints.alertsToKeep.push(alert)
}
})
endpoints.traces.push(...traces)
endpoints.dataFields.push(...endpoint.dataFields)
}
endpoints.similarEndpoints.push(...similarEndpoints)
}
}
}
}
Expand Down Expand Up @@ -269,8 +289,9 @@ export class SpecService {
return []
}
const specObject: JSONValue = yaml.load(openApiSpec.spec) as JSONValue
const specPath: JSONValue =
specObject["paths"][endpoint.path][endpoint.method.toLowerCase()]
const parsedSpec = await SwaggerParser.dereference(specObject as any)
const specPath =
parsedSpec.paths?.[endpoint.path][endpoint.method.toLowerCase()]

// Validate request info
const specRequestParameters = getSpecRequestParameters(
Expand All @@ -291,7 +312,7 @@ export class SpecService {
const requestValidator = new OpenAPIRequestValidator({
parameters: specRequestParameters?.value,
requestBody: specRequestBody?.value,
schemas: specObject?.["components"]?.["schemas"] ?? {},
schemas: parsedSpec?.["components"]?.["schemas"] ?? {},
errorTransformer: (error, ajvError) => {
if (ajvError.params["additionalProperty"]) {
return { ...error, path: ajvError.params["additionalProperty"] }
Expand Down Expand Up @@ -338,24 +359,21 @@ export class SpecService {
)

// Validate response info
const responses = getSpecResponses(specObject, endpoint)
const responses = getSpecResponses(parsedSpec, endpoint)
const responseValidator = new OpenAPIResponseValidator({
components: specObject["components"],
responses: responses?.value,
errorTransformer: (error, ajvError) => {
if (ajvError.params) {
const keys = Object.keys(ajvError.params)
return { ...error, path: ajvError.params[keys[0]] }
}
return error
return ajvError
},
})
const traceStatusCode = trace.responseStatus
const traceResponseBody = parsedJsonNonNull(trace.responseBody, true)
const responseErrors: OpenAPIResponseValidatorValidationError =
const responseValidationItems: OpenAPIResponseValidatorValidationError =
responseValidator.validateResponse(traceStatusCode, traceResponseBody)
const responseErrors = responseValidationItems.errors
const respErrorItems = generateAlertMessageFromRespErrors(
responseErrors?.errors as OpenAPIResponseValidatorError[],
responseErrors as AjvError[],
responses?.path,
)

Expand All @@ -369,6 +387,7 @@ export class SpecService {
)
} catch (err) {
console.error(`Error finding OpenAPI Spec diff: ${err}`)
return []
}
}
}
Loading

0 comments on commit 5738db8

Please sign in to comment.