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
-
-
-
-
-
-
-
-
-
- {{ total_count ?? 0}}
-
-
- {"error": "Unknown language"}
-
-
-
- {{$repo_url := "https://raw.githubusercontent.com/leachim6/hello-world" }}
- {{$file_path := substring-after(items/value[1]/html_url, '/blob') }}
-
- {"url": {{ concat($repo_url, $file_path) }}}
-
-
-```
-
-### `upstream_request.xml`
-
-```xml
-
-
- [
- "q=hello",
- "repo:leachim6/hello-world",
- {{ concat("filename:", $request/params/language) }},
- {{ concat("language:", $request/params/language) }}
- ]
-
-
-
- {
- "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).