diff --git a/CHANGELOG.md b/CHANGELOG.md index eccfe44..582d756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [20210107](https://hub.docker.com/r/sevenvaltechnologies/flatrunner/tags) + +### Added + +- The [`uuid3()` and `uuid4()` functions](/reference/functions/uuid.md) +- The [`ldap-query()` function](/reference/functions/ldap-query.md) +- [LDAP TLS configuration](/reference/configuration.md#ldap-tls-configuration) and [LDAP timeout](/reference/configuration.md#ldap-timeout) +- The [`scope-claim`](/reference/OpenAPI/security.md#the-x-flat-jwt-field) and [`post-check-flow`](/reference/OpenAPI/security.md#the-x-flat-jwt-field) properties +- Specifying the [required token scope](/reference/OpenAPI/security.md#applying-security-schemes) +- [Merging directives into `php.ini`](/administration/configuration.md#php-ini) via environment variables + +### Fixed + +- [Path parameters](/reference/OpenAPI/routing.md#path-parameters) were not usable in [error flows](/reference/OpenAPI/routing.md#error-flow) + + ## [20200828](https://hub.docker.com/r/sevenvaltechnologies/flatrunner/tags) ### Added diff --git a/administration/configuration.md b/administration/configuration.md index 49245af..2336c25 100644 --- a/administration/configuration.md +++ b/administration/configuration.md @@ -13,6 +13,7 @@ The chapter [Defining Env Vars](/cookbook/envvars.md#defining-env-vars) in the C * `FLAT_STATUS_AUTH`: Username and password, separated by a `:` for access to the `php-fpm` and `httpd` status pages. The pages are disabled entirely if `FLAT_STATUS_AUTH` is not set. If enabled, the `httpd` status can be requested via HTTP at `/ServerStatus`, and the php-fpm status at `/FPMStatus?full`. * `FLAT_DEBUG_ALLOW_HEADER`: enable [per request debugging](/reference/debugging.md#request-debugging), defaults to `false` unless `FLAT_DEBUG_AUTH` is set. * `FLAT_DEBUG_AUTH`: sets a password to protect [per request debugging](/reference/debugging.md#request-debugging). +* `FIT_FLAT_UID_FORMAT`: use `uuid3` or `uuid4` to switch the format of the `requestID` generated in the logs and the `id` in the DC to UUID version 3 or 4 respectively instead of the Apache httpd unique_id used by default. ### Request Timeouts @@ -26,6 +27,10 @@ Use the following environment variables to configure the timeouts used during re * `FLAT_MAX_TIMEOUT`: Maximum allowed time in seconds for outgoing HTTP requests. * `FLAT_MAX_TIMEOUT_PROCESSES`: Specifies how many PHP-FPM processes are allowed to wait for slow sources at any one time. The lower the threshold value, the earlier slow sources will be blocked. +### php.ini + +Arbitrary directives can be merged into `php.ini` by setting environment variables starting with `PHP_INI_`. For example, to set `post_max_size = 100M`, just export `PHP_INI_post_max_size=50M` in your shell before starting `flat` CLI. Refer to the [PHP documentation](https://www.php.net/manual/en/ini.list.php) for a list of directives. + ### PHP-FPM Some parameters used for PHP-FPM process management can be adjusted using environment variables. Refer to the [PHP-FPM documentation](https://www.php.net/manual/en/install.fpm.configuration.php) for more information. diff --git a/cookbook/README.md b/cookbook/README.md index 86d55de..3e94a1b 100644 --- a/cookbook/README.md +++ b/cookbook/README.md @@ -2,6 +2,8 @@ ## [Protecting Access using JWT Tokens](x-flat-jwt.md) +## [Performing Additional Checks on JWT Access Tokens](checking-jwt-tokens.md) + ## [Proxying Requests to Upstream APIs (Swagger)](proxy-requests.md) ## [Testing Templates](test-templates.md) diff --git a/cookbook/checking-jwt-tokens.md b/cookbook/checking-jwt-tokens.md new file mode 100644 index 0000000..43749f7 --- /dev/null +++ b/cookbook/checking-jwt-tokens.md @@ -0,0 +1,264 @@ +# Performing Additional Checks on JWT Access Tokens + +Before you start, make sure that you have read the cookbook chapter [Protecting Access using JWT Tokens](x-flat-jwt.md). + +Let's start with the following swagger definition: + +swagger.yaml: + +```yaml +swagger: "2.0" +host: my-api.com +schemes: + - https +basePath: / +securityDefinitions: + JWTCookie: + type: apiKey + in: header + name: Cookie + x-flat-cookiename: authtoken + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + out-var: $jwt +security: + - JWTCookie: [] +paths: + /projects/{p}: + x-flat-flow: … + get: + parameters: + - name: p + in: path + description: The project identifier + type: string + required: true + patch: + parameters: + - name: p + in: path + description: The project identifier + type: string + required: true +``` + +The API has one endpoint with a path parameter `p` indicating the project identifier and two operations (`GET` and `PATCH`). The whole API is secured with a security scheme labelled "JWTCookie". + +FLAT will make sure that every request to this endpoint +* has a `Cookie` header +* with a value for the `authtoken` cookie +* that is a JWT +* properly signed and +* not expired. + +In addition to this, FLAT provides features for further checks: + +* [Claims](#checking-claims) +* [Scopes](#checking-scopes) +* [Post-check flow](#the-post-check-flow) + +## Checking Claims + +For example, you can ensure that the token was issued by a specific token provider (`iss` claim) + +```yaml +… + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + out-var: $jwt + claims: + iss: "https://trustworthy-token-provider.com" # ⬅ the mandatory value for the iss claim +… +``` + +and that your API is (one of) the intended audience(s) for the token (`aud` claim) + +```yaml +… + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + out-var: $jwt + claims: + iss: "https://trustworthy-token-provider.com" + aud: "https://my-api.com/" # ⬅ a mandatory value for the aud claim +… +``` + +A JWT with the following claims will pass the test: + +```json +{ + "iss": "https://trustworthy-token-provider.com", + "aud": [ "https://my-api.com/", "https://a-different-api.org/" ], + … +} +``` + +while + +```json +{ + "iss": "https://the-reckless-token-provider.com", + "aud": [ "https://my-api.com/", "https://a-different-api.org/" ], + … +} +``` +or +```json +{ + "iss": "https://trustworthy-token-provider.com", + "aud": [ "https://a-different-api.org/" ], + … +} +``` +will **not** pass. + +## Checking Scopes + +Let's restrict the use of the `PATCH` operation to specially authorized requests. We can use scopes to achieve this: + +```yaml +… + patch: + security: + - JWTCookie: [ write ] + parameters: +… +``` + +FLAT will now look for a scope claim (default claim name is `scope`) with a value of `write`. If the `write` scope is present (possibly along with further scopes, like in `"scope": "read write create"`), the request passes, otherwise it is rejected. + +BTW, you can specify another claim name for scopes using the `scope-claim` property of `x-flat-jwt`: + +```yaml +… + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + scope-claim: sc # ⬅ look for scopes in the sc JWT claim +… +``` + +## The post-check flow + +Finally, we want to check that a certain non-standard JWT claim `pid` matches the path param `p` (the project identifier). + +We use the post-check-flow feature: + +```yaml +… + x-flat-jwt: + key: + file: pubkey.pem + … + post-check-flow: check-jwt.xml +… +``` +with check-jwt.xml: +```xml + + + + { + "token_id": {{ $jwt/pid }}, + "path_id": {{ $request/params/p }} + } + + + { + "status": 401, + "message": "Token is not applicable for this project." + } + + +``` + +A JWT with the claim + +```json +{ + … + "pid": "ABC123", + … +} +``` +will permit access to `https://my-api.com/projects/ABC123`, but **not** to `https://my-api.com/projects/DEF456`. + + +## All files together + +swagger.yaml: +```yaml +swagger: "2.0" +host: my-api.com +schemes: + - https +basePath: / +securityDefinitions: + JWTCookie: + type: apiKey + in: header + name: Cookie + x-flat-cookiename: authtoken + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + claims: + iss: "https://trustworthy-token-provider.com" + scope-claim: sc # default: scope + out-var: $the_claims + out-header: JWT + post-check-flow: check-jwt.xml +security: + - JWTCookie: [] +paths: + /projects/{p}: + x-flat-flow: ... + get: + parameters: + - name: p + in: path + description: The project identifier + type: string + required: true + patch: + security: + - JWTCookie: [ write ] + parameters: + - name: p + in: path + description: The project identifier + type: string + required: true +``` + +check-jwt.xml: +```xml + + + + { + "token_id": {{ $jwt/pid }}, + "path_id": {{ $request/params/p }} + } + + + { + "status": 401, + "message": "Token is not applicable for this project." + } + + +``` + +## See also + +* [FLAT Security](/reference/OpenAPI/security.md) (reference) diff --git a/cookbook/post-check-flow.md b/cookbook/post-check-flow.md new file mode 100644 index 0000000..39ed0cc --- /dev/null +++ b/cookbook/post-check-flow.md @@ -0,0 +1,32 @@ +# Performing Additional Checks on JWT Access Tokens + +swagger.yaml: + +```yaml +swagger: "2.0" +basePath: / +securityDefinitions: + JWTCookie: + type: apiKey + in: header + name: Cookie + x-flat-cookiename: authtoken + x-flat-jwt: + key: + file: pubkey.pem + alg: RS256 + claims: + iss: "The token provider" + scope-claim: sc # default: scope + out-var: $jwt + post-check-flow: check-jwt.xml +paths: + /projects/{p}: + x-flat-flow: ... + get: + security: + - JWTCookie: [ read ] + patch: + security: + - JWTCookie: [ write ] +``` \ No newline at end of file diff --git a/cookbook/request-timeout.md b/cookbook/request-timeout.md index fef5472..f4cf2e5 100644 --- a/cookbook/request-timeout.md +++ b/cookbook/request-timeout.md @@ -16,3 +16,8 @@ In case we have to deal with a slow upstream system, the [various timeout option ``` Without that `timeout` option the request will be aborted after three seconds – FLAT's default timeout setting. + +## See also + +* [`request` action timeouts](/reference/actions/request.md#options) (Reference) +* [default timeouts](/administration/configuration.md#request-timeouts) (Administration) diff --git a/cookbook/x-flat-jwt.md b/cookbook/x-flat-jwt.md index 7bd4d5a..ffe16d3 100644 --- a/cookbook/x-flat-jwt.md +++ b/cookbook/x-flat-jwt.md @@ -347,6 +347,8 @@ init.xml: ``` +If you want to know, how to perform some additional checks on the JWT, visit the cookbook [Performing Additional Checks on JWT Access Tokens](checking-jwt-tokens.md). + ## See also * [FLAT Security](/reference/OpenAPI/security.md) (reference) diff --git a/reference/OpenAPI/security.md b/reference/OpenAPI/security.md index c3f8e1e..233d265 100644 --- a/reference/OpenAPI/security.md +++ b/reference/OpenAPI/security.md @@ -14,12 +14,17 @@ The `x-flat-jwt` field references an object with fields describing the expected * `out-var` - The name of the variable in which the JWT is stored (must be a proper variable name, starting with `$`; default: `"$jwt"`). * `out-header` - The name of an HTTP request header that shall carry the JWT in upstream requests. * `claims` - An object with claims the JWT payload is expected to contain. The field names are the claim names, the expected claim value is specified either with a value, or by referencing a file (`file`) or an environment variable (`env`). +* `scope-claim` - The name of the claim, storing the scope of the token (string; default: `scope`). The claim value must either be + * a string containing a whitespace-separated list of scopes (`"scope1 scope2 scope3"`) or + * an array with a string entry for each scope (`["scope1", "scope2", "scope3"]`). +* `post-check-flow` - The path to a flow executed after the JWT is checked and found valid. In this flow, e.g. additional checks can be implemented (use the [`error` action](/reference/actions/error.md) to throw errors). The token is considered valid if all of the following are true: * the JWS can be decoded, * the JWS has a proper JWT, * the JWT is not expired, * the JWT contains the expected claims, if any are configured, +* the scope of the token contains all required scopes as specified in the security requirement, * the JWT can be stored in a variable. `$jwt` or the alternative variable specified in `out-var` and the header specified in `out-header` will be unset if the token is not valid. @@ -40,6 +45,7 @@ securityDefinitions: alg: env: FLAT_JWT_ALG out-var: $header_token + scope-claim: permission claims: aud: env: FLAT_JWT_AUDIENCE @@ -49,6 +55,7 @@ The token is expected to be a bearer token in the `Authorization` header. The key is read from a file named `secret.pem` relative to the `swagger.yaml`. The signing algorithm is read from the `FLAT_JWT_ALG` environment variable. The JWT will be stored in the `$header_token` variable. +The scope of the token is specified in a `permission` claim. The JWT payload is expected to contain an `aud` claim with a value read from the `FLAT_JWT_AUDIENCE` environment variable. If the request does not contain an `Authorization` header with the proper bearer structure, or the token is invalid, this security scheme will fail. @@ -98,9 +105,13 @@ paths: In Swagger, security schemes can be specified at the top level (default security) or for specific operations. With FLAT, you can also specify a security scheme for a specific path (default security for all operations on the path). -In the following example, a `GET` request to `/foo` must satisfy the security scheme named `JWTHeaderAuth`, while e.g. a `POST` or `PUT` request to `/foo` must satisfy the security scheme named `JWTCookieAuth`. +In the following example, a `GET` request to `/foo` must satisfy the security scheme named `JWTHeaderAuth`, +a `POST` request to `/foo` must satisfy the security scheme named `JWTHeaderAuth` and the token must have both `write:foo` and `read:bar` scopes, +while e.g. a `DELETE` or `PUT` request to `/foo` must satisfy the security scheme named `JWTCookieAuth`. All other requests must satisfy either the `JWTHeaderAuth` **or** `JWTCookieAuth` security schemes. +**Note:** While in Swagger 2.0, specifying scopes in security requirements is limited to OAuth2 security definitions, FLAT has no such limitation. + ```yaml # default security: @@ -115,6 +126,10 @@ paths: # specific for GET on /foo security: - JWTHeaderAuth: [] + post: + # specific for POST on /foo + security: + - JWTHeaderAuth: [ write:foo, read:bar ] ``` If a request does not pass the security check, it is rejected with status code `403` and error code `3206`, and the [error flow](/reference/OpenAPI/routing.md#error-flow) is run, if configured. @@ -135,6 +150,7 @@ securityDefinitions: alg: env: FLAT_JWT_ALG out-var: $header_token + scope-claim: permission claims: aud: env: FLAT_JWT_AUDIENCE @@ -147,6 +163,16 @@ securityDefinitions: key: env: FLAT_COOKIE_SECRET out-var: $cookie_token + JWTCookieAuth2: + type: apiKey + in: header + name: Cookie + x-flat-cookiename: authtoken2 + x-flat-jwt: + key: + env: FLAT_COOKIE_SECRET + out-var: $cookie_token2 + post-check-flow: check-cookie-jwt.xml security: # alternatives: token in Authorization header or authtoken cookie - JWTHeaderAuth: [] - JWTCookieAuth: [] @@ -156,6 +182,38 @@ paths: get: security: # token must be in Authorization header - JWTHeaderAuth: [] + post: + # specific for POST on /foo + security: + - JWTHeaderAuth: [ write:foo, read:bar ] + /projects/{p}: + get: + security: + - JWTCookieAuth2: [] + parameters: + - name: p + in: path + type: string + required: true +``` + +check-cookie-jwt.xml (checking whether the value of the `pid` JWT claim equals the `p` request path parameter): + +```xml + + + { + "token_id": {{ $cookie_token2/pid }}, + "path_id": {{ $request/params/p }} + } + + + { + "status": 401, + "message": "Token is not applicable for this project." + } + + ``` ## Forwarding JWT Upstream diff --git a/reference/actions/log.md b/reference/actions/log.md index 8d558dd..f62fbed 100644 --- a/reference/actions/log.md +++ b/reference/actions/log.md @@ -22,6 +22,7 @@ All name/value pairs of the object are registered for logging. When the system [System log fields](/administration/logging.md#fields) like `timestamp` cannot be overriden. You can call the action multiple times. Fields of the same name are overwritten. However, nested fields are merged into the previously registered log fields. +The order of fields is maintained. ```xml @@ -50,6 +51,12 @@ The merged custom log fields are: } ``` +They would appear in the [access log](/administration/logging.md#access-log) like this: + +```json +{"timestamp": …,"user":{"name":"alice","role":"admin"}} +``` + Fields with `null` values are not included in the log event. In order to remove a previously registered field, you can unset it with `null` value. The `user` field of the previous example can be unregistered like this: diff --git a/reference/actions/request.md b/reference/actions/request.md index d710ce3..8880ca9 100644 --- a/reference/actions/request.md +++ b/reference/actions/request.md @@ -289,9 +289,9 @@ Example: `uploads` with `src` or `value` The `options` property sets the request options. Its value must be a JSON object. The following options are valid: -* `timeout` - Maximum time in seconds for processing a request (type: `number`, default: `$FLAT_FETCH_DEFAULT_TIMEOUT`) -* `connect-timeout` - Maximum time in seconds for establishing a connection to a server (type: `number`, default: `$FLAT_FETCH_DEFAULT_CONNECT_TIMEOUT)`) -* `time-to-first-byte-timeout` - Maximum time in seconds for receiving the first byte (type: `integer`, default: `$FLAT_DEFAULT_TTFB_TIMEOUT`) +* `timeout` - Maximum time in seconds for processing a request (type: `number`, default: [`$FLAT_FETCH_DEFAULT_TIMEOUT`](/administration/configuration.md#request-timeouts)) +* `connect-timeout` - Maximum time in seconds for establishing a connection to a server (type: `number`, default: [`$FLAT_FETCH_DEFAULT_CONNECT_TIMEOUT`](/administration/configuration.md#request-timeouts)) +* `time-to-first-byte-timeout` - Maximum time in seconds for receiving the first byte (type: `integer`, default: [`$FLAT_DEFAULT_TTFB_TIMEOUT`](/administration/configuration.md#request-timeouts)) * `send-x-forwarded-for` - Whether to include the client's IP address in an `X-Forwarded-For` header (type: `boolean`, default `false`) * `set-response-headers` - The HTTP response header fields to set in the response (type: `object` with field name as key and `string` or array of `string` as value) * `tls-version` - The TLS version (valid values: `"auto"`, `"TLSv1"`, `"TLSv1.0"`, `"TLSv1.1"`, `"TLSv1.2"`) diff --git a/reference/configuration.md b/reference/configuration.md index be97fff..abbe271 100644 --- a/reference/configuration.md +++ b/reference/configuration.md @@ -31,3 +31,33 @@ The difference is, that you may use _Dynamic Attribute Values_ and if-clauses as ``` + +## LDAP TLS Configuration + +If you use the [`ldap-lookup()`](/reference/functions/ldap-lookup.md) or +[`ldap-query()`](/reference/functions/ldap-query.md) function and connect to the LDAP server via TLS +(`ldaps://...` URL), you may have to provide the corresponding CA certificate using the following config setting in your config file: + +```xml + + + + + +``` + +The path is resolved relative to the config.xml file. + +## LDAP Timeout + +LDAP requests via [`ldap-lookup()`](/reference/functions/ldap-lookup.md) or +[`ldap-query()`](/reference/functions/ldap-query.md) use `FLAT_MAX_TIMEOUT` as the default timeout. +If you want to set a lower timeout for LDAP requests, use the setting below in your config file: + +```xml + + + + + +``` diff --git a/reference/functions/README.md b/reference/functions/README.md index 3d4c572..55aab62 100644 --- a/reference/functions/README.md +++ b/reference/functions/README.md @@ -86,6 +86,7 @@ * [`html-parse()`](html-parse.md) * [`id()` ↗](https://developer.mozilla.org/en/XPath/Functions/id) * [`ldap-lookup()`](ldap-lookup.md) +* [`ldap-query()`](ldap-query.md) * [`lang()` ↗](https://developer.mozilla.org/en/XPath/Functions/lang) * [`last()` ↗](https://developer.mozilla.org/en/XPath/Functions/last) * [`local-name()` ↗](https://developer.mozilla.org/en/XPath/Functions/local-name) @@ -93,4 +94,5 @@ * [`name()` ↗](https://developer.mozilla.org/en/XPath/Functions/name) * [`namespace-uri()` ↗](https://developer.mozilla.org/en/XPath/Functions/namespace-uri) * [`position()` ↗](https://developer.mozilla.org/en/XPath/Functions/position) +* [`uuid3()` and `uuid4()`](uuid.md) * [`xml-parse()`](xml-parse.md) diff --git a/reference/functions/ldap-query.md b/reference/functions/ldap-query.md new file mode 100644 index 0000000..c2b886e --- /dev/null +++ b/reference/functions/ldap-query.md @@ -0,0 +1,60 @@ +# `ldap-query()` + +``` +OXN-node-set ldap-query(string url, string rdn, string rdnPassword, string base_dn, string search, string attributes) +``` + +The `ldap-query()` function connects to an LDAP server with the given `url`, `rdn` and `rdnPassword`. +It then performs a query by the given `search`. +An [OXN](/reference/templating/oxn.md) JSON array is returned with objects containing `dn` and additional attributes given by `attributes` of all the entities that were found. +If no entities match the query, an empty node-set is returned. + +## Parameters + +* `url` The ldap URL (string) +* `rdn` The (relative) distinguished name of the connecting user (string) +* `rdnPassword` The password of the connecting user (string) +* `base_dn` The base distinguished name for the directory, used for the search (string) +* `search` The filter for searching entities (string) +* `attributes` A comma-separated list of attributes to return (string) + + +## Example + +In the following example, FLAT connects to the LDAP server with the DN given in the `rdn` and `rdnPassword` POST parameters. +The given filter is used to search for an entry of a person which is a member of a group `Users` and sAMAccountName containing `doe`. +In addition to the (default) `dn`, the `sAMAccountName`, `displayName` and `mail` from the found entries are included in the results. + +```xml + + concat("(&(objectClass=person)(memberOf=CN=Users,ou=People,dc=example,dc=com)(sAMAccountName=*doe*))") + "sAMAccountName,displayName,mail" + "ldap://ad.example.com" + + ldap-lookup($ldap_url, $request/post/rdn, $request/post/rdnPassword, "dc=example,dc=com", $search, $attributes) + + { + "status": 403, + "message": "ldap-lookup() failed" + } + + +``` + +The result is +```json +[ + { + "dn": "cn=John Doe,ou=People,dc=example,dc=com", + "sAMAccountName": "john.doe", + "displayName": "John Doe", + "mail": "john.doe@example.com" + }, + { + "dn": "cn=Joseph Adoell,ou=People,dc=example,dc=com", + "sAMAccountName": "joseph.adoell", + "displayName": "Joseph Adoell", + "mail": "joseph.adoell@example.com" + } +] +``` diff --git a/reference/functions/uuid.md b/reference/functions/uuid.md new file mode 100644 index 0000000..44c6056 --- /dev/null +++ b/reference/functions/uuid.md @@ -0,0 +1,18 @@ +# `uuid3()` and `uuid4()` + +``` +string uuid3(string namespace, string name) +string uuid4() +``` + +The functions `uuid3()` and `uuid4()` generate a [UUID](https://tools.ietf.org/html/rfc4122) version 3 or 4, respectively. + +## Examples + +* `uuid4()` returns `da7e73a7-8dca-4123-b671-7997d7fd2fb8` or a similar, random UUID +* `uuid3('6ba7b810-9dad-11d1-80b4-00c04fd430c8', 'example.com')` returns `9073926b-929f-31c2-abc9-fad77ae3e8eb`. +* `uuid3("foobar")` is not really a call that conforms to the standard, since no namespace (a UUID) was included, but returns `3858f622-30ac-3c91-9f30-0c664312c63f` anyway. + +## See also + +* [`FIT_FLAT_UID_FORMAT`](/administration/configuration.md#miscellaneous) diff --git a/tutorial/README.md b/tutorial/README.md index a8cb641..8c1a522 100644 --- a/tutorial/README.md +++ b/tutorial/README.md @@ -33,6 +33,11 @@ $ sudo mv flat /usr/local/bin > You could also put `flat` into your `~/bin/` directory. If that is not > already in your `$PATH`, you can add it with `export PATH="$PATH:~/bin"`. +If you do not have `bash` installed on your system, you can use `docker-compose` as an alternative to the flat-cli. +In addition to all the configuration files used in this tutorial, the repository +[FLAT tutorial files](https://github.com/sevenval/flat-tutorial-files) also provides `docker-compose.yml` files to +get you started. Refer to the (accompanying documentation)[https://github.com/sevenval/flat-tutorial-files/blob/main/README.md] for more information. + ## Getting Started Let's create a workspace for our little project. We call it `hello-world` and create a directory with that name: @@ -367,7 +372,7 @@ $ curl --silent localhost:8080/..%2fswagger.yaml%23 | jq "message": "Client request validation failed", … "info": [ - "Pattern constraint violated in path for language: '../swagger.yaml#' does not match the pattern '^[a-zA-Z0-9]+$.'" + "Pattern constraint violated in path for language: '../swagger.yaml#' does not match the pattern '^[a-zA-Z0-9]+$'." ] } } @@ -639,14 +644,14 @@ x-flat-validate: If we now alter the domain name in the second template of our flow to `flaw.githubusercontent.com`, we get a validation error: ```bash -$ curl --silent localhost:8080/javascript | jq +$ curl --silent localhost:8080/json | jq { "error": { - "message": "Output Validation Failed", + "message": "Client response validation failed", "status": 500, "requestID": "XNrzofUrpUX@vMuN6J31EwAAADQ", "info": [ - "Pattern constraint violated in body for code: Does not match the regex pattern ^https://raw.githubusercontent.com/." + "Pattern constraint violated in body for url: 'https://flaw.githubusercontent.com/leachim6/hello-world/a5df0ebf101fbb762604717ad10165ad7d4a5317/j/json.json' does not match the pattern '^https://raw.githubusercontent.com/'." ] } } @@ -815,19 +820,21 @@ To abort the flow in case the upstream request or response is invalid or the req ``` -To see the effect, change the parameter name `language` to `lang` in upstream_request.xml: +To see the effect, shorten the `hello-world` of the repository name to just `hello` in `upstream_request.xml`: ```xml + … + [ + "q=hello", + "repo:leachim6/hello", + … - {{ concat("lang:", $request/params/language) }} - - ] - ``` If we request our API -``` -curl -si localhost:8080/html + +```bash +$ curl -si localhost:8080/html ``` instead of the output @@ -835,19 +842,15 @@ instead of the output ``` HTTP/1.1 404 Not Found … -``` -``` {"error": "Unknown language"} ``` we now get ``` -HTTP/1.1 400 Bad Request +HTTP/1.1 502 Bad Gateway … -``` -```json -{"error":{"message":"Upstream Request Validation Failed","status":400,"requestID":"main","info":["Pattern constraint violated in query for q: 'hello repo:leachim6\/hello-world filename:html lang:html' does not match the pattern '^hello repo:leachim6\/hello-world filename:\\w+ language:\\w+$'."],"code":3202}} +{"error":{"message":"Upstream Response Validation Failed","status":502,"requestID":"main","info":["No definition for status code 422 and no 'default'.","Upstream status: 422 Unprocessable Entity"],"code":3203}} ``` If you prefer to provide a custom error document, you can configure an error flow. Just create `error.xml`: @@ -883,23 +886,22 @@ x-flat-error: # ⬅ We now get ``` -HTTP/1.1 400 Bad Request +HTTP/1.1 502 Bad Gateway … Error-Code: 3202 … -``` -```json -{"CustomError":{"Message":"Upstream Request Validation Failed","Info":["Pattern constraint violated in query for q: 'hello repo:leachim6\/hello-world filename:html lang:html' does not match the pattern '^hello repo:leachim6\/hello-world filename:\\w+ language:\\w+$'."]}} +{"CustomError":{"Message":"Upstream Response Validation Failed","Info":["No definition for status code 422 and no 'default'.","Upstream status: 422 Unprocessable Entity"]}} ``` -Now revert the change to upstream_request.xml: +Now revert the change to `upstream_request.xml`: ```xml + … + [ + "q=hello", + "repo:leachim6/hello-world", + … - {{ concat("language:", $request/params/language) }} - - ] - ``` @@ -1064,134 +1066,6 @@ Besides the built-in debug information, custom debug output can help understand … ``` -## Complete Configuration - -Here are the complete configuration files: - -### `hello.xml` - -```xml - - - - - - - - - - - - - {"error": "Unknown language"} - - - - -``` - -### `upstream_request.xml` - -```xml - - - - - { - "url": "https://api.github.com/search/code", - "query": {{ join(" ", $query_parameters) }}, - "options": { - "definition": "upstream.yaml", - "validate-request": true, - "validate-response": true, - "exit-on-error": true - } - } - - -``` - -### `swagger.yaml` +## Configuration Files -```yaml -swagger: "2.0" -info: - version: "1.0" - title: Hello World! -x-flat-validate: - request: true - response: true -x-flat-error: - flow: error.xml -paths: - /{language}: - get: - x-flat-flow: hello.xml - parameters: - - name: language - in: path - required: true - type: string - pattern: ^[a-zA-Z0-9]+$ - produces: - - application/json - responses: - 200: - description: URL to a "Hello World" snippet - schema: - type: object - required: - - url - properties: - url: - type: string - pattern: ^https://raw.githubusercontent.com/ -``` - -### `upstream.yaml` - -```yaml -swagger: "2.0" -info: - version: "1.0" - title: GitHub Search API -host: api.github.com -schemes: - - https -paths: - /search/code: - get: - parameters: - - name: q - in: query - required: true - type: string - pattern: ^hello repo:leachim6/hello-world filename:\w+ language:\w+$ - produces: - - application/json - responses: - 200: - description: GitHub code search results - schema: - type: object - required: - - total_count - - items - properties: - total_count: - type: integer - items: - type: array -``` +The complete configuration files can be found in the companion repository [FLAT tutorial files](https://github.com/sevenval/flat-tutorial-files).