Skip to main content

Design Customizations

Variable Assistant

Introduction

Flowable Design has a variable assistant and an expression builder that is used to help modelers. This assistant allows modelers to have easy access (through auto complete) to the defined variables in the scope. In addition to that it also exposes different functions and beans that are available in the context. e.g. flwStringUtils (the Flowable String Utils) in BPMN / CMMN or flw in Forms / Pages.

There are 3 types of definitions:

  • Function definitions - These are definitions that are exposing the available FlowableFunctionDelegate(s)
  • Bean Definitions - The beans that are available in the context (e.g. flw in Forms / Pages and authenticatedUserId / flwStringUtils in BPMN / CMMN)
  • Type Definitions - The definitions of the different definition types

Expose through resources

The definitions can be exposed by providing a json resource in a specific location. The resources need to be placed on the classpath in the com/flowable/design/expressions/custom folder and each have to end with a specific suffix.

  • Function definitions - file ending in .function.delegates.json e.g. myCustom.function.delegates.json. The JSON schema definition for this is located here
  • Bean definitions - file ending in .beans.json e.g. myCustom.beans.json. The JSON schema definition for this is located here
  • Type definitions - file ending in .types.json e.g. myCustom.types.json. The JSON schema definition for this is located here

App Publish Events

Introduction

In certain use cases, it's needed to execute custom logic when an app is published to a runtime system. The following section describes how this can be done.

Implementation

Listening to app publish events can be done using an event listener implementation that looks similar to the event listener on the various Flowable engines.

An implementation in Java of the org.flowable.common.engine.api.delegate.event.FlowableEventListener needs to be added to the classpath as a Spring bean. There also is the org.flowable.common.engine.api.delegate.event.AbstractFlowableEventListener which has a default implementation for the transaction handling (which is only relevant in some very specific situations).

An implementation could look lik this:

public class CustomAppPublishListener extends AbstractFlowableEventListener {

@Override
public boolean isFailOnException() {
return false;
}

@Override
public Collection<? extends FlowableEventType> getTypes() {
return Collections.singleton(DesignEventType.APP_PUBLISHED);
}

@Override
public void onEvent(FlowableEvent event) {
if (event instanceof AppPublishedEvent) {
appPublished((AppPublishedEvent) event);
}
}

protected void appPublished(AppPublishedEvent event) {
// Write your custom logic with the event here
}
}

Listening to the com.flowable.design.engine.api.event.DesignEventType.APP_PUBLISHED event will result in receiving an instance of com.flowable.design.engine.api.event.AppPublishedEvent which contains

  • The app model.
  • The content of the app in bytes.
  • The url to which this app is deployed to (which is useful when having multiple deployment targets configured).
  • Information about the app revision, which contains app key, app name, tenantId, workspaceKey, etc.

There also is the com.flowable.design.engine.api.event.DesignEventType.APP_REVISION_CREATED event type, which will result in an instance of com.flowable.design.engine.api.event.AppRevisionCreatedEvent. This event will be fired whenever a new revision is created in Design and contains the app model and the revision information.

Note that new app revisions can be created during app publishing, which means that you'll received both the events corresponding with the APP_REVISION_CREATED event and the APP_PUBLISHED.

Security configuration

Application Security Configuration

An example security configuration for basic authentication looks like:

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;

import jakarta.servlet.DispatcherType;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;

import com.flowable.autoconfigure.design.security.DesignHttpSecurityCustomizer;
import com.flowable.design.security.spring.web.authentication.AjaxAuthenticationFailureHandler;
import com.flowable.design.security.spring.web.authentication.AjaxAuthenticationSuccessHandler;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
@Order(10)
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<DesignHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
for (DesignHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream().toList()) {
customizer.customize(http);
}

http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.logout(logout -> logout
.logoutUrl("/auth/logout")
.logoutSuccessUrl("/")
);

// Non authenticated exception handling. The formLogin and httpBasic configure the exceptionHandling
// We have to initialize the exception handling with a default authentication entry point in order to return 401 each time and not have a
// forward due to the formLogin or the http basic popup due to the httpBasic
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.defaultAuthenticationEntryPointFor((request, response, authException) -> {}, new DispatcherTypeRequestMatcher(DispatcherType.ERROR))
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AnyRequestMatcher.INSTANCE))
.formLogin(formLogin -> formLogin
.loginProcessingUrl("/auth/login")
.successHandler(new AjaxAuthenticationSuccessHandler())
.failureHandler(new AjaxAuthenticationFailureHandler())
)
.authorizeHttpRequests(configurer -> configurer
// allow context root for all (it triggers the loading of the initial page)
.requestMatchers(antMatcher("/")).permitAll()
.requestMatchers(
antMatcher("/**/*.svg"), antMatcher("/**/*.ico"), antMatcher("/**/*.png"), antMatcher("/**/*.woff2"), antMatcher("/**/*.css"),
antMatcher("/**/*.woff"), antMatcher("/**/*.html"), antMatcher("/**/*.js"),
antMatcher("/**/flowable-frontend-configuration"),
antMatcher("/**/index.html")).permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());

return http.build();
}
}
note

By default, a remember-me cookie is used and no session context stored. If you wish to disable the remember-me cookie feature, you will need to provide a session security context repository.

Actuator Security Configuration

An example security configuration for basic authentication for the actuator endpoints looks like:

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

import com.flowable.design.actuate.autoconfigure.security.servlet.ActuatorRequestMatcher;
import com.flowable.design.engine.api.idm.DesignPrivileges;

@Configuration
public class SecurityActuatorConfiguration {

@Bean
@Order(6) // Actuator configuration should kick in before the Form Login there should always be HTTP basic for the endpoints
public SecurityFilterChain basicActuatorSecurity(HttpSecurity http) throws Exception {

http
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(AbstractHttpConfigurer::disable);

http
.securityMatcher(new ActuatorRequestMatcher())
.authorizeHttpRequests(configurer -> configurer
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAuthority(DesignPrivileges.ACCESS_ADMIN)
.anyRequest().denyAll()
)
.httpBasic(Customizer.withDefaults());

return http.build();
}
}