RED-9147: Deny websocket subscription between different tenants #496

Merged
dominique.eiflaender1 merged 1 commits from RED-9147 into master 2024-05-23 14:20:28 +02:00

View File

@ -0,0 +1,88 @@
package com.iqser.red.service.persistence.management.v1.processor.configuration;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import com.knecon.fforesight.keycloakcommons.security.TokenUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Value("${cors.enabled:false}")
private boolean corsEnabled;
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.simpTypeMatchers(SimpMessageType.HEARTBEAT, SimpMessageType.UNSUBSCRIBE, SimpMessageType.DISCONNECT)
.permitAll()
.simpTypeMatchers(SimpMessageType.CONNECT)
.anonymous() // this is intended, see WebSocketConfiguration.configureClientInboundChannel
.simpTypeMatchers(SimpMessageType.SUBSCRIBE)
.access("@tenantWebSocketSecurityMatcher.checkCanSubscribeTo(authentication,message)")
.anyMessage()
.denyAll();
}
@Override
protected boolean sameOriginDisabled() {
return corsEnabled;
}
@Bean
public TenantWebSocketSecurityMatcher tenantWebSocketSecurityMatcher() {
return new TenantWebSocketSecurityMatcher();
}
public class TenantWebSocketSecurityMatcher {
public boolean checkCanSubscribeTo(JwtAuthenticationToken authentication, Message<?> message) {
var targetedTenant = extractTenantId(message);
var currentTenant = getCurrentTenant(authentication);
return targetedTenant.isPresent() && currentTenant.isPresent() && currentTenant.get().equals(targetedTenant.get());
}
private Optional<String> getCurrentTenant(JwtAuthenticationToken authentication) {
if (authentication != null && authentication.getToken() != null && authentication.getToken().getTokenValue() != null) {
return Optional.of(TokenUtils.toTenant(authentication.getToken().getTokenValue()));
} else {
return Optional.empty();
}
}
}
private Optional<String> extractTenantId(Message<?> message) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(message);
String topic = sha.getDestination();
if (topic == null) {
return Optional.empty();
}
String tenant = topic.split("/")[2];
return Optional.of(tenant);
}
}