Skip to content

Commit

Permalink
Merge pull request #68 from sevenval/develop
Browse files Browse the repository at this point in the history
Release 20200318
  • Loading branch information
johakoch authored Mar 18, 2020
2 parents 36a848c + e36c0b3 commit a9b017c
Show file tree
Hide file tree
Showing 12 changed files with 344 additions and 10 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [20200318](https://hub.docker.com/r/sevenvaltechnologies/flatrunner/tags)

### Added

- [`body()` function](/reference/functions/body.md)
- [`pass-body` action](/reference/actions/pass-body.md)
- [Security checks with JWT](/reference/OpenAPI/security.md).

### Changed

- [`set-response-headers` action](/reference/actions/set-response-headers.md) now accepts the empty object `{}`
- Reading [`swagger.yaml`](/reference/OpenAPI/README.md) is faster because of caching


## [20200213](https://hub.docker.com/r/sevenvaltechnologies/flatrunner/tags)

### Added
Expand Down
17 changes: 16 additions & 1 deletion cookbook/forward-request-upstream.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Forwarding a Request to an Upstream API

To forward a request to an upstream API ("proxy" a request), you can simply use a
[`proxy-request` action](/reference/actions/proxy-request.md), for example:
[`proxy-request` action](/reference/actions/proxy-request.md), for example


```xml
<flow>
<proxy-request>
{
"url": "https://httpbin.org/anything"
}
</proxy-request>
</flow>
```

to delegate the incoming request to [httpbin.org](https://httpbin.org/#/Anything).

The following [flow](/reference/flow.md) shows a more advanced example:

```xml
<flow>
Expand Down
54 changes: 54 additions & 0 deletions cookbook/upstream-response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Processing Upstream Responses

Let's take a look at how responses from upstream systems provided by
[`request`](/reference/actions/request.md)/[`requests`](/reference/actions/requests.md)
or [`proxy-request`](/reference/actions/proxy-request.md) actions can be processed
further.

The [`body` function](/reference/functions/body.md) provides you with the raw body of
an HTTP response. For further processing, it may be necessary to parse the body using
functions like [`json-parse`](/reference/functions/json-parse.md) or
[`xml-parse`](/reference/functions/xml-parse.md):

```xml
<flow>
<requests>{
"xml": { "url": "…" } },
"json": { "url": "…" } }
}
</requests>

<eval out="$upstream-json">json-parse(body('json'))</eval>
<eval out="$upstream-xml">xml-parse(body('xml'))</eval>

<if test="$upstream-json/success">…</if>
</flow>
```

The [`$upstream` variable](/reference/variables.md) gives additional information about
upstream responses like the HTTP status and the headers. The following
recursive [sub-flow](/reference/actions/sub-flow.md) demonstrates a general way how to
follow a redirect:

request.xml:
```xml
<flow>
<request>{"id": "my-request", "url": {{ $url }}}</request>

<if test="$upstream/my-request/status = 302">
<eval out="$url">$upstream/my-request/headers/location</eval>
<sub-flow src="request.xml"/>
</if>
</flow>
```

To just pass the upstream response downstream you can use the
[`pass-body` action](/reference/actions/pass-body.md):

```xml
<flow>
<request>{"url": "http://example.com"}</request>
<pass-body/>
</flow>
```
2 changes: 2 additions & 0 deletions reference/OpenAPI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ FLAT uses [Swagger Extensions](https://swagger.io/docs/specification/2-0/swagger

## [Validation](validation.md)

## [Security Check](security.md)

## [Mocking](mocking.md)

## [CORS](cors.md)
Expand Down
2 changes: 2 additions & 0 deletions reference/OpenAPI/differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ First of all, several extensions named `x-flat-…` are recognized on different
* `x-flat-error`: [error flow](routing.md#error-flow) (top-level)
* `x-flat-cors`: [CORS configuration](cors.md) (top-level)
* `x-flat-validate`: [validation](validation.md) (top-level, below `paths/<path>` and `paths/<path>/<operation>`)
* `x-flat-jwt`: [expected JWT](security.md#the-x-flat-jwt-field) (in a [security scheme object](https://swagger.io/specification/v2/#securitySchemeObject))
* `x-flat-cookiename`: [JWT cookie name](security.md#jwt-in-cookie)

## Slimline Definition

Expand Down
151 changes: 151 additions & 0 deletions reference/OpenAPI/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Security

Security-related documentation in Swagger is described in [Security Definitions Object](https://swagger.io/specification/v2/#securityDefinitionsObject) and [Security Requirement Object](https://swagger.io/specification/v2/#securityRequirementObject)

Standard Swagger features in combination with some extensions can be used to configure security checks about JWT tokens which are expected to be sent with a client request.
If the JWT token is valid, it is stored in a [variable](/reference/variables.md), which can then be used in the [flow](/reference/flow.md).

## The `x-flat-jwt` field

The `x-flat-jwt` field references an object with fields describing the expected JWT token:

* `key` - REQUIRED. The key to decode the JSON Web Signature (JWS). This can either be specified with a value, or by referencing a file (`file`) or an environment variable (`env`).
* `alg` - The signing algorithm the JWS is expected to be created with. This can either be specified with a value, or by referencing a file (`file`) or an environment variable (`env`). See the [`algorithm` parameter for `jwt-decode()`](/reference/functions/jwt-decode.md) for more information.
* `out-var` - The name of the variable in which the JWT is stored (must be a proper variable name, starting with `$`; default: `"$jwt"`).
* `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`).

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 JWT can be stored in a variable.

## JWT in `Authorization` Header

Use a [Security Scheme Object](https://swagger.io/specification/v2/#securitySchemeObject) with `type: apiKey`, `in: header` and `name: Authorization` if the JWT is expected to be sent as a bearer token in an `Authorization` header; e.g.:

```yaml
securityDefinitions:
JWTHeaderAuth:
type: apiKey
in: header
name: Authorization
x-flat-jwt:
key:
file: secret.pem
alg:
env: FLAT_JWT_ALG
out-var: $header_token
claims:
aud:
env: FLAT_JWT_AUDIENCE
```
The code in this example defines a security scheme named `JWTHeaderAuth`.
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 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.


## JWT in Cookie

Use a [Security Scheme Object](https://swagger.io/specification/v2/#securitySchemeObject) with `type: apiKey`, `in: header`, `name: Cookie` and `x-flat-cookiename: <name>` if the JWT is expected to be sent as a cookie value; e.g.:

```yaml
securityDefinitions:
JWTCookieAuth:
type: apiKey
in: header
name: Cookie
x-flat-cookiename: authtoken
x-flat-jwt:
key:
env: FLAT_COOKIE_SECRET
out-var: $cookie_token
```

In this example the key is read from the `FLAT_COOKIE_SECRET` environment variable.
The JWT signing algorithm is unspecified – it defaults to `HS256`.
The JWT will be stored in `$cookie_token`.
No claims are expected.

If the request does not contain a `Cookie` header with an `authtoken` cookie, or the token is invalid, this security scheme will fail.

## Security Scheme Combinations

Swagger allows for combinations of security schemes to be applied to API endpoints.
If security schemes are specified as _alternatives_, at least one alternative must not fail.
In the following example, a `GET` request to `/foo` must satisfy at least one of the security schemes named `JWTHeaderAuth` and `JWTCookieAuth`.

```yaml
paths:
/foo:
get:
security:
- JWTHeaderAuth: []
- JWTCookieAuth: []
```

## Applying Security Schemes

In Swagger, Security Schemes can be specified at the top level (default security) or for specific operations.

In the following example, a `GET` request to `foo` must satisfy the security scheme named `JWTHeaderAuth`.
All other requests must satisfy either the `JWTHeaderAuth` **or** `JWTCookieAuth` security schemes.

```yaml
security:
- JWTHeaderAuth: []
- JWTCookieAuth: []
paths:
/foo:
get:
security:
- JWTHeaderAuth: []
```

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.

The security check is performed after [validation](validation.md).

## Full Example

```yaml
securityDefinitions:
JWTHeaderAuth:
type: apiKey
in: header
name: Authorization
x-flat-jwt:
key:
file: secret.pem
alg:
env: FLAT_JWT_ALG
out-var: $header_token
claims:
aud:
env: FLAT_JWT_AUDIENCE
JWTCookieAuth:
type: apiKey
in: header
name: Cookie
x-flat-cookiename: authtoken
x-flat-jwt:
key:
env: FLAT_COOKIE_SECRET
out-var: $cookie_token
security: # alternatives: token in Authorization header or authtoken cookie
- JWTHeaderAuth: []
- JWTCookieAuth: []
paths:
/: # default security as defined at top-level
/foo:
get:
security: # token must be in Authorization header
- JWTHeaderAuth: []
```

33 changes: 33 additions & 0 deletions reference/actions/pass-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# `pass-body` Action

The `pass-body` action sends the unmodified response body of a [`request`](request.md)
to the client. It prevents unnecessary copying which is especially useful when
dealing with binary data.

## Syntax

* `request` (optional): Specifies which request body shall be passed, default `main`.
* `mime` (optional): Sets the `Content-Type`, default `text/plain`.
* `status` (optional): Sets the HTTP response status code, see
[`set-status` action](set-status.md).

## Examples

```xml
<flow>
<request>{ "url": "…" }</request>
<pass-body />
</flow>
```

```xml
<flow>
<request>{ "id": "pass-me", "url": "…" }</request>
<pass-body request="pass-me" status="200"/>
</flow>
```

## See also

* [Processing Upstream Responses](/cookbook/upstream-response.md)
* [`body()`](/reference/functions/body.md)
12 changes: 7 additions & 5 deletions reference/actions/proxy-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ The `proxy-request` action forwards the incoming request almost unmodified to an

The HTTP method and the request body are taken as-is from the client request.
`Cookie`, `Authorization` and any hop-by-hop header fields like `Connection`
will be automatically dropped.
will be dropped automatically, the remaining header fields will be sent upstream.

The response body is written into `fit://request/content/main` where it
can be directly accessed with the [`content()` function](/reference/functions/content.md).
can be directly accessed with the [`body`](/reference/functions/body.md) or the
[`content` function](/reference/functions/content.md).
Additional information about the response, such as headers and status code can
be found in the [`$upstream` variable](/reference/variables.md#predefined-variables).

Expand All @@ -19,11 +20,12 @@ with the following properties:

### `url`

Sets the URL to the upstream system.
Sets the URL to the upstream system. Required.

### `headers`

Sets the request headers. The syntax is the same as in the [request action](request.md#headers).
Sets or removes request header fields. The syntax is the same as in the [request action](request.md#headers).
To remove a header, set its value to `""`.

### `options`

Expand Down Expand Up @@ -54,5 +56,5 @@ Sets request options. See the [`request` action options](request.md#options) for

## See also

* [`request` action](/reference/actions/request.md) (reference)
* [Forwarding a Request to an Upstream API](/cookbook/forward-request-upstream.md)
* [`request` action](/reference/actions/request.md) (reference)
5 changes: 3 additions & 2 deletions reference/actions/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ Example: POST request
</request>
```

The response body will be written into `fit://request/content/<ID>`.
It can be accessed with the [`content` function](/reference/functions/content.md): `content(<ID>)`.
The response body will be written into `fit://request/content/<ID>` and can be
accessed with the [`body` function](/reference/functions/body.md) or the
[`content` function](/reference/functions/content.md).
Additional information about the response, such as headers and status code can
be found in the [`$upstream` variable](/reference/variables.md#predefined-variables).

Expand Down
1 change: 1 addition & 0 deletions reference/functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

## Other Functions

* [`body()`](body.md)
* [`content()`](content.md)
* [`count()`](https://developer.mozilla.org/en/XPath/Functions/count)
* [`file-exists()`](file-exists.md)
Expand Down
36 changes: 36 additions & 0 deletions reference/functions/body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `body()`

```
string body([string request-id])
```

The `body` function provides access to an upstream response body. The result is
the raw body – if needed, parsing has to be done explictly.

When called without a parameter, the function returns the body for the request
with the ID `main`, which is the default ID for an [upstream request](/reference/actions/request.md).


## Example

```xml
<flow>
<request>
{"url": "https://example.com/json"}
</request>
<request>
{"id": "xml", "url": "https://example.com/xml"}
</request>

<eval out="$json">json-parse(body())</eval>
<eval out="$dom">xml-parse(body('xml'))</eval>
</flow>
```

## See also

* [Processing Upstream Responses](/cookbook/upstream-response.md)
* [`pass-body` action](/reference/actions/pass-body.md)
* [`request` action](/reference/actions/request.md)
Loading

0 comments on commit a9b017c

Please sign in to comment.