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 andauthenticatedUserId
/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();
}
}
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();
}
}