//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
//
=== RESTful services

All the features provided by the <<core>> are available as RESTful services.

The base URL for invoking such services is normally set as

....
protocol://host:port/syncope/rest/
....

where `protocol`, `host` and `port` reflect your Java EE container installation.

[NOTE]
.REST Reference
====
A complete REST reference generated from https://en.wikipedia.org/wiki/Web_Application_Description_Language[WADL^] is
http://syncope.apache.org/rest/2.1/index.html[published^] as well as made available with each deployment at

....
protocol://host:port/syncope/
....

where `protocol`, `host` and `port` reflect your Java EE container installation.
====

[TIP]
The <<swagger,Swagger extension>> might also help greatly when working with RESTful services.

==== REST Authentication and Authorization

The <<core>> authentication and authorization is based on http://projects.spring.io/spring-security/[Spring Security^].

As an initial step, authentication is required to obtain, in the `X-Syncope-Token` HTTP header, the
unique signed https://en.wikipedia.org/wiki/JSON_Web_Token[JSON Web Token^] to include in all subsequent requests.

By providing the token received in the initial exchange, the requester can be identified and checked for authorization,
based on owned <<entitlements,entitlements>>.

[NOTE]
Users can examine their own entitlements looking at the `<<x-syncope-entitlements,X-Syncope-Entitlements>>`
header value, and their own privileges looking at the `<<x-syncope-privileges,X-Syncope-Privileges>>` header value.

[TIP]
====
The relevant security configuration lies in
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/spring/src/main/resources/securityContext.xml[securityContext.xml^];
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/2_1_X/core/spring/src/main/resources/securityContext.xml[securityContext.xml^];
endif::[]
while normally not needed, this configuration can be anyway customized via the <<override-behavior,override behavior>>.

https://en.wikipedia.org/wiki/Basic_access_authentication[HTTP Basic Authentication] is set for use by default.
====

===== JWTSSOProvider

Besides validating and accepting the JSON Web Tokens generated during the authentication process as sketched above,
Apache Syncope can be enabled to cope with tokens generated by third parties, by providing implementations of the
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java[JWTSSOProvider^]
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/tree/2_1_X/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java[JWTSSOProvider^]
endif::[]
interface.

[[authorization-summary]]
.Authorization Summary
****
The set of RESTful services provided by Apache Syncope can be divided as:

. endpoints accessible without any sort of authentication (e.g. truly anonymous), for self-registration and
<<password-reset,password reset>>;
. endpoints disclosing information about the given Syncope deployment (available <<schema,schema>>, configured
<<extensions,extensions>>, Groups, ...), requiring some sort of shared authentication defined by the
`anonymousKey` value  in the `security.properties` file - for more information, read about Spring Security's
http://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#anonymous[Anonymous Authentication^];
. endpoints for self-service (self-update, password change, ...), requiring user authentication and no entitlements;
. endpoints for administrative operations, requiring user authentication with authorization granted by the related
<<entitlements,entitlements>>, handed over to users via <<roles,roles>>.
****

==== REST Headers

Apache Syncope supports a number of HTTP headers as detailed below, in addition to the common HTTP headers such as
`Accept`, `Content-Type`, etc.

[TIP]
It is possible to deal with the headers below when using the <<client-library>> via the `SyncopeClient` class methods.

===== X-Syncope-Token

`X-Syncope-Token` is returned on response to <<rest-authentication-and-authorization,successful authentication>>, and
contains the unique signed https://en.wikipedia.org/wiki/JSON_Web_Token[JSON Web Token^] identifying the authenticated
user.

The value returned for the `X-Syncope-Token` header must be included in all subsequent requests, in order for the
requester to be checked for authorization, as part of the standard https://tools.ietf.org/html/rfc6750[Bearer^]
`Authorization` header.

.Obtaining JWT with http://curl.haxx.se/[curl^] 
====
....
curl -I -u admin:password -X POST http://localhost:9080/syncope/rest/accessTokens/login
....
returns
....
HTTP/1.1 204 
X-Syncope-Token: eyJ0e..
....
which can then be used to make a call to the REST API
.....
curl -I -H "Authorization: Bearer eyJ0e.." http://localhost:9080/syncope/rest/users/self
.....
====

The token duration can be configured via the `jwt.lifetime.minutes` property - see
<<configuration-parameters, below>> for details.

===== X-Syncope-Domain

`X-Syncope-Domain` can be optionally set for requests (when not set, `Master` is assumed) to select the target
<<domains,domain>>. +
The value for this header is provided in all responses.

===== X-Syncope-Key and Location

When creating an entity (User, Group, Schema, External Resource, ...) these two headers are populated respectively with
the entity key (which may be auto-generated) and the absolute URI identifying the new REST resource.

===== X-Application-Error-Code and X-Application-Error-Info

If the requested operation is in error, `X-Application-Error-Code` will contain the error code (mostly from
ifeval::["{snapshotOrRelease}" == "release"]
https://github.com/apache/syncope/blob/syncope-{docVersion}/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java[ClientExceptionType^])
endif::[]
ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/2_1_X/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java[ClientExceptionType^])
endif::[]
and `X-Application-Error-Info` might be optionally populated with more details, if available.

===== X-Syncope-Null-Priority-Async

When set to `true`, this request header instructs the <<propagation,propagation process>> not to wait for completion
when communicating with <<external-resource-details,External Resources>> with no priority set.

===== Prefer and Preference-Applied

Some REST endpoints allow the clients to request certain behavior; this is done via the `Prefer` header.

When `Prefer` is specified in the request, the response will feature the `Preference-Applied` header, with value set
to the effective preference applied.

====== return-content / return-no-content
 
REST endpoints for creating, updating or deleting Users, Groups or Any Objects return the entity in the response payload
by default. +
If this is not required, the `Prefer` request header can be set to `return-no-content` (`return-content` will instead
keep the default behavior).

[TIP]
Use `Prefer: return-no-content` in scenarios where it is important to avoid unnecessary data in the response payload.

====== respond-async

The <<batch>> endpoint can be requested for <<asynchronous-batch-processing,asynchronous processing>>.

===== ETag, If-Match and If-None-Match

For each response containing Users, Groups or Any Objects, the https://en.wikipedia.org/wiki/HTTP_ETag[ETag^] header is
generated, which contains the latest modification date.

This value can be passed, during subsequent requests to modify the same entity, via the `If-Match` or
`If-None-Match` headers. +
When the provided `If-Match` value does not match the latest modification date of the entity, an error is reported and
the requested operation is not performed.

[TIP]
The combined usage of `ETag` and `If-Match` can be enforced to implement optimistic concurrency control over Users,
Groups and Any Objects operations.

===== X-Syncope-Entitlements

When invoking the REST endpoint `/users/self` in `GET`, the `X-Syncope-Entitlements` response header will list all
the <<entitlements,entitlements>> owned by the requesting user.

===== X-Syncope-Privileges

When invoking the REST endpoint `/users/self` in `GET`, the `X-Syncope-Privileges` response header will list all
the <<privileges,privileges>> owned by the requesting user.

==== Batch

Batch requests allow grouping multiple operations into a single HTTP request payload. +
A batch request is represented as a https://tools.ietf.org/html/rfc2046[Multipart MIME v1.0 message^], a standard format
allowing the representation of multiple parts, each of which may have a different content type (currently
JSON, YAML or XML), within a single request.

Batch requests are handled by the `/batch` REST endpoint: via HTTP `POST` method to submit requests, via HTTP `GET`
method to fetch responses <<asynchronous-batch-processing,asynchronously>>.

[NOTE]
The specification and implementation of batch processing in Apache Syncope is inspired by the standards defined
by http://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html#_Toc372793748[OData 4.0^]

===== Batch requests

The batch request must contain a `Content-Type` header specifying a content type of `multipart/mixed` and a boundary
specification as defined in https://tools.ietf.org/html/rfc2046[RFC2046^].

The body of a batch request is made up of a series of individual requests, each represented as a distinct MIME part
(i.e. separated by the boundary defined in the `Content-Type` header).

Core will process the requests within a batch request sequentially.

An individual request must include a `Content-Type` header with value `application/http` and a
`Content-Transfer-Encoding` header with value `binary`.

.Sample batch request
====
----
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652 // <1>
Content-Type: application/http
Content-Transfer-Encoding: binary
^M // <2>
POST /users HTTP/1.1 // <3>
Accept: application/json
Content-Length: 1157
Content-Type: application/json
^M
{"@class":"org.apache.syncope.common.lib.to.UserTO","key":null,"type":"USER","realm":"/"}
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
POST /groups HTTP/1.1 // <4>
Accept: application/xml
Content-Length: 628
Content-Type: application/xml
^M
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><syncope21:group xmlns:syncope21="http://syncope.apache.org/2.1">
</syncope21:group>
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
PATCH /users/24eb15aebatch@syncope.apache.org HTTP/1.1 // <5>
Accept: application/json
Content-Length: 362
Content-Type: application/json
Prefer: return-no-content
^M
{"@class":"org.apache.syncope.common.lib.patch.UserPatch","key":"24eb15aebatch@syncope.apache.org"}
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
DELETE /groups/287ede7c-98eb-44e8-979d-8777fa077e12 HTTP/1.1 // <6>
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652--
----
<1> message boundary
<2> represents CR LF
<3> user create, with JSON payload (shortened)
<4> group create, with XML payload (shortened)
<5> user update, with JSON payload (shortened)
<6> group delete
====

===== Batch responses

Requests within a batch are evaluated according to the same semantics used when the request appears outside the context
of a batch.

The order of individual requests in a batch request is significant.

If the set of request headers of a batch request are valid (the `Content-Type` is set to `multipart/mixed`, etc.)
Core will return a `200 OK` HTTP response code to indicate that the request was accepted for processing, and the
related execution results.

If Core receives a batch request with an invalid set of headers it will return a `400 Bad Request` code and perform no
further processing of the request.

A response to a batch request must contain a `Content-Type` header with value `multipart/mixed`.

Structurally, a batch response body must match one-to-one with the corresponding batch request body, such that the same 
multipart MIME message structure defined for requests is used for responses

.Sample batch response
====
----
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652 // <1>
Content-Type: application/http
Content-Transfer-Encoding: binary
^M // <2>
HTTP/1.1 201 Created // <3>
Content-Type: application/json
Date: Thu, 09 Aug 2018 09:55:46 GMT
ETag: "1533808545975"
Location: http://localhost:9080/syncope/rest/users/d399ba84-12e3-43d0-99ba-8412e303d083
X-Syncope-Domain: Master
X-Syncope-Key: d399ba84-12e3-43d0-99ba-8412e303d083
^M
{"entity":{"@class":"org.apache.syncope.common.lib.to.UserTO"}
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
HTTP/1.1 201 Created // <4>
Content-Type: application/xml
Date: Thu, 09 Aug 2018 09:55:46 GMT
ETag: "1533808546342"
Location: http://localhost:9080/syncope/rest/groups/843b2fc3-b8a8-4a8b-bb2f-c3b8a87a8b2e
X-Syncope-Domain: Master
X-Syncope-Key: 843b2fc3-b8a8-4a8b-bb2f-c3b8a87a8b2e
^M
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<syncope21:provisioningResult xmlns:syncope21="http://syncope.apache.org/2.1"></syncope21:provisioningResult>
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
HTTP/1.1 204 No Content // <5>
Content-Length: 0
Date: Thu, 09 Aug 2018 09:55:47 GMT
Preference-Applied: return-no-content
X-Syncope-Domain: Master
^M
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652
Content-Type: application/http
Content-Transfer-Encoding: binary
^M
HTTP/1.1 200 OK // <6>
Content-Type: application/json
Date: Thu, 09 Aug 2018 09:55:47 GMT
X-Syncope-Domain: Master
^M
{"entity":{"@class":"org.apache.syncope.common.lib.to.GroupTO"}
--batch_61bfef8d-0a00-41aa-b775-7b6efff37652--
----
<1> message boundary (same as request)
<2> represents CR LF
<3> user create response, with JSON payload (shortened)
<4> group create respose, with XML payload (shortened)
<5> user update, no content as `Prefer: return-no-content` <<return-content-return-no-content,was specified>>
<6> group delete response, with JSON payload (shortened)
====

===== Asynchronous Batch Processing

Batch requests may be executed asynchronously by <<respond-async,including>> the `respond-async` preference in the
`Prefer` header.

Core will return an empty response, with status `202 Accepted`.

Clients can poll the `/batch` endpoint in `GET` by passing the same boundary used for request: if `202 Accepted` is
returned, then the request is still under processing; otherwise, `200 OK` will be returned, along with the full batch
response. +
Once retrieved, the batch response is not available any more from the `/batch` endpoint.

==== Search

It is possible to search for Users, Groups and Any Objects matching a set of given conditions expressed through
https://cxf.apache.org/docs/jax-rs-search.html#JAX-RSSearch-FeedItemQueryLanguage[FIQL^].

The https://tools.ietf.org/html/draft-nottingham-atompub-fiql-00[Feed Item Query Language^] (FIQL, pronounced “fickle”)
is a simple but flexible, URI-friendly syntax for expressing filters across the entries in a syndicated feed.

The FIQL queries can be passed (among other parameters) to the search endpoints available, e.g.

* `GET /users?fiql=query`
* `GET /groups?fiql=query`
* `GET /anyObjects?fiql=query`

where `query` is an URL-encoded string representation of the given FIQL query, as in the following examples.

.Simple attribute match
====
----
username==rossini
----
====

.Wildcard attribute match
====
----
username==*ini
----
====

.Case-insensitive attribute match
====
----
username=~rOsSiNi
----
====

.Case-insensitive wildcard attribute match
====
----
username=~*iNi
----
====

.Null attribute match
====
----
loginDate==$null
----
====

.Date attribute comparison
====
----
lastLoginDate=ge=2016-03-02 15:21:22
----
====

.Resource assignment match
====
----
$resources==resource-ldap
----
====

.Group membership match (only for Users and Any Objects)
====
----
$groups==root
----
====

.Role membership match (only for Users)
====
----
$roles==Other
----
====

.Type match (only for Any Objects)
====
----
$type==PRINTER
----
====

.Complex match (featuring logical AND and OR)
====
----
username=~*iNi;(loginDate==$null,$roles==Other)
----
====

===== Sorting Search Results

Search results can be requested for sorting by passing the optional `orderBy` query parameter to the search endpoints
available, e.g.

* `GET /users?fiql=query&orderBy=sort`
* `GET /groups?fiql=query&orderBy=sort`
* `GET /anyObjects?fiql=query&orderBy=sort`

where `sort` is an URL-encoded string representation of the sort request, as in the following examples.

.Single attribute sort, default direction (`ASC`)
====
----
username
----
====

.Single attribute sort, with direction
====
----
username DESC
----
====

.Multiple attribute sort, with directions
====
----
email DESC, username ASC
----
====

==== Client Library

The Java client library simplifies the interaction with the <<core>> by hiding the underlying HTTP
communication details and providing native methods and payload objects.

The library is available as a Maven artifact:

[source,xml,subs="verbatim,attributes"]
----
<dependency>
  <groupId>org.apache.syncope.client</groupId>
  <artifactId>syncope-client-lib</artifactId>
  <version>{docVersion}</version>
</dependency>
----

ifeval::["{snapshotOrRelease}" == "snapshot"]

[WARNING]
====
Do not forget to add the following repository to your `pom.xml`:

[source,xml]
----
<repository>
  <id>ASF</id>
  <url>https://repository.apache.org/content/repositories/snapshots/</url>
  <snapshots>
    <enabled>true</enabled>
  </snapshots>
</repository>
----
====

endif::[]

[discrete]
===== Initialization

First you need to build an instance of `SyncopeClientFactoryBean` by providing the deployment base URL, as follows:

[source,java]
----
SyncopeClientFactoryBean clientFactory = new SyncopeClientFactoryBean().
              setAddress("http://localhost:9080/syncope/rest/");
----

You might also select a specific <<domains,domain>> - other than `Master`, choose to exchange XML payloads - rather
than JSON (default), or to select 
https://en.wikipedia.org/wiki/HTTP_compression[HTTP compression^] (more options in the
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/client/lib/SyncopeClientFactoryBean.html[Javadoc^]):

[source,java]
----
SyncopeClientFactoryBean clientFactory = new SyncopeClientFactoryBean().
              setAddress("http://localhost:9080/syncope/rest/").
              setDomain("Two").
              setContentType(SyncopeClientFactoryBean.ContentType.XML).
              setUseCompression(true);
----

At this point an instance of `SyncopeClient` can be obtained by passing the login credentials via:

[source,java]
----
SyncopeClient client = clientFactory.create("admin", "password");
----

Or you can combine into a single statement as:

[source,java]
----
SyncopeClient client = new SyncopeClientFactoryBean().
              setAddress("http://localhost:9080/syncope/rest/").
              create("admin", "password");
----

[discrete]
===== Usage

Select one of the
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/common/rest/api/service/package-summary.html[RESTful services^]
and invoke one of the available methods:

[source,java]
----
LoggerService loggerService = client.getService(LoggerService.class);

LoggerTO loggerTO = loggerService.read(LoggerType.LOG, "org.apache.syncope.core.connid");
loggerTO.setLevel(LoggerLevel.DEBUG);

loggerService.update(LoggerType.LOG, loggerTO);
----

[NOTE]
More RESTful services could be available besides the 
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/common/rest/api/service/package-summary.html[default set^],
as there might be <<extensions,extensions>> installed in the given deployment; the
<<apache-camel-provisioning-manager>> provides the
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/common/rest/api/service/CamelRouteService.html[CamelRouteService^],
for instance.

[TIP]
Advanced REST features are also available from `SyncopeClient` instances: check
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/client/lib/SyncopeClient.html[the javadoc^]
for more information.

.Search for Users, Groups or Any Objects
====
All search operations return
http://syncope.apache.org/apidocs/2.1/org/apache/syncope/common/lib/to/PagedResult.html[paged result handlers^]
which can be exploited both for getting the actual results and for extrapolating pagination coordinates.

[source,java]
----
UserService userService = client.getService(UserService.class);

int count = userService.search(new AnyQuery.Builder().page(0).size(0).build()).getTotalCount(); // <1>

PagedResult<UserTO> matchingUsers = userService.search(
    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
    fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("ros*ini").query()).
    build()); // <2>

PagedResult<UserTO> matchingUsers = userService.search(
    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
    fiql(SyncopeClient.getUserSearchConditionBuilder().isNull("loginDate").query()).
    build()); // <3>

PagedResult<UserTO> matchingUsers = userService.search(
    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
    fiql(SyncopeClient.getUserSearchConditionBuilder().inRoles("Other").query()).
    build()); // <4>

AnyObjectService anyObjectService = client.getService(AnyObjectService.class);

PagedResult<AnyObjectTO> matchingAnyObjects = anyObjectService.search(
    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
    fiql(SyncopeClient.getAnyObjectSearchConditionBuilder("PRINTER").query()).
    build()); // <5>

GroupService groupService = client.getService(GroupService.class);

PagedResult<GroupTO> matchingGroups = groupService.search(
    new AnyQuery.Builder().realm("/even/two").page(3).size(150).
    fiql(SyncopeClient.getGroupSearchConditionBuilder().isAssignable().
        and("name").equalTo("palo*").query()).
    build()); // <6>
----
<1> get the total number of users available in the given deployment (and <<domains,domain>>)
<2> get users in the root realm with username matching the provided wildcard expression
<3> get users in the root realm with no values for `loginDate`, i.e. that have never authenticated to the
given deployment
<4> get users in the root realm with <<roles,role>> `Other` assigned
<5> get all any objects in the root realm with <<anytype,type>> `PRINTER`
<6> get all groups that can be assigned to users or any objects in the `/even/two` realm - third page of the result,
where each page contains 150 items
====

.Delete several users at once
====
[source,java]
----
BatchRequest batchRequest = client.batch(); // <1>

UserService batchUserService = batchRequest.getService(UserService.class);

final int pageSize = 100;
final int count = userService.search(
        new AnyQuery.Builder().page(0).size(0).build()).getTotalCount(); // <2>
for (int page = 1; page <= (count / pageSize) + 1; page++) {
    for (UserTO user : userService.search(
            new AnyQuery.Builder().page(page).size(pageSize).build()).getResult()) {  // <3>  

        batchUserService.delete(user.getKey()); // <4>
    }       
}       

BatchResponse batchResponse = batchRequest.commit();  // <5>  
List<BatchResponseItem> batchResponseItems = batchResponse.getItems(); // <6>
----
<1> begin the batch request
<2> get the total number of users available in the given deployment (and <<domains,domain>>)
<3> loop through all users available, using paginated search
<4> add each user's deletion to the batch request
<5> send the batch request for processing
<6> examine the batch results
====

.Self-read own profile information
====
[source,java]
----
Pair<Map<String, Set<String>>, UserTO> self = client.self();
UserTO userTO = self.getRight(); // <1>
Map<String, Set<String>> realm2entitlements = self.getLeft(); // <2>
----
<1> http://syncope.apache.org/apidocs/2.1/org/apache/syncope/common/lib/to/UserTO.html[UserTO^] of the requesting user
<2> for each <<realms,realm>>, the owned <<entitlements,entitlements>>
====

.Change user status
====
[source,java]
----
String key = ...; // <1>
StatusPatch statusPatch = new StatusPatch();
statusPatch.setKey(key);
statusPatch.setType(StatusPatchType.SUSPEND); // <2>
UserTO userTO = userService.status(statusPatch).
  readEntity(new GenericType<ProvisioningResult<UserTO>>() {
  }).getEntity(); // <3>
----
<1> assume the key of the user to be suspended is known in advance
<2> `ACTIVATE`, `SUSPEND`, `REACTIVATE` values are accepted, and honoured depending on the actual status of the user
being updated
<3> request for user update and read back the updated entity
====
