Hello,
I would like to ensure that users of the rest api can only receive tasks that they have permission to see. I.e. if a user has an authority “sales”, then they will only see talks with an identitylink for the corresponding candidateGroup when accessing the REST API: /flowable-rest/process-api/runtime/tasks
I have got this working using AbstractCommandInterceptor
but this seems too low level, and I guess there is a better way. Could someone point me how to do this “properly”.
Here is what I have:
ublic class CustomTaskCommandInterceptor extends AbstractCommandInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomTaskCommandInterceptor.class);
private static final ThreadLocal<Boolean> isProcessing = ThreadLocal.withInitial(() -> false);
@Override
public <T> T execute(CommandConfig config, Command<T> command, CommandExecutor commandExecutor) {
if (isProcessing.get()) {
// to avoid recursion... :(
return next.execute(config, command, commandExecutor);
}
try {
isProcessing.set(true);
LOGGER.info("Executing Task Command Interceptor");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
LOGGER.warn("User is not authenticated");
return next.execute(config, command, commandExecutor);
}
String username = authentication.getName();
List<String> userGroups = extractGroupsFromAuthorities(authentication.getAuthorities());
if (isTaskQueryCommand(command)) {
TaskQuery taskQuery = (TaskQuery) command;
taskQuery.or()
.taskCandidateGroupIn(userGroups)
.taskAssignee(username)
.taskInvolvedUser(username)
.endOr();
LOGGER.debug("Modified TaskQuery for user: {}", username);
}
return next.execute(config, command, commandExecutor);
} finally {
isProcessing.set(false);
}
}
private List<String> extractGroupsFromAuthorities(Collection<? extends GrantedAuthority> authorities) {
return authorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
}
private boolean isTaskQueryCommand(Command<?> command) {
String commandClassName = command.getClass().getSimpleName();
return "TaskQueryImpl".equals(commandClassName) || "GetTasksCmd".equals(commandClassName) || "CountTasksCmd".equals(commandClassName);
}
}
In my SecurityConfig I register the interceptor like so:
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> customProcessEngineConfigurer() {
return processEngineConfiguration -> {
logger.info("Configuring custom pre-command interceptors");
ProcessEngineConfigurationImpl configImpl = (ProcessEngineConfigurationImpl) processEngineConfiguration;
CommandInterceptor customInterceptor = new CustomTaskCommandInterceptor();
if (configImpl.getCustomPreCommandInterceptors() == null) {
configImpl.setCustomPreCommandInterceptors(new ArrayList<>());
}
configImpl.getCustomPreCommandInterceptors().add(customInterceptor);
};
}
It might be relevant to say that I am reading the user/groups from a JWT
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
logger.info("Configuring SecurityFilterChain");
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new LoggingGrantedAuthoritiesConverter());
return converter;
}
where the LoggingGrantedAuthoritiesConverter
reads the JWT and adds roles to the grantedAuthorities.