Workflow API
The workflow engine simplifies the way you define different kinds of flows, such as registration or activation. It provides a simple, configurable, and flexible way to create a flow for different clients that each want their own custom flow.
The workflow API exposes two endpoints:
POST /workflowapi/process?type={type}&returnUrl={returnUrl}
POST /workflowapi/process/{processToken}
POST /workflowapi/process?type={type}&returnUrl={returnUrl}
The POST /workflowapi/process?type={type}&returnUrl={returnUrl}
API starts a process instance.
It uses the following parameters:
-
type
This parameter represents the process type to start. It represents the name of the Business Process Model and Notation (BPMN) that is triggered to start. Depending on the process type, some processes require an initial set of data as the request body to start properly. -
returnUrl
This optional parameter represents the URL where the user is redirected when the process ends.
Note
An important BPMN term to understand is process definition, which represents the XML that describes the steps in the flow. Basically, a flow means the execution of a process definition in the workflow engine (Activiti).
Request
The frontend of the workflow API renders itself according to each step in the flow. The first time a page that uses a flow is rendered, such as registration
, the user interface (UI) is responsible for making a call to the workflow API:
POST /process?type=registration
Basically, this POST
tells the workflow API to start a new workflow for the user who is accessing this page.
Response
{
"configurationName": "registration_selection_step",
"processToken": "8be540c7-20e0-4f42-baa9-38523d2ce042",
"data": {
"registration_selection_step": {}
},
"errors": {}
}
The structure of this response is important, because throughout the whole flow, this response remains consistent:
configurationName
: This field tells the UI the name of the configuration from the ui-config service (uic
) to render. If you use a custom UI, this field signals to the consumer of the API to render the next UserTask for the user. In the example above, it renders theregistration_selection_step
UserTask.processToken
: This token is linked to the ID of the process definition instance. This means that the user who initialized the process is associated with a unique token, which ensures that it is the same user that initialized the process and not a different user.data
: This defines where the data for the user is stored throughout the steps in a flow. Basically, the system keeps track of the user’s inputs.errors
: If a step in the flow fails, the UI checks this field to determine whether there are any errors from the backend in regards to the input data.
POST /workflowapi/process/{processToken}
Going back to the responsibility of the UI, the UI renders itself from the specified configurationName
.
A typical workflow includes the following steps:
- The UI renders the actual page for that step based on the configuration that it received in the response.
- The user enters some data on the page.
- The UI validates the data that the user entered.
- If the data is correct, the user clicks the Next button (or similar action).
- The UI calls the workflow API with data similar to what is covered in the
POST /workflowapi/process/{processToken}
API. - This API moves the flow to the next step in the process instance, which is identified by the
processToken.
Request
{
"language": "en_GB",
"user": {
"language": "en_GB",
"emails": "johndoeritm@mailinator.com",
"carPolicyNumber": "34561784567"
}
}
In this example, 8be540c7-20e0-4f42-baa9-38523d2ce042
is the processToken
from the first API. This causes the flow to move to the next steps until the next UserTask
, DecisionGateway
, or EndTask
.
Response
The following example shows a response from the backend:
{
"configurationName": "email_sent_step",
"processToken": "8be540c7-20e0-4f42-baa9-38523d2ce042",
"data": {
"email_sent_step": {}
},
"errors": {}
}
With the previous call, if everything goes well, the user is created in the INACTIVE state, which triggers the activation flow:
- The UI repeats from step 1 until it receives
configurationName: thanks
, which is basically a stop point or poison pill.
Workflow actions
Workflow actions give the user the ability to reiterate over process steps or to completely cancel a process that they started.
There are four actions that can control the process flow:
CONTINUE
goes to the next step in the flow, and can be omitted.STEP_BACK
goes to the previous user task in the flow.STEP_TO_SERVICE_TASK
goes to a service task in the flow, it usually recalls a service task that failed for some reason, such as sending an email or SMS.CANCEL
cancels the flow.
The workflow actions are sent to /workflowapi/process/{processToken}
as part of the request body. However, WORKFLOW_ACTION
is a reserved keyword and must not be used for any other purposes.
{
"WORKFLOW_ACTION": "CONTINUE"
}
To make the workflow actions work, the BMPN process model must implement the logic for these actions (gateways):
<exclusiveGateway id="first_task_gateway"/>
<sequenceFlow id="step_back_to_first_user_task" sourceRef="first_task_gateway" targetRef="first_user_task">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${execution.getVariable('WORKFLOW_ACTION') != null && WORKFLOW_ACTION == 'STEP_BACK'}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="cancel_flow_from_first_user_task" sourceRef="first_task_gateway" targetRef="cancel_user_task">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${execution.getVariable('WORKFLOW_ACTION') != null && WORKFLOW_ACTION == 'CANCEL'}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="step_to_generate_process_task_from_first_user_task" sourceRef="first_task_gateway" targetRef="generate_process_token">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${execution.getVariable('WORKFLOW_ACTION') != null && WORKFLOW_ACTION == 'STEP_TO_SERVICE_TASK'}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="continue_flow_from_first_user_task" sourceRef="first_task_gateway" targetRef="second_user_task">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${execution.getVariable('WORKFLOW_ACTION') == null || WORKFLOW_ACTION == 'CONTINUE' || (WORKFLOW_ACTION != 'STEP_BACK' && WORKFLOW_ACTION != 'CANCEL' && WORKFLOW_ACTION != 'GO_TO_SERVICE_TASK')}]]>
</conditionExpression>
</sequenceFlow>
The gateway specifies the exit point, or step, for the desired actions. This means that at a step, the user has the option to choose between any number of these actions (always a minimum of one: CONTINUE
).
The following example takes the user to the cancel_user_task
if the request body contains "WORKFLOW_ACTION": "CANCEL"
:
<exclusiveGateway id="first_task_gateway"/>
<sequenceFlow id="cancel_flow_from_first_user_task" sourceRef="first_task_gateway" targetRef="cancel_user_task">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${execution.getVariable('WORKFLOW_ACTION') != null && WORKFLOW_ACTION == 'CANCEL'}]]>
</conditionExpression>
</sequenceFlow>
The workflow actions are not restrictive to the previously specified behavior, but to avoid confusion, use them as designed. For example, use CANCEL
to take the user to a previous user task instead of cancelling the flow.
Process instance
When the workflow engine receives a process definition (XML), the user ingesting process definitions into the engine is responsible for starting a new flow or process. The result of starting a new flow or process represents the process instance
, which returns some information about the process instance. To identify the process instance that was initialized by the user, use processInstance.id
. When it's exposed, the name is simplified to processToken
.
User task
A user task models work that needs to be done by a human actor. The UserTask
is pretty powerful. The UserTask
can be defined as simply as follows:
<userTask id="form_registration_utask_1" name="registration_step1"/>
The UI call to render a page uses the name of the userTask
as an identifier. Simply said: userTask.name
= configurationName.
Configuring a workflow
To configure a custom workflow process, use a validation group after each user task. This validation group consists of the following tasks:
InputProvidedTask
checks whether a value has been specified for a declared field or list of fields.ValidationTask
takes the current step name and performs validation against user attributes. The validation rules can be defined inUIC
and retrieved by calling/api/v1/workflow-configuration/{process}
.
In some situations, the ValidationTask
cannot be used, and therefore it is necessary to use a custom task for data validation.
Note
If the validation fails in one of the validation steps, then the process returns to the previous step with a code and an error message.
For the sake of simplicity, only the relevant things that you can configure in the BPMN file are shown here. For a full example of a BPMN, check src/main/resources/bpmn/registration.bpmn20.xml
.
To configure a flow’s expiration time, use the token API from self-service, where you can configure the expiry token separately for each tenant and brand (value represented in duration).
<process id="registration_process" name=" Registration flow" isExecutable="true">
<startEvent id="registration_start_event" name="Start"/>
<sequenceFlow id="step1" sourceRef="registration_start_event" targetRef="form_registration_utask_1"/>
<userTask id="form_registration_utask_1" name="registration_step1"/>
<sequenceFlow id="step2" sourceRef="form_registration_utask_1" targetRef="validate_user_data_task"/>
<serviceTask id="validate_user_data_task" name="registration_step2"
activiti:delegateExpression="${validationTask}"/>
<sequenceFlow id="step3" sourceRef="validate_user_data_task" targetRef="form_registration_utask_2"/>
<userTask id="form_registration_utask_2" name="registration_step3"/>
<sequenceFlow id="step4" sourceRef="form_registration_utask_2" targetRef="create_scim_user_task"/>
<serviceTask id="create_scim_user_task" name="registration_step5"
activiti:delegateExpression="${createScimUserTask}">
</serviceTask>
<sequenceFlow id="step5" sourceRef="create_scim_user_task" targetRef="generate_token"/>
<serviceTask id="generate_token" name="registration_step4" activiti:delegateExpression="${generateTokenTask}"/>
<serviceTask id="email_sender_task" name="registration_step6"
activiti:delegateExpression="${emailSenderTask}"/>
<sequenceFlow sourceRef="generate_token" targetRef="email_sender_task"/>
<sequenceFlow id="step6" sourceRef="email_sender_task" targetRef="last_step"/>
<userTask id="last_step" name="thanks"/>
<sequenceFlow sourceRef="last_step" targetRef="registration_end_event"/>
<endEvent id="registration_end_event" name="End"/>
</process>
- A flow always includes
<startEvent>
and<endEvent>
. - When defining a
userTask
, which is usually followed by a validationTask (such asserviceTask
), if you don't specify avalidationTask
, then validation is not performed from the backend. - Steps are connected to each other using
<sequenceFlow>.
- The following service task is available:
validationTask
takes the current step name and calls/api/v1/workflow-configuration/{process}
to perform validations that are located inuic
. The validations are performed against user attributes.
When declaring a validationTask
in a BPMN file, the attribute name
is required for retrieving the correct validations.
Process variable requirements:
Input
{
"user" {
}
}
Output: None
Available tasks
Some out-of-the-box tasks are available:
emailSenderTask
Sends an email.
Process variable requirements:
Input
"userId" : "recipient's user id",
"email" : "recipient's email address",
"emailTemplate" : "name of email template",
"processToken" : "processToken",
"token" : "self service token used for building the url (in case of activation, passwordreset)"
"user" : {
"preferredLanguage" : "language for email (en_GB, nl_NL, etc)"
},
"locale" : "language for email (en_GB, nl_NL, etc)"
Note
You can use either user.preferredLanguage
or locale
.
Output: none
generateTokenTask
Generates a token for a user. You need to call this task if a user already exists, such as if the user was created using createScimUserTask
in a previous step in the flow, or if the user is available at the beginning of the flow.
Process variable requirements:
Input
{
"tokenType": "token type",
"userId" : "user id",
"email" : "user email address"
}
Output: none
createScimUserTask
Takes all the data input by the user from data and performs a SCIM create. You can configure the payload for this task.
Input
{
"user": {
"email": "user email", //required
...
}
}
Output
{
"userId": "created user id",
"email": "created user email address"
}
updateScimUserTask
Takes all the data input by the user from data and performs a SCIM update. You can configure the payload for this task.
Process variable requirements:
Input
{
"userId", "user id"
"user": {
}
}
Output:
{
"userId": "updated user id",
"email": "updated user email address"
}
HttpRequestorTask
Executes an HTTP or HTTPS request and sets the response on the execution.
Service task properties:
httpRequestConfigName
is the configuration name that contains a JSON representation of a request.
Example
<serviceTask id="http_request" activiti:delegateExpression="${httpRequestorTask}">
<extensionElements>
<activiti:field name="httpRequestConfigName" stringValue="patch_user_with_birth_date"/>
</extensionElements>
</serviceTask>
Configuration
iwelcome.requests.requests
is a JSON representation of a map in the format: <string, HttpRequestConfig>
The string value from the map represents the httpRequestConfigName
that needs to be provided in the BPMN. The HttpRequestConfig
format is explained after the example.
Example
{
"get_user_by_id": {
"request" : {
"method": "GET",
"endpoint": "http://ums:8091/api/{version}/internal/Users/{userId}",
"urlPlaceholders": {
"version": "#execution['applicationProperties']['iwelcome.umsEndpointVersion']",
"userId": "#execution['userId']"
}
},
"responseContainer": "internalUser"
},
"patch_user_with_birth_date": {
"request" : {
"method": "PATCH",
"endpoint": "http://ums:8091/api/{version}/internal/Users/{userId}",
"urlPlaceholders": {
"version": "#execution['applicationProperties']['iwelcome.umsEndpointVersion']",
"userId": "#execution['internalUser']['response']['id']"
},
"template" : {
"schemas": [
"urn:scim:schemas:core:1.0",
"urn:scim:schemas:extension:iwelcome:1.0"
],
"urn:scim:schemas:extension:iwelcome:1.0": {
"birthDate": "#data['birthDate']"
}
},
"payload" : {
"valuePlaceholders": {
"birthDate": "#execution['birthDate']"
}
}
},
"responseContainer": "updatedUser"
}
}
HttpRequestConfig
format:
request
is theHttpRequestData
object.responseContainer
is a string that represents the name of the variable from the execution where the response is set. When set to the execution, the variable defined in theresponseContainer
is of typeHttpResponseData
.HttpResponseData
format:response
is the actual response from the invoked endpoint.statusCode
is the returned HTTP status code.headers
is the returned headers, which are needed when dealing with other types of data, such as images.
HttpRequestData
format:
endpoint
is the URL where you send the request. It can contain placeholders for URL variables. Define the placeholders within curly brackets{}
.method
is the HTTP method name, such as GET, POST, PATCH, DELETE, and so on.authInfo
is the SpEL expression that specifies which type authentication to apply for the configured endpoint such as Basic Auth, Oauth, and so on.headers
is a map representing the headers needed while invoking an endpoint. The headers map follows the following pattern:header-name : header-value
.contextHeaders
is a map representing the headers needed while invoking an endpoint. This is different from the headers, because it allows header values to be specified dynamically based on a SpEL expression from the execution context, such as"contextHeaders": {"Authorization": "#execution['internalUser']['response']['access_token']"}
urlPlaceholders
is a map of<string, SpEL expression>
, where the string represents the URL placeholder names and the SpEL expression defines where to retrieve the value from the execution.template
is a mustache template that defines the payload structure for requests that support the request body (can be omitted for other requests).payload
is theRequestPayload
object.
RequestPayload
format:
valuePlaceholders
is a map of <string, SpEL expression>
where the string represents the request body placeholder names from the template
, and the SpEL expression defines where to retrieve the value from the execution.
Reserved keywords
execution:
Every SpEL expression fromurlPlaceholders
andvaluePlaceholders
starts with#execution
and this aspect is mandatory, because needed values are retrieved from the process execution.data
: Alltemplate
SpEL expressions start with#data
and this aspect is mandatory to properly map the attributes.applicationProperties
: When the process starts, all application properties are set to the process execution. To retrieve these properties, the user needs to access this map. This is a decision that simplifies the way a request is defined for anHttpRequestorTask
service task.
activateUserTask
Activates a user, which means it sets the user’s state to ACTIVE.
Process variable requirements:
Input
{
"userId": "user id"
}
Output: none
changePasswordTask
Changes a user's password.
Process variable requirements:
Input
{
"userId": "user id"
}
Output: none
deleteTokenTask
Inactivates a flow token, such as an activation token or a password reset token.
Process variable requirements:
Input
{
"token" {
"token": "process-token"
}
}
Output: none
generateConsentTask
Generates consent for the user attributes.
Process variable requirements:
Input
{
"userId": "user-id",
"processPrivateData" {
"consentData": {
"ppData": [
]
}
}
}
Output: none
generateDocumentConsentTask
Generates consent for documents (such as terms of service or a privacy policy).
Process variable requirements:
Input
{
"consentData": {
"document": {
"documents": [
{
"name": "example attribute",
...
},
...
]
}
}
}
Output: none
generateProcessTokenTask
Generates a process token.
Input: none
Output
{
"processToken": "generated-process-token"
}
Warning
The validity period for this token is taken from the ss
microservice.
generateProcessTokenTtlTask
Generates a process token for which it is mandatory to specify the time-to-live (TTL) for the token.
Input: none
Output
{
"processToken": "generated-process-token"
}
Example
<serviceTask id="generate_process_token" name="generate_process_token" activiti:delegateExpression="${generateProcessTokenTtlTask}">
<extensionElements>
<activiti:field name="tokenValidity" stringValue="P7D"/>
</extensionElements>
</serviceTask>
Note
When you define this service task, you must specify the tokenValidity
period , otherwise it uses the default period specified in the SS
microservice.
deleteProcessTokenTask
Inactivates the current process token with the WORKFLOW_PROCESS_TOKEN
type. If the token was
generated with a different type than WORKFLOW_PROCESS_TOKEN
, specify the type of the current process token to inactivate.
Process variable requirements:
Input
{
"processToken": "process-token"
}
Output: none
Example:
<serviceTask id="delete_current_process_token" name="delete_current_process_token_step" activiti:delegateExpression="${deleteProcessTokenTask}">
<extensionElements>
<activiti:field name="tokenType" stringValue="WORKFLOW_PROCESS_TOKEN"/>
</extensionElements>
</serviceTask>
GenerateHashTask
Generates a hash for a provided string value. This task takes the hashing configuration from the Consul (config/application/iwelcome.attribute-hashing.hashing-templates) and sends it to the hashing library, which means that it won't work without this configuration. This service task looks in consul for hashing.
Input
inputValue
is the value to hash.outputName
is the name of the variable on the execution that contains the hashed value.hashingTemplate
is the name of the template used to hash the value.
Note
When you define this service task, you must specify the inputValue
, outputName
, and hashingTemplate
fields, or it is unable to generate the hash.
Output
{
"isStepSuccessful": "Boolean which indicates if the value has been successfully hashed",
"outputName": "Variable on the execution which contains the hashed value. The name of the variable should be provided when defining the task inside BPMN."
}
Example
<serviceTask id="generate_hash_task" activiti:delegateExpression="${generateHashTask}">
<extensionElements>
<activiti:field name="inputValue" expression="${internalUser.id}"/>
<activiti:field name="outputName" stringValue="hashResponse"/>
<activiti:field name="hashingTemplate" stringValue="hash_with_sha256"/>
</extensionElements>
</serviceTask>
<sequenceFlow sourceRef="generate_hash_task" targetRef="check_hash_response_script_task"/>
<scriptTask id="check_hash_response_script_task" name="check_hash_response_step" scriptFormat="groovy">
<script>
String hashValue= execution.getVariable("hashResponse");
</script>
</scriptTask>
<sequenceFlow sourceRef="check_hash_response_script_task" targetRef="..."/>
SmsSenderTask
Sends an SMS to a specific user.
Input
{
"user": {
"phone":"0040...",
"language":"en_GB" },
"otpTransmissionMethod": "Transmission method can be *sms* or *voice*",
"sendOtpMaxAttempts": "specifies the maximum number of attempts that a user can do when sending an SMS.",
"userId": "userId"
}
Output
{
"isStepSuccessful": "boolean which indicates if the SMS was sent"
}
Example
<serviceTask id="send_sms_task" name="send_sms_step" activiti:delegateExpression="${smsSenderTask}"/>
Note
sendOtpMaxAttempts
specifies the maximum number of attempts that a user can perform when sending an SMS. The limit is set to three. If this maximum is exceeded, an error is generated.
If the sendOtpMaxAttempts
is not present on the execution, default configuration is picked up from iwelcome.two-fa.sendOtpMaxAttempts.<<tenant>>.<<brand>>
.
Script example
<scriptTask id="set_sms_max_attempts" scriptFormat="groovy">
<script>
execution.setVariable("sendOtpMaxAttempts", 3)
</script>
</scriptTask>
<sequenceFlow sourceRef="set_sms_max_attempts" targetRef="send_..._task"/>
SmsCodeValidationTask
Validates the SMS code.
Input
{
"user": {
"phone":"004...",
"sms_code":"123456"
},
"resendSms": "In case of resending the SMS the value true needs to be send otherwise false",
"validateOtpMaxAttempts": "validateOtpMaxAttempts specifies the maximum number of attempts that a user can do when validating an SMS code.",
"userId": "userId"
}
Output
{
"isStepSuccessful": "boolean which indicates if the SMS code was valid or not"
}
Example
<serviceTask id="sms_code_validation_task" name="sms_code_validation_task" activiti:delegateExpression="${smsCodeValidationTask}"/>
Note
validateOtpMaxAttempts
specifies the maximum number of attempts that a user can perform when validating an SMS code. The limit is set to three. If this maximum is exceeded, an error is generated.
If the sendOtpMaxAttempts
is not present on the execution, the default configuration is picked up from iwelcome.two-fa.validateOtpMaxAttempts.<<tenant>>.<<brand>>
.
Script example:
<scriptTask id="set_validate_sms_max_attempts" scriptFormat="groovy">
<script>
execution.setVariable("validateOtpMaxAttempts", 3)
</script>
</scriptTask>
<sequenceFlow sourceRef="set_validate_sms_max_attempts" targetRef="send_..._task"/>
inputProvidedTask
Checks whether a value is specified for declared fields.
When declaring this service task in a BPMN, you must specify the fieldNames
attribute. This attribute value must consist of a single attribute name or a list of attribute names separated by commas (,
).
Example
<serviceTask id="check_if_input_is_provided" activiti:delegateExpression="${inputProvidedTask}">
<extensionElements>
<activiti:field name="fieldNames" stringValue="password, email"/>
</extensionElements>
</serviceTask>
Process variable requirements:
Input
{
"user" : {
"exampleUserAttributeName" : "example attribute value",
...
}
}
Output: none
authorizationUserTask
Checks if a provided access token contains the required scopes that were configured in the BPMN.
When declaring this service task in a BPMN, you must specify the requiredScopes
attribute. This attribute value must consist of a single attribute name, or a list of attribute names separated by commas (,
).
Example
<serviceTask id="check_if_user_is_authorized" activiti:delegateExpression="${authorizationUserTask}">
<extensionElements>
<activiti:field name="requiredScopes" stringValue="openid,credential:invitation:admin:post"/>
</extensionElements>
</serviceTask>
<sequenceFlow sourceRef="check_if_user_is_authorized" targetRef="authorization_decision"/>
<exclusiveGateway id="authorization_decision"/>
<sequenceFlow id="is_authorized_flow" sourceRef="authorization_decision" targetRef="user_authorized">
<conditionExpression xsi:type="tFormalExpression">${isStepSuccessful == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="is_not_authorized_flow" sourceRef="authorization_decision" targetRef="user_not_authorized">
<conditionExpression xsi:type="tFormalExpression">${isStepSuccessful == false}</conditionExpression>
</sequenceFlow>
Input: An access token sent on the authorization header using the bearer schema.
Output
- None when the user is authorized and the access token contains the required scopes
user_not_authorized
when the user is not authorized
{
"configurationName": "user_not_authorized",
"processToken": "bac2c549-e846-4022-9952-3bc8d6435032",
"data": {
"user_not_authorized": {}
},
"errors": {}
}
validateTokenTask
Checks whether a token is valid.
Process variable requirements:
Input
{
"user": {
"token" : "token"
}
}
Output: none
getSocialProfileTask
Retrieves a social user profile from a social provider.
Process variable requirements:
Input
{
"socialProvider": "social provider name",
"token" {
"token": "social-registration-token"
}
}
Output
{
"socialUser" {
}
}
userConsentMappingTask
Maps the user consent to social consent. Creates a list of consented attributes to retrieve from social providers, based on local consented attributes. Attribute mapping is based on the map defined in Consul under the following key: iwelcome.social.<PROVIDER_NAME_mapping>
, such as iwelcome.social.facebookMapping
. To support additional social providers, some minor code change are required, such as adding a key for each provider.
Attribute mapping example (map has localAttrName : socialAttrName
form):
{
"emails": "email",
"birthDate": "user_birthday"
}
Process variable requirements:
Input
{
"socialProvider": "social provider name",
"processPrivateData" : {
"consentData": {
"ppData": [
]
}
}
}
Output
{
"socialConsent": [
]
}
socialUserDataMappingTask
Maps data retrieved from a social account to a user considering consented processing purposes. The mapping is needed because some attributes are always retrieved from a social provider. For example, Facebook returns the user's name in the public profile and the public profile is always retrieved from Facebook.
Attribute mapping is based on the map defined in Consul under the following key: iwelcome.social.<PROVIDER_NAME_ProcessingPurposeAttributeMapping>
, such as iwelcome.social.facebookProcessingPurposeAttributeMapping
. To support additional social providers, some minor code changes are required, such as adding a key for each provider.
Attribute mapping example (map has the localAttrName
: attribute name from processing purposes`` form):
Process variable requirements:
Input
{
"socialProvider": "social provider name",
"socialUser" : {
// data retrieved from social provider
}
"collectedConsentData" : {
"ppData": [
// consented processing purposes
]
}
}
Output
{
"user": [
// mapped user data
]
}
socialProviderRequesterTask
Builds the URL used for retrieving the user data from a social provider based on the consented fields.
Process variable requirements:
Input
{
"socialProvider": "social provider name",
"processToken": "process token"
"processPrivateData" : {
"consentData": {
"socialConsent": [
]
}
}
}
Output
"data" {
"redirectUrl": "the redirect url to the social provider"
}
getUserTask
Retrieves user information based on the user's email address.
Process variable requirements:
Input
{
"user" : {
"email" : "email address"
}
}
Output
{
"userId": "retrieved user id",
"email": "user email",
"internalUser": "the internal user"
}
deleteUnusedTokensTask
Deletes tokens for a user based on the token type.
Process variable requirements:
Input
{
"userId" : "userId",
"tokenType": "token type"
}
Output: none
eventPublisherTask
Publishes an event for a user. When declaring this service task in a BPMN, you must specify the eventType
attribute . This attribute value must consist of a single event type name.
Example
<serviceTask id="publish_event" activiti:delegateExpression="${eventPublisherTask}">
<extensionElements>
<activiti:field name="eventType" stringValue="LOGIN"/>
</extensionElements>
</serviceTask>
Process variable requirements:
Input
{
"userId" : "userId"
}
Output: none
generateAuthTokenTask
Generate an auth token that can be used for the authentication code flow.
Example
<serviceTask id="generate_auth_token" name="generate_auth_token" activiti:delegateExpression="${generateAuthTokenTask}"/>
<sequenceFlow sourceRef="generate_auth_token" targetRef="last_step"/>
```
Process variable requirements:
**Input**
```json
{
"userId" : "userId"
}
Output
{
"authToken": "token",
"isStepSuccessful": "boolean which indicates if the auth token was generated"
}
genericLookupTask
Searches for a user in an external database or store, based on lookup data.
Process variable requirements:
Input
{
"user" : {
"lookupData" : {
}
}
}
Output
{
"user" : {
"lookupResponseAttr1" : "value1",
...
}
}
sdUserAuthenticationTask
Verifies whether a user is a service desk user. This check is done using the request headers.
Process variables requirements:
Input: none
Output: none
userStatusActiveVerifierServiceTask
Verifies whether a user is active
based on the user's email address.
Process variables requirements:
Input
{
"user" : {
"email" : "user email address"
}
}
Output (only if the user is not active):
{
"userId" : "user id",
"email" : "user email"
}
deleteUserTask
Deletes a user.
Process variables requirements:
Input
{
"userId" : "Users' to be deleted id"
}
Output: none
saveProcessDataTask
Collects and stores BPMN process data to be shared between multiple BPMN processes.
Service task properties:
processDataTransferType
is a type of process data. Together withuserId
, it forms a unique pair for storing and retrieving purposes.transferredAttributes
is a list of attribute names (separated by commas) to retrieve from execution and store in the process data.
Example
<serviceTask id="transfer_process_data" activiti:delegateExpression="${saveProcessDataTask}">
<extensionElements>
<activiti:field name="processDataTransferType" stringValue="process-data-type"/>
<activiti:field name="transferredAttributes" stringValue="name, of, attrs, to, be, saved"/>
</extensionElements>
</serviceTask>
Process variable requirements:
Input
{
"userId" : "Users' associated with the process id" //mandatory
}
Output: none
getProcessDataTask
Retrieves BPMN process data, and each attribute in the process data is set to the process execution service task properties:
processDataTransferType
identifies the type of process data to retrieve.
Example
<serviceTask id="get_transfer_process_data" activiti:delegateExpression="${getProcessDataTask}">
<extensionElements>
<activiti:field name="processDataTransferType" stringValue="process-data-type"/>
</extensionElements>
</serviceTask>
Process variable requirements:
Input
{
"userId" : "Users' associated with the process id" //mandatory
}
Output
{
"attributes" : "stored",
"in" : "process data",
"will_be_put_on" : "execution"
}
deleteProcessDataTask
deletes the BPMN process data service task properties.processDataTransferType
specifies the type of process data to delete.
Example
<serviceTask id="delete_transfer_process_data" activiti:delegateExpression="${deleteProcessDataTask}">
<extensionElements>
<activiti:field name="processDataTransferType" stringValue="process-data-type"/>
</extensionElements>
</serviceTask>
Process variable requirements:
Input
{
"userId" : "Users' associated with the process id" //mandatory
}
Output: none
publishDataTask
Puts attributes from execution to the process public data. The attributes are in the HTTP response.
Service task properties:
publishDataConfigName
is the configuration name with attributes to publish, which means to place in the public process data.
Example
<serviceTask id="publish_transfer_process_data" activiti:delegateExpression="${publishDataTask}">
<extensionElements>
<activiti:field name="publishDataConfigName" stringValue="config_name"/>
</extensionElements>
</serviceTask>
Configuration
iwelcome.publish.publishData
json map of PublishData
object list
Example
In this example, publish_preferred_language
and publish_return_url
are publishDataConfigName
to provide as values in the BPMN.
{
"publish_preferred_language": [
{
"attributeName": "#internalUser['preferredLanguage']",
"executionVariableName": "internalUser",
"publishedAttributedName": "preferred_language"
}
],
"publish_return_url": [
{
"attributeName": "#returnUrl",
"executionVariableName": "returnUrl",
"publishedAttributedName": "return_url"
}
]
}
The format of PublishData
object is:
attributeName
is the SpEL expression for the attribute to publish.executionVariableName
is the name of the attribute in the execution where you can look for theattributeName.
publishedAttributedName
is the name of the attribute after it is published (placed in the public process data).
Process variable requirements:
Input
{
"attributes" : "from",
"process" : "execution"
}
Output
{
"attributes" : "from",
"process" : "execution"
}
IsPasswordSetForUserTask
Makes a request to the credential-api
and checks if a user has already provided a password.
Example
<serviceTask id="check_if_password_is_set_for_user" activiti:delegateExpression="${isPasswordSetForUserTask}"/>
Process variable requirements:
Input
{
"userId" : "<userId to check if password is provided>",
}
Output
{
"passwordProvided" : "true/false",
}
MoveProcessVariablesTask
Facilitates moving variables on a process execution from one place to another. This service task is like a connector task, because it prepares and makes available data for following service tasks. You can use it to match the input of a task to the output of a previous task.
Service task properties:
configuration
specifies the name that contains a JSON representation of a move data action.
Example
<serviceTask id="move_lookup_data_to_user_map" activiti:delegateExpression="${moveProcessVariablesTask}">
<extensionElements>
<activiti:field name="configuration" stringValue="activation_move_lookup_user_to_user"/>
</extensionElements>
</serviceTask>
Configuration
iwelcome.move-process-variables.configuration
is a JSON representation of a map in the format:<string, MoveDataConfig>
The string value from the map represents the configuration
that needs to be provided in the BPMN.
The MoveDataConfig
format is explained after the example.
Example
{
"activation_move_lookup_user_to_user": {
"from": "lookupUser",
"fromSpel": "#execution['lookupUser']",
"to": "user",
"toSpel": "#execution['user']",
"operation": "APPEND"
}
}
MoveDataConfig
format:
from
specifies the name of the source variable.fromSpel
is a SpEL expression used to pinpoint thefrom
attribute in the process execution.to
specifies the name of the destination variable.toSpel
is a SpEL expression used to pinpoint theto
attribute in the process execution.operation
specifies the type of move operation. It can take the following values: APPEND or OVERWRITE.
Reserved keywords
execution
: Every SPeL expression fromfromSpel
andtoSpel
starts with#execution
. This aspect is mandatory because all needed values are retrieved from the process execution.- Operation notes:*
OVERWRITE
: Overwrites theto
attribute with the value of thefrom
attribute, regardless of the data types.APPEND
: Behaves differently according to the data types that are involved. You can use the following compatible data types with theAPPEND
operation:- from Map to Map: Adds all attributes from the source map to the destination map.
- from String to Map: Adds the
fromSpel
value to the destination map. The new attribute key is represented byfrom.
- from List to List: will add all attributes from the source list to the destination list
- After the defining the flow, the last step should always be a userTask
with the name
thanks
. The UI requires this step.
Configuring push notifications
Push notification message templates
You can configure the messages in the iwelcome.imi.push-notification-message-templates.
"iwelcome-segment": {
"iwelcome-brand": {
"messageContent": {
"en_GB": {
"message": "Registration request from OneWelcome"
},
"it_IT": {
"message": "it_IT Registration request from OneWelcome"
}
}
}
}
Specify the tenant and brand inside the configuration file. Specify the logo on the tenant and brand level. The messageContent
property contains the message templates for sending the push notification, and is
configurable on the tenant level.
This configuration is useful if you need to send different messages in the push notification, for the session and for different languages. The logo is not set at the locale level, because it is the same regardless of locale.
ImiPushUserNotificationTask
This is the IMI user push notification.
Example
<serviceTask id="push_user_notification_task" activiti:delegateExpression="${imiPushUserNotificationTask}">
<extensionElements>
<activiti:field name="pollingInterval" stringValue="PT2S"/>
</extensionElements>
</serviceTask>
<sequenceFlow sourceRef="push_user_notification_task" targetRef="show_push_confirmation_step"/>
Configuration
pollingInterval
: Represents the interval (in seconds) that is used
by external parties, such as the UI, APIs, and so on, to query the notification status. Uses the ISO 8601 duration format.
Process variable requirements:
Input
{
"userId" : "<The user ID for which the QR code is generated>",
"language" : "<The language that is displayed for the push notification. Example of language: en_GB>"
}
Output
{
"show_push_confirmation_step": {
"validityPeriod": "Represents the time elapsed since the push notification was sent, after this period, the service task returns status = FAILED",
"pollingInterval": "Integer representing the notification status polling interval (in seconds)",
}
}
GetImiNotificationStatusTask
Retrieves the IMI notification status for the nonce code that is present in the process.
Example
<serviceTask id="query_notification_status_task" activiti:delegateExpression="${getImiNotificationStatusTask}">
</serviceTask>
Process variable requirements:
Input
The get notification status API is called using the nonce
value.
{
"nonce": "A unique identifier used in the push notification process generated in the ImiPushNotificationTask step.",
"option": "Two possible values 'CANCEL' or 'PUSH_NOTIFICATION'. CANCEL ends the process and PUSH_NOTIFICATION resends the push notification."
}
Output
{
"status": "The status of the imi confirmation (values: CONFIRMED, PENDING or FAILED)",
"isStepSuccessful": "Boolean that indicates whether the query to the notification status API was successful."
}
ImiDeleteAccountsTask
Deletes all the accounts for a specific user.
Example
<serviceTask id="delete_imi_accounts_service_task" activiti:delegateExpression="${imiDeleteAccountsTask}"/>
<sequenceFlow sourceRef="delete_imi_accounts_service_task" targetRef="delete_user_service_task"/>
Process variables requirements:
Input
{
"userId" : "<The user ID for which the QR code is generated>"
}
Output
{
"isStepSuccessful": "Boolean that indicates whether the delete account status API was successful."
}
GenerateImiEnrolQrTask
Generates a QR code for a user. The generated code can be used for OMI enrolment.
Example
<serviceTask id="generate_enrolment_qr_code" activiti:delegateExpression="${generateImiEnrolQrTask}">
<extensionElements>
<activiti:field name="pollingInterval" stringValue="PT2S"/>
</extensionElements>
</serviceTask>
SsoCookieVerifierServiceTaskTest
Validates whether the SSO session is valid.
Example
<serviceTask id="sso_cookie_verifier_service_task" activiti:delegateExpression="${ssoCookieVerifierServiceTask}"/>
<sequenceFlow sourceRef="sso_cookie_verifier_service_task" targetRef="<target>"/>
Process variable requirements:
Input
{
"Cookie iwdev=value" : "The cookie that contains the SSO session named iwdev, this session is obtained after successful authentication."
}
Output
{
"isStepSuccessful": "Boolean that indicates whether the SSO session is valid.",
"errors": "When the SSO session is invalid or missing, the error message is: Sso token validation failed."
}
Configuration
pollingInterval
: Represents the interval (in seconds) that external parties, such as the UI, APIs, and so on, use to query the QR scanning status. Uses the ISO 8601 duration format. Process variable requirements:
Input
{
"userId" : "<The user ID for which the QR code is generated>"
}
Output
{
"qrCode": "The generated QR code",
"pollingInterval": "Integer representing the QR scan status polling interval (in seconds)",
"validityPeriod": "Represents the time elapsed since the QR was generated. After this period, the service task returns status = FAILED and isStepSucessfull = false.",
"isStepSuccessful": "Boolean that indicates whether the QR code was generated."
}
GetImiEnrolmentStatusTask
Retrieves the IMI enrolment status for the nonce code that is present in the process.
Example
<serviceTask id="get_imi_enrolment_status" activiti:delegateExpression="${getImiEnrolmentStatusTask}">
</serviceTask>
Process variable requirements:
Input
The get enrolment status API is called using the nonce
value.
{
"nonce": "A unique identifier used in the enrolment process generated in the GenerateImiEnrolQrTask step."
}
Output
{
"status": "The status of the imi enrolment (values: enrolled, pending, failed).",
"isStepSuccessful": "Boolean that indicates whether the query to the enrolment status API was successful."
}
Status cases
enrolled (isStepSuccessful=true)
: The enrolment was successful and can move to the next step.pending (isStepSuccessful=true)
: The user has not yet scanned the generated QR code. The process should stay in the same step (GetImiEnrolmentStatusTask
).failed (isStepSuccessful=false)
: The credential API call failed and the process should be redirected to the generate QR code step.
Configuration of precomputed claims
The PrecomputedClaims flow is a mechanism triggered by the login-api service when a user needs to have OIDC or SAML claims precomputed in some way. The BPMN that is triggered can be configured from the login-api properties.
Generally, the steps in a precomputed claims flow look like this:
- Receive the default claims from the login-api.
- Calculate new claims.
- Pass the new claims to an
httpRequestor
task, which sends the requests to UDH for storing the claims.
The initial claims can be retrieved from the execution variables using the initialClaims
key .
WARNING: When receiving input from the user, and before storing the precomputed claims, always make sure that all data that came from user input is actually valid.
In the following example of a flow, a user can enter malicious data: When the precomputed claims are triggered, the user needs to select one group to use on the external application that is triggering the OIDC or SAML request. Instead of selecting a group that this user has, the user calls the API and specifies that they selected the group admins. If no checks are done in the workflow to determine if this user actually belongs to that group, and the precomputed claims are stored, the user gains administrative rights for the external application.
Note
In general, anything that comes from a userTask
must be validated.
Listeners and events
Process End Event Listener
Deletes the workflow process token from self-service. This event must be attached to the end event on every workflow BPMN definition.
<endEvent id="generic_end_event" name="End">
<extensionElements>
<activiti:executionListener event="end" delegateExpression="${processEndEventListener}"/>
</extensionElements>
</endEvent>
Get Process Purposes Listener
Listener that can be attached to a user task. When attached, it retrieves the consent process purposes for that task, filtered by the filter
field.
<userTask id="example_user-task" name="example_name">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${getProcessingPurposesListener}">
<activiti:field name="filter" stringValue="tags eq step2"/>
</activiti:taskListener>
</extensionElements>
</userTask>
Note
When modifying processing purposes in consul, remember that changing the tags
field directly affects the registration flows (workflow-api bpmn). If you do not provide the correct tags, the processing purpose does not appear throughout the registration flows, and consent is not saved for the user. The tags
field is connected with the steps defined in the BPMNs.
Example
If we have a userTask
in a BPMN:
<userTask id="example_user-task" name="example_name">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${getProcessingPurposesListener}">
<activiti:field name="filter" stringValue="tags eq step2"/>
</activiti:taskListener>
</extensionElements>
</userTask>
The value step2
needs to be in the processing purpose in the tags
field.
Example of processing purpose (notice the tags
section in the JSON):
{
"id": "1",
"consentType": "ATTRIBUTE_CONSENT",
"descriptions": [
{
"locale": "en_GB",
"description": "Purpose for consent"
},
{
"locale": "nl_NL",
"description": "Purpose for consent in Dutch"
}
],
"legalBasis": "consent",
"attributeName": "emails",
"dataController": "iWelcome B.V.",
"default_cache_time_to_live": "0",
"data_retention_period": "0",
"tags": [
"step2"
],
"consent_withdrawable": true,
"status": "active"
}
Process variable requirements:
No input variable is needed.
The listener queries for the required processing purposes and places them in the following variables:
- Processing purposes with attribute
legalBasis : consent
.
"data" {
"consentData": {
"ppData": [
]
}
}
- Processing purposes with attribute
legalBasis : contract
"data" {
"processPrivateData": {
"consentData": {
"ppData": [
]
}
}
}
Collect Process Purposes Listener
Should be used with Get Process Purposes Listener. You can attach the Collect Process Purposes Listener to a user task. When attached, it adds the consented processing purposes in the task to the possible previously consented processing purposes in the process flow.
<userTask id="example_user-task" name="example_name">
<extensionElements>
<activiti:taskListener event="complete" delegateExpression="${collectProcessPurposesListener}"/>
</extensionElements>
</userTask>
Process variable requirements:
The listener looks into the following process variable to collect the consented processing purposes:
"data" {
"consentedData": {
"ppData": [
]
}
}
The listener collects the consented private purposes in the following process variable (note that processPrivateData
is not sent via HTTP and remains visible only to the workflowapi service):
"data" {
"processPrivateData": {
"consentData": {
"ppData": [
]
}
}
}
Get Documents Listener
Listener that you can attach to a user task. When attached, it retrieves the consent documents for that task, filtered by the filter
field.
<userTask id="example_user-task" name="example_name">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${getDocumentsListener}">
<activiti:field name="filter" stringValue="tags eq step1, status eq active"/>
</activiti:taskListener>
</extensionElements>
</userTask>
Process variables requirements:
No input variable is needed.
The listener queries for the needed documents and places them in the following variables:
"data" {
"consentData": {
"documents": [
]
}
}
Collect Documents Listener
Should be used with the Get Documents Listener. You can attach the Collect Documents Listener to a user task. When attached, it adds the consented documents in the task to the possible previously consented documents in the process flow.
<userTask id="example_user-task" name="example_name">
<extensionElements>
<activiti:taskListener event="complete" delegateExpression="${collectDocumentsListener}"/>
</extensionElements>
</userTask>
Process variable requirements:
The listener looks into the following process variable to collect the consented documents:
"data" {
"consentedData": {
"documents": [
]
}
}
API validation
- Both the UI and the API validate the data that the user submits.
- To perform the API validation, use the validators _ _defined in the
uic
. - Define the API validations in consul under the following key:
CONFIG/UIC/WORKFLOW/API/VALIDATIONS/
. - For each process there is a key under the specified folder and a key has the
tenant.brand.process_id_
format. - Validators _ are defined for (
process_id
,_step_name
,field_name
) tuples.
API validator types
- Maximum length (
max_length
): Defines a maximum length for a field. Constraint: The value must be a positive integer. - Minimum length (
min_length
) : Defines a minimum length for a field. Constraint: The value must be a positive integer. - Regular expression (
regex
) : Defines a regular expression that the field must match. Constraint: The value must be a valid java regex. - Required (
required
): Defines a required Boolean value for the field. Constraints:true
if value is required,false
otherwise. - Unique email (
unique_email
) : Defines whether the value (email) must be unique among the primary email addresses for a brand. Constraints:true
if value is required,false
otherwise. - Unique attribute(
unique_attribute
): Defines whether the attribute value must be unique among the other existing attribute values. - Attribute: Specifies the name of the unique attribute. Constraints:
true
if the value is required,false
otherwise.
Example for the activation flow:
- consul key
config/uic/workflow/api/validations/ongo.bikes.activation
[
{
"stepName": "registration_step2",
"validations": [
{
"attribute": "urn:scim:schemas:core:1.0:emails[0].value",
"attributeValidations": [
"#ValidatorFactory.from('min_length').withConstraints(1)",
"#ValidatorFactory.from('max_length').withConstraints(50)",
"#ValidatorFactory.from('unique_email').withConstraints(true)"
]
},
{
"attribute": "urn:scim:schemas:extension:ohra:1.0:Relatienummer",
"attributeValidations": [
"#ValidatorFactory.from('unique_attribute').attribute('Relatienummer').withConstraints(true)"
]
},
{
"attribute": "urn:scim:schemas:core:1.0:password",
"attributeValidations": [
"#ValidatorFactory.from('min_length').withConstraints(8)",
"#ValidatorFactory.from('max_length').withConstraints(24)",
"#ValidatorFactory.from('regex').withConstraints('^(.*[@#$%*(!]){2}')"
]
}
]
},
{
"stepName": "registration_step4",
"validations": [
{
"attribute": "urn:scim:schemas:core:1.0:name.givenname",
"attributeValidations": [
"#ValidatorFactory.from('min_length').withConstraints(1)",
"#ValidatorFactory.from('max_length').withConstraints(50)",
"#ValidatorFactory.from('required').withConstraints(false)"
]
}
]
}
]
Consent Generation
- The consent microservice is called using a JWT that contains a fictitious user with the ROLE of SD user. This JWT solution is temporary. The client is then responsible for logging in a super-user that can create consent for the user that registers.
GenerateConsentTask
is used for attribute consent generation, which takes the attribute consents from the process variables and stores them.GenerateDocumentConsentTask
is used for document consent generation, which takes the documents from the process variables, looks for the configuration in UIC, and then stores the consent for thedocumentId
and the user.
The main reason for the separation of consent generation is flexibility. You can add service tasks anywhere in the BPMN process and it is generic, can be added on multiple steps of the process, and all that you need to do is have the consents on the processVariables.
The consentData
can be incomplete. For example, it can have only attribute consent data if you are storing only attribute consent, and you can add the document consent later, or not at all.
{
"consentData": {
"document": {
"documents": [
{
"name": "tos"
},
{
"name": "pp"
}
]
}
}
}
- Add the desired consent generator task in the BPMN at the required step.
BPMN sub processes
Two reusable sub processes have been created. One is for sending an email, and one is for activating the user.
The sub process must be included in the main process, as follows:
Send email
<!-- CALLING A SUBPROCESS FOR SENDING ACTIVATION EMAIL -->
<callActivity id="process_send_activation_email" name="Send activation email"
calledElement="send_email">
<extensionElements>
<activiti:in source="tokenType" target="tokenType" />
<activiti:out source="tokenType" target="tokenType" />
<activiti:in sourceExpression="${emailTemplateIdentifierService.getEmailTemplate('resendactivation')}" target="emailTemplate" />
<activiti:in source="userId" target="userId"/>
<activiti:in source="email" target="email"/>
<activiti:out source="ERRORS" target="ERRORS"/>
<activiti:in source="ERRORS" target="ERRORS"/>
<activiti:out source="isStepSuccessful" target="isStepSuccessful"/>
<activiti:in source="isStepSuccessful" target="isStepSuccessful"/>
<activiti:in source="processToken" target="processToken"/>
<activiti:out source="processToken" target="processToken"/>
<activiti:in source="headers" target="headers"/>
<activiti:out source="headers" target="headers"/>
<activiti:out source="token" target="token"/>
</extensionElements>
</callActivity>
Activate user
<!-- CALLING A SUBPROCESS FOR USER ACTIVATION -->
<callActivity id="process_user_activation" name="User confirmation step"
calledElement="activate_user_process">
<extensionElements>
<activiti:in source="userId" target="userId"/>
<activiti:out source="userId" target="userId"/>
<activiti:in source="email" target="email"/>
<activiti:in source="user" target="user"/>
<activiti:out source="user" target="user"/>
<activiti:out source="ERRORS" target="ERRORS"/>
<activiti:in source="ERRORS" target="ERRORS"/>
<activiti:out source="isStepSuccessful" target="isStepSuccessful"/>
<activiti:in source="isStepSuccessful" target="isStepSuccessful"/>
<activiti:in source="processToken" target="processToken"/>
<activiti:out source="processToken" target="processToken"/>
<activiti:in source="headers" target="headers"/>
<activiti:out source="headers" target="headers"/>
</extensionElements>
</callActivity>
Configuring payloads
SCIM postObject/patchObject configurations:
The configuration is done using the iwelcome.scim.postObject.<<TENANT>>.<<BRAND>>
key in Consul. Replace the TENANT and BRAND according to your needs.
By default, there is a sensible default for any type of tenant and brand:
iwelcome.scimpostObject.default_tenant.default_brand: "@@scim/scim-create.json.mustache"
If no configuration is made in Consul, the src/main/resources/scim/scim-create.json.mustache
are picked off.
The JSON is actually a mustache template, which is changed at runtime with the corresponding tenant brand that comes from reverse proxy.
To override the default configuration, go to src/main/resources/scim/scim-create.json.mustache
, take a copy, replace all occurrences of {{segment}}
and {{brand}}
with your tenant and brand combination,
and then paste that in a new key in Consul: config/workflowapi/iwelcome.scim.postObject.YOUR_TENANT.YOUR_BRAND
The same applies for the iwelcome.scim.patchObject.<<TENANT>>.<<BRAND>>
configuration, but the corresponding configuration
is located in src/main/resources/scim/scim-patch.json.mustache
NOTE: When configuring the scim-[create/patch].json file, remember to wrap each attribute value with simple quotes, otherwise the value is not read as a string. For more information about this Sprint Expression Language requirement, see https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions.
Example: "'ongo'"
Configuring createScimUserTask
The configuration is done using the iwelcome.scim.postObject.<<TENANT>>.<<BRAND>>
key in Consul.
The value of this key is a map of maps that represents the link between payload attributes and the user-to-be-created SCIM attributes.
The key for each map in the config represents the createScimUserTask
BPMN ID of that particular service task.
To change a payload attribute name, change the mapped attribute key as follows:
- The name of the new payload attribute needs to be defined as
#userMap['newPayloadAttributeName']
.
The following example maps the email
(defined by the #userMap['email']
) attribute from the payload to the emails
SCIM attribute. For example, for iwelcome.scim.postObject.ongo.bikes
and the BPMN service task that is associated with it:
{
"segment": "'ongo'",
"brand": "'bikes'",
"locale": "'nl_NL'",
"operation": "'post'",
"values": {
"emails": [
{
"primary": true,
"type": "'home'",
"value": "#userMap['email']"
}
],
"preferredLanguage" : "en_GB",
"schemas": [
"urn:scim:schemas:core:1.0",
"urn:scim:schemas:extension:iwelcome:1.0"
],
"urn:scim:schemas:extension:iwelcome:1.0": {
"segment": "'ongo'",
"state": "'INACTIVE'"
}
}
}
Note
Set the preferredLanguage
while creating the user. This attribute facilitates sending email to the user in their language of choice (the language that was set during registration).
Step input or output validation
To control the data that is present in the workflow process, for security reasons, configure each step to accept or return only the attributes that it needs.
This can be configured through Consul (iwelcome.workflow-data-sanitization.configuration
), where you can specify the
input and the output attributes that are accepted for each step. The extra attributes present in the payload are ignored.
In addition to filtering the input and output attributes, you can make certain value manipulations with the help of SpEL expressions when needed.
One example is masking the phone number on the output (response to the client, which can be the UI). This is optional.
If an attribute is mutated using a SpEL expression, its name is prefixed with mutated_
when returning it in the response. For example, phoneNumber
is returned as mutated_phoneNumber
.
"example_step": {
"input": [
{
"attributeName": "firstName",
"spelExpression": "#firstName.toUpperCase()"
},
{
"attributeName": "phoneNumber"
}
],
"output": [
{
"attributeName": "firstName"
},
{
"attributeName": "phoneNumber",
"spelExpression": "#phoneNumber.replaceAll(\"(?<=.{2}).(?=.{1})\", \"*\")"
}
]
}
With the example configuration above, the following actions are performed:
- The
firstName
attribute is upper-cased before saving it to the process execution (and going forward, everywhere this attribute is used, it is upper-cased). - The
phoneNumber
attribute does not suffer any mutations on the input side. - The
firstName
attribute is part of the response (it is upper-cased because of the previousinput
configuration). - The
phoneNumber
attribute is part of the response and every character or digit in it, except the first two and the last one, are replaced by the character. The replacement is not saved on the process execution, which means that the actualphoneNumber
from the user input is used in the process execution.
Configure the workflow to check the input and the output for the step named example_step
:
-
Input: a list of data sanitization objects
attributeName
: (string) the name of the attribute that is accepted as input in the payloadspelExpression
: a SpEL expression
-
Output: a list of data sanitization objects
attributeName
: (string) the name of the attributes returned by the stepspelExpression
: a SpEL expression that changes the attribute, the name of the attribute is prefixed withmutated_
Both input
and output
are optional attributes in the configuration JSON.
Filtering is not performed if the input or output attributes are not present. If these attributes are defined as empty JSON ([]
), the step input or output validation filters out everything.
SpEL expression example
To mask the phone number when returning it to the UI, so that only the first two digits and the last digit are shown, and the rest of the digits are replaced with *
, use the following SpEL expression:
#phoneNumber.replaceAll("(?⇐.{2}).(?=.{2})", "*")
Profile completion step configuration
The profile completion step allows you to ask for additional data from the user when that data is not present.
For example, we call an IDP (such as a user lookup) that returns data for the user, but it is missing the user’s email, which is an attribute that we need. The profile completion page should have a configuration that allows us to state which attributes we need, and if some of them are missing, request them from the user.
In this example, we need the email, so that the user is redirected to the profile completion page, where they must specify the email.
Use case
When a login with a remote IDP is performed, and the login fails because there is no account linked in the iWelcome store, then a workflow is triggered with the ID token data that has been received from the remote IDP. The data is used to look up the user in the iWelcome store, and if there is no user found, we have to request additional information.
Additional information is requested through the profile completion page. The page is constructed by comparing the input payload with a list of configured attributes. If something is missing from the input payload, then profile completion asks for the missing data. After the user completes the forms, then that data is used to look up the user again.
BPMN configuration
The service task findMissingRequiredDataTask
checks for the requiredDataConfigName
, based on a configuration in Consul, and checks whether the user has the attributes specified in the configuration. The output of this service task is as follows:
-
Required data is missing:
requiredAttributesMissing
process variable is set totrue
, stating that some data is missingmissingData
is a list of missing attributes that is put on the public process data
-
All the required data is present for the user:
requiredAttributesMissing
process variable is set tofalse
After this gateway, the user is directed to the profile completion step, if needed, or to the next step in the flow, if no additional data is required.
The missingData
variable is removed from the processPublicData
, to not be seen on further workflow steps.
<serviceTask id="check_required_data" activiti:delegateExpression="${findMissingRequiredDataTask}">
<extensionElements>
<activiti:field name="requiredDataConfigName" stringValue="required_step1"/>
</extensionElements>
</serviceTask>
<sequenceFlow sourceRef="check_required_data" targetRef="additional_data_required"/>
<exclusiveGateway id="additional_data_required"/>
<sequenceFlow id="data_required" sourceRef="additional_data_required" targetRef="profile_completion_step">
<conditionExpression xsi:type="tFormalExpression">${requiredAttributesMissing == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="data_not_required" sourceRef="additional_data_required" targetRef="next_step">
<conditionExpression xsi:type="tFormalExpression">${requiredAttributesMissing == false}</conditionExpression>
</sequenceFlow>
<userTask id="profile_completion_step" name="profile_completion_step"/>
<sequenceFlow sourceRef="profile_completion_step" targetRef="remove_missing_attributes"/>
<scriptTask id="remove_missing_attributes" scriptFormat="groovy">
<script>
Map publicData = execution.getVariable("processPublicData", Map.class)
publicData.remove("missingData")
</script>
</scriptTask>
<sequenceFlow sourceRef="remove_missing_attributes" targetRef="next_step"/>
Consul configuration
To configure the required attributes for the profile completion page, you must change the following Consul key: iwelcome.required-step-data.configuration
. The configuration is a list of attributes (strings) that are needed in the respective step.
{
"required_step1": [
"email",
"familyName",
"givenName"
]
}
The key required_step1
is the requiredDataConfigName
specified in the service task findMissingRequiredDataTask
in the BPMN. For this reason, this configuration does not have to be tenant and brand aware, and you can use it multiple times in the same BPMN, you just have to change the requiredDataConfigName
.
<serviceTask id="check_required_data" activiti:delegateExpression="${findMissingRequiredDataTask}">
<extensionElements>
<activiti:field name="requiredDataConfigName" stringValue="required_step1"/>
</extensionElements>
</serviceTask>
Social identity link
Initiate identity link: The service task initIdentityLinkTask
initiates the account linking process before redirecting the user to the (social) identity provider for login. The required key needs to be set on execution: identityProviderName
.
<serviceTask id="init_social_account" activiti:delegateExpression="${initIdentityLinkTask}"> </serviceTask>
<sequenceFlow sourceRef="init_social_account" targetRef="init_account_decision"/>
_Get identity link profile process input: _the output of the initIdentityLinkTask
task, the authorization_code
, and redirect_uri
.
_Process output: _the output is a map containing the account information from the remote IDP, and is set on the execution context under them profileData
property.
<serviceTask id="get_remote_idp_profile" activiti:delegateExpression="${getRemoteIdpProfile}"/>
<sequenceFlow sourceRef="get_remote_idp_profile" targetRef="add_remote_idp_data_to_public_process"/>
Activate identity link: The service task activateIdentityLinkTask
activates the identity link. Invoking this service task changes the status of the identity-link from pending to active. Before you use this service task, use the initIdentityLinkTask
. The required keys that you need to set on execution: initiateIdentityLink
, idpUser
.
<serviceTask id="activate_social_account" activiti:delegateExpression="${activateIdentityLinkTask}"> </serviceTask>
<sequenceFlow sourceRef="activate_social_account" targetRef="activate_social_account_gateway"/>
Link Account: The service task LinkAccountTask
is used to complete the identity link. Before using this service task, use the initIdentityLinkTask
. The required keys that you need to set on execution: initiateIdentityLink
, authorization_code
, redirect
_uri``
<serviceTask id="link_social_account" activiti:delegateExpression="${linkAccountTask}"> </serviceTask>
<sequenceFlow sourceRef="link_social_account" targetRef="link_account_decision"/>
Redirect hook: The service task RedirectHookTask
redirects the user to any social provider. Before using this service task, use the initIdentityLinkTask
.
<serviceTask id="set_return_url" activiti:delegateExpression="${redirectHookTask}"> </serviceTask>
<sequenceFlow sourceRef="set_return_url" targetRef="redirect_user_task"/>
<userTask id="redirect_user_task" name="redirect_hook"/>
<sequenceFlow sourceRef="redirect_user_task" targetRef="remove_return_url_from_public_data"/>
Advanced configuration of createScimUserTask
It is possible using SpEL expressions to configure something more complicated than just normal attributes or default values. For example, to configure a field to be lowercase, you could do the following:
{
"create_user_task": {
"segment": "'ongo'",
"brand": "'bikes'",
"locale": "'nl_NL'",
"operation": "'post'",
"values": {
"emails": [
{
"primary": true,
"type": "'home'",
"value": "#userMap['email'].toLowerCase()"
}
],
"schemas": [
"urn:scim:schemas:core:1.0",
"urn:scim:schemas:extension:iwelcome:1.0"
],
"urn:scim:schemas:extension:iwelcome:1.0": {
"segment": "'ongo'",
"state": "'INACTIVE'"
}
}
}
}
Another example would be if you want to strip some substring from an attribute, based on a condition:
{
"create_user_task": {
"segment": "'ongo'",
"brand": "'bikes'",
"locale": "'nl_NL'",
"operation": "'post'",
"values": {
"emails": [
{
"primary": true,
"type": "'home'",
"value": "#userMap['email']"
}
],
"schemas": [
"urn:scim:schemas:core:1.0",
"urn:scim:schemas:extension:iwelcome:1.0"
],
"urn:scim:schemas:extension:ohra:1.0": {
"Relatienummer": "(#userMap['Relatienummer'] != null && #userMap['Relatienummer'].startsWith('0')) ? #userMap['Relatienummer'].replaceFirst('^0+(?!$)','') : #userMap['Relatienummer']",
"mijnohra": true,
"binregistered": "'false'"
},
"urn:scim:schemas:extension:iwelcome:1.0": {
"segment": "'ongo'",
"state": "'INACTIVE'"
}
}
}
}
This example verifies whether the relatienummer
starts with a 0 (zero), strips the 0s using a regex, and replaces it with an empty string (this is the Java equivalent of Linux sed
).
Configuring updatedScimUserTask
The configuration is done using iwelcome.scim.patchObject.[[TENANT]](https://github.com/onewelcome/se-community-documentation/blob/master/documentation/workflow%20api/workflow%20api%20public.adoc#TENANT).[[BRAND]](https://github.com/onewelcome/se-community-documentation/blob/master/documentation/workflow%20api/workflow%20api%20public.adoc#BRAND)
key in Consul. The value of this key is a map of maps that represents the link between payload attributes and the SCIM attributes for the user to update . The key for each map in the configuration represents the updateScimUserTask
BPMN ID of that particular service task.
To change a payload attribute name, you must change the mapped attribute key. Define the name of the new payload attribute needs as #userMap['newPayloadAttributeName']
The following example maps the email
(defined by #userMap['email']
) attribute from payload to the emails
SCIM attribute. The following example shows iwelcome.scim.patchObject.[[TENANT]](https://github.com/onewelcome/se-community-documentation/blob/master/documentation/workflow%20api/workflow%20api%20public.adoc#TENANT).[[BRAND]](https://github.com/onewelcome/se-community-documentation/blob/master/documentation/workflow%20api/workflow%20api%20public.adoc#BRAND)
and the BPMN service task that is associated with it:
<serviceTask id="update_user_profile" activiti:delegateExpression="${updateScimUserTask}"/>
{
"update_user_profile": {
"segment": "'ongo'",
"brand": "'bikes'",
"locale": "'nl_NL'",
"operation": "'patch'",
"values": {
"name": {
"familyName": "#userMap['familyName']",
"givenName": "#userMap['givenName']"
},
"title": "#userMap['title']",
"password": "#userMap['password']",
"schemas": [
"urn:scim:schemas:core:1.0",
"urn:scim:schemas:extension:iwelcome:1.0"
],
"urn:scim:schemas:extension:iwelcome:1.0": {
"segment": "'ongo'",
"state": "'ACTIVE'"
}
}
}
}
Process Flows
Each user task corresponds to a UI page. The name of the user task is the name of the UI page.
If you configure a new user task it should look like this:
<userTask id="user_task_id" name="page_name"/>
Here the page_name is the name of the UI page config that will be used for this step.
These UI pages configurations can be configured in consul, config/uic/ui/tenant.brand.workflowEngine.name. Replace tenant, brand and name with what is needed.
The following tables explain the correlation between BPMN user tasks and the UI pages configuration.
Flow Type: registration
step | configuration(config/uic/ui/tenant.brand.workflowEngine.name) |
---|---|
registration_selection_step | registration_selection_step |
form_registration_utask_confirm_email | email_sent_step |
last_step | thanks |
Flow Type: activation
step | configuration(config/uic/ui/tenant.brand.workflowEngine.name) |
---|---|
form_registration_utask_confirm_email | email_confirm_step |
token_validation_failed | token_validation_failed |
form_registration_utask_2 | registration_step3 |
form_registration_utask_1 | registration_step1 |
last_step | thanks |
Flow Type: registration_email_consent
step | configuration(config/uic/ui/tenant.brand.workflowEngine.name) |
---|---|
registration_email_consent_step | registration_email_consent_step |
email_sent | email_sent_step |
Flow Type: activation_idin_lookup_otp
step | configuration(config/uic/ui/tenant.brand.workflowEngine.name) |
---|---|
wait_for_activation_email_click | email_confirm_step |
token_validation_failed | token_validation_failed |
set_user_password | set_password_step |
select_idin_bank | select_idin_bank_step |
match_successful | match_successful_step |
match_failed | match_failed_step |
set_phone_number | set_phone_number_step |
input_otp | input_otp_step |
activation_failed | activation_failed_step |
activation_complete | account_ready |
Activation process flow
The user activation process flow starts when a user is created. If the user is INACTIVE, the activation flow sends an activation email to the user’s primary email.
The workflow listens to a rabbitMQ queue called activation-queue
for the user-created process event. This process event is published by UMS when creating a user.
The listener picks up this event and consumes it, starting the activation flow and populating it with initial data collected from the event.
When starting a new activation process, the old activation processes are deleted. For details about how this is done, see the Ending already existing processes section.
If the workflow cannot process the message on this queue after a configurable number of retries, then the message is passed to the activation dead letter queue. The following lines configure the retry mechanism for reading a message from rabbitMQ (maximum number of retries, delay, interval).
Remember that for the dead letter queue to work as expected, you need to configure the default-requeue-rejected
property to false
, otherwise the messages are lost:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true
initial-interval: 2000
max-attempts: 2
multiplier: 1.5
max-interval: 5000
default-requeue-rejected: false
Complex invitation process
The invitation flow is used when a user (invitor in this case) wants someone else (invitee) to register, and maybe give that person access to some resources.
To start the invitation process, the invitor completes some data about the invited user:
- language
- personal note: Some validation is performed. If it fails, the user is redirected to the start page, where they have to change the data.
After this step, the invited user receives an email informing them that they are invited to register.
The invited user clicks the email link and is redirected to the profile completion page. Validation is performed, and if it fails, the user is redirected to the profile completion page.
The user is created and the web hook API is called. If everything is performed successfully, the invitation flow ends and the invitor receives a confirmation email informing them that the invited user accepted the request.
To start the invitation flow, the invitor must be logged in. The following headers are needed:
- Authorization (Bearer token)
- tenant
- brand
The following section describes the complex invitation flow:
-
Start the invitation flow: https://www.ongo.com/bikes/workflowapi/process/?type=invitation_complex.
- body:
- response:
json { "configurationName": "invitation_user_task", "processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5", "data": { "invitation_user_task": {} }, "errors": {} }
-
Accept email, language, and personal note task: https://www.ongo.com/bikes/workflowapi/process/357c183c-bcee-4ac6-8da9-f4568f7a95c5.
- body:
{
"email": "doom.guy@bfgdivision.com",
"language": "en_GB",
"note": "this is my note for you",
"machineCode": "l33t",
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5"
}
b. response:
{
"configurationName": "email_confirm_step",
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5",
"data": {
"email_confirm_step": {}
},
"errors": {}
}
- Email step (invited user clicks email)
- body:
{
"user": {
"token": "0238cbe4-6f2f-47e9-b51e-b4851db0cedd"
},
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5"
}
b. response:
{
"configurationName": "complete_profile_user_task",
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5",
"data": {
"complete_profile_user_task": {}
},
"errors": {}
}
- Complete profile step:
- body:
{
"user": {
"emails": "doom.guy@bfgdivision.com",
"familyName": "A",
"givenName": "B",
"middleName": "C",
"salutation": "Mr",
"password": "totallysecurepwd",
"phoneNumbers": "+31611111111",
"country": "nl"
},
"consentData": {
"documents": [
"1",
"6"
]
},
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5"
}
b. response:
{
"configurationName": "thanks",
"processToken": "357c183c-bcee-4ac6-8da9-f4568f7a95c5",
"data": {
"thanks": {}
},
"errors": {}
}
- Send an email to the inviter informing them that the user accepted their invitation.
Time-based one-time password
There is a sample of how to configure Time-based One-Time Password (TOTP) enrollment. The location of individual configurations is: src/main/resources/totp
The user enrolls a new TOTP device, and after the process is completed, the user is automatically logged in.
Social registration flow
Ending already existing processes
When a new process is created, and another one exists for the same user, the old process must be deleted. Deleting old processes is necessary because of timers for certain tasks (like deleting the user if they do not complete the process), to avoid cluttering the memory with useless, suspended processes.
The workflow API listens to a queue named end-workflow-process.queue
for messages that contain a processId
and deletes the process with the respective processId
.
When a process is started for a user, it deletes old tokens for the same process type and user. The SSUI microservice deletes the tokens and publishes a message containing the processId
to the end-workflow-process.queue
. The listener (EndProcessListener.java
) in the Workflow API picks up this message and deletes the process.
Example: activation process
- In the activation process BPMN, the following task deletes the old tokens:
<serviceTask id="delete_unused_user_token_service" name="delete_unused_user_token_step" activiti:delegateExpression="${deleteUnusedTokensTask}">
<extensionElements>
<activiti:field name="tokenType" stringValue="ACTIVATION_TOKEN"/>
</extensionElements>
</serviceTask>
-
The SSUI microservice deletes the process token and publishes the process event to end the old activation process.
-
The Workflow API deletes the old activation process.