Add route for errors with secrets and PII (#354)
* Add route for errors with secrets and PII Relates to Azure/azure-sdk#3959 * Resolve feedback * Run prettier * Resolve PR feedback * Bump patch version * Add POST endpoint Resolves feedback for more routes. * Resolve PR feedback
This commit is contained in:
Родитель
3e868f255b
Коммит
fb790237dc
|
@ -1,9 +1,10 @@
|
|||
# Writing mock apis
|
||||
|
||||
1. First step is to create a new file typescript file in the [src/test-routes](../src/test-routes) folder:
|
||||
1. All the needed imports are from the `api` folder
|
||||
1. Define the category for your apis using `app.category("vanilla" | "azure" | "optional", () => {})`
|
||||
1. Start writing mock apis inside the `category` callback
|
||||
1. First step is to create a new file typescript file in the [src/test-routes](../src/test-routes) folder.
|
||||
1. All the needed imports are from the `api` folder.
|
||||
1. Define the category for your apis using `app.category("vanilla" | "azure" | "optional", () => {})`.
|
||||
1. Start writing mock apis inside the `category` callback as shown below.
|
||||
1. Add a swagger file e.g., using the same file name, to the [swagger](../swagger) folder.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -33,6 +34,77 @@ app.category("vanilla", () => {
|
|||
});
|
||||
```
|
||||
|
||||
### Example swagger
|
||||
|
||||
```json
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Test Client",
|
||||
"description": "Client for an example test service.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"host": "localhost:3000",
|
||||
"schemes": ["http"],
|
||||
"produces": ["application/json"],
|
||||
"paths": {
|
||||
"/test": {
|
||||
"get": {
|
||||
"operationId": "GetMyTest",
|
||||
"description": "Gets a test object.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A test object containing 'foo' and 'bar'.",
|
||||
"schema": { "$ref": "#/definitions/MyTest" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"operationId": "PostMyTest",
|
||||
"description": "Creates a test object..",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": "A test object containing 'foo' and 'bar'.",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": { "$ref": "#/definitions/MyTest" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A response indicating success.",
|
||||
"schema": { "$ref": "#/definitions/PostMyTestResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"MyTest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["foo", "bar"]
|
||||
},
|
||||
"PostMyTestResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"succeeded": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How to build response
|
||||
|
||||
Return the reponse object. [See type](../src/api/mock-response.ts)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@microsoft.azure/autorest.testserver",
|
||||
"version": "3.3.17",
|
||||
"version": "3.3.18",
|
||||
"description": "Autorest test server.",
|
||||
"main": "dist/cli/cli.js",
|
||||
"bin": {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
validateBodyNotEmpty,
|
||||
validateXMLBodyEquals,
|
||||
validateHeader,
|
||||
validateQueryParam,
|
||||
} from "./request-validations";
|
||||
|
||||
/**
|
||||
|
@ -68,6 +69,15 @@ export class RequestExpectation {
|
|||
validateHeader(this.originalRequest, headerName, expectedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect the query string of the request contains the expected name/value pair.
|
||||
* @param paramName Name of the query parameter.
|
||||
* @param expectedValue Value expected of the query parameter.
|
||||
*/
|
||||
public containsQueryParam(paramName: string, expectedValue: string): void {
|
||||
validateQueryParam(this.originalRequest, paramName, expectedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two requests are equal
|
||||
* @param actual Actual value
|
||||
|
|
|
@ -95,3 +95,13 @@ export const validateHeader = (request: RequestExt, headerName: string, expected
|
|||
throw new ValidationError(`Expected ${expected} but got ${actual}`, expected, actual);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the query string contains the given parameter name and value.
|
||||
*/
|
||||
export const validateQueryParam = (request: RequestExt, paramName: string, expected: string): void => {
|
||||
const actual = request.query[paramName];
|
||||
if (actual !== expected) {
|
||||
throw new ValidationError(`Expected query param ${paramName}=${expected} but got ${actual}`, expected, actual);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { app, json } from "../api";
|
||||
|
||||
app.category("vanilla", () => {
|
||||
// Returns an error response with secrets and PII in the headers and body.
|
||||
app.get("/secrets/error", "ErrorWithSecrets", (req) => {
|
||||
return {
|
||||
status: 403,
|
||||
headers: {
|
||||
// Following headers should be redacted.
|
||||
"x-ms-pii": "true",
|
||||
|
||||
// Following headers should not be redacted.
|
||||
"x-ms-request-id": "5e123516-834e-4222-9e80-353108d33357",
|
||||
"x-ms-version": "2022-02-01",
|
||||
},
|
||||
body: json({
|
||||
error: {
|
||||
code: "Unauthorized",
|
||||
message: "The user 'user@contoso.com' is unauthorized.",
|
||||
details: [
|
||||
{
|
||||
code: "UnauthorizedSharedKey",
|
||||
innererror: "Shared key 1c88a67921784300a462b2cb61da2339 is not permitted access.",
|
||||
},
|
||||
],
|
||||
token: "1c88a67921784300a462b2cb61da2339",
|
||||
},
|
||||
primaryKey: "1c88a67921784300a462b2cb61da2339",
|
||||
connectionString: "Key1=1c88a67921784300a462b2cb61da2339",
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/secrets/[:]create", "RequestWithSecrets", (req) => {
|
||||
req.expect.containsHeader("authorization", "SharedKey 1c88a67921784300a462b2cb61da2339");
|
||||
req.expect.containsQueryParam("key", "1c88a67921784300a462b2cb61da2339");
|
||||
req.expect.bodyEquals({ key: "1c88a67921784300a462b2cb61da2339" });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
// Following headers should be redacted.
|
||||
"x-ms-pii": "true",
|
||||
|
||||
// Following headers should not be redacted.
|
||||
"x-ms-request-id": "49997f20-3cee-4c0c-92ff-572acbbed13d",
|
||||
"x-ms-version": "2022-02-01",
|
||||
},
|
||||
body: json({
|
||||
key: "1c88a67921784300a462b2cb61da2339",
|
||||
value: "secret",
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
|
@ -0,0 +1,160 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Error with Secrets",
|
||||
"description": "Tests whether loggers/tracers redact secrets and PII within error responses.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"host": "localhost:3000",
|
||||
"schemes": ["http"],
|
||||
"produces": ["application/json"],
|
||||
"securityDefinitions": {
|
||||
"SharedKey": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/secrets/:create": {
|
||||
"post": {
|
||||
"operationId": "CreateSecret",
|
||||
"description": "Creates a secret.",
|
||||
"security": [
|
||||
{
|
||||
"SharedKey": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "This method will always return an error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SecretResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An error occurred.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/secrets/error": {
|
||||
"get": {
|
||||
"operationId": "GetErrorWithSecrets",
|
||||
"description": "Gets an error response containing secrets and PII.",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "This method will always return an error."
|
||||
},
|
||||
"default": {
|
||||
"description": "An error occurred.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"SecretResponse": {
|
||||
"type": "object",
|
||||
"description": "A secret.",
|
||||
"required": ["key", "value"],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The secret key."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The secret value."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"description": "Error response.",
|
||||
"additionalProperties": true,
|
||||
"required": ["error"],
|
||||
"properties": {
|
||||
"error": {
|
||||
"description": "The error object.",
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"description": "The error object.",
|
||||
"additionalProperties": true,
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "One of a server-defined set of error codes.",
|
||||
"$ref": "#/definitions/ErrorCode"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A human-readable representation of the error."
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "The target of the error."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"description": "An array of details about specific errors that led to this reported error.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"innererror": {
|
||||
"description": "An object containing more specific information than the current object about the error.",
|
||||
"$ref": "#/definitions/InnerError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InnerError": {
|
||||
"type": "object",
|
||||
"description": "An object containing more specific information about the error. As per Microsoft One API guidelines - https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses.",
|
||||
"additionalProperties": true,
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "One of a server-defined set of error codes.",
|
||||
"$ref": "#/definitions/InnerErrorCode"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Error message."
|
||||
},
|
||||
"innererror": {
|
||||
"description": "An object containing more specific information than the current object about the error.",
|
||||
"$ref": "#/definitions/InnerError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorCode": {
|
||||
"type": "string",
|
||||
"description": "Human-readable error code.",
|
||||
"x-ms-enum": {
|
||||
"name": "ErrorCode",
|
||||
"modelAsString": true
|
||||
},
|
||||
"enum": ["BadParameter", "Unauthorized"]
|
||||
},
|
||||
"InnerErrorCode": {
|
||||
"type": "string",
|
||||
"description": "Human-readable error code.",
|
||||
"x-ms-enum": {
|
||||
"name": "InnerErrorCode",
|
||||
"modelAsString": true
|
||||
},
|
||||
"enum": ["MissingSharedKey", "UnauthorizedSharedKey"]
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче