Commit a029a762 authored by LAVENIER's avatar LAVENIER
Browse files

[fix] Avoid to start subscriptions, when Pod configuration is not ready yet

[fix] Build GraphQL bean for subscription only once
parent f6507fc6
......@@ -23,6 +23,8 @@ package net.sumaris.server.http.graphql;
*/
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import graphql.execution.SubscriptionExecutionStrategy;
import graphql.schema.GraphQLSchema;
import io.leangen.graphql.GraphQLSchemaGenerator;
import io.leangen.graphql.metadata.strategy.query.AnnotatedResolverBuilder;
......@@ -201,5 +203,12 @@ public class GraphQLConfiguration implements WebSocketConfigurer {
public WebSocketHandler webSocketHandler() {
return new PerConnectionWebSocketHandler(SubscriptionWebSocketHandler.class);
}
@Bean
public GraphQL webSocketGraphQL() {
return GraphQL.newGraphQL(graphQLSchema())
.subscriptionExecutionStrategy(new SubscriptionExecutionStrategy())
.build();
}
}
......@@ -32,17 +32,26 @@ import graphql.GraphQL;
import graphql.execution.SubscriptionExecutionStrategy;
import graphql.schema.GraphQLSchema;
import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.event.config.ConfigurationEvent;
import net.sumaris.core.event.config.ConfigurationEventListener;
import net.sumaris.core.event.config.ConfigurationReadyEvent;
import net.sumaris.core.event.config.ConfigurationUpdatedEvent;
import net.sumaris.core.service.technical.ConfigurationService;
import net.sumaris.server.config.SumarisServerConfiguration;
import net.sumaris.server.exception.ErrorCodes;
import net.sumaris.server.http.security.AuthService;
import net.sumaris.server.util.security.AuthTokenVO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.ehcache.spi.service.ServiceConfiguration;
import org.nuiton.i18n.I18n;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
......@@ -53,11 +62,13 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class SubscriptionWebSocketHandler extends TextWebSocketHandler {
......@@ -68,6 +79,9 @@ public class SubscriptionWebSocketHandler extends TextWebSocketHandler {
private Map<String, List<Subscription>> subscriptionsBySessionId = Maps.newConcurrentMap();
private AtomicBoolean ready = new AtomicBoolean(false);
@Resource(name = "webSocketGraphQL")
private GraphQL graphQL;
@Autowired
......@@ -77,11 +91,23 @@ public class SubscriptionWebSocketHandler extends TextWebSocketHandler {
private AuthService authService;
@Autowired
public SubscriptionWebSocketHandler(GraphQLSchema graphQLSchema) {
public SubscriptionWebSocketHandler(ConfigurationService configuration) {
this.debug = log.isDebugEnabled();
this.graphQL = GraphQL.newGraphQL(graphQLSchema)
.subscriptionExecutionStrategy(new SubscriptionExecutionStrategy())
.build();
// When configuration not ready yet (e.g. APp try to connect BEFORE pod is really started)
if (!configuration.isReady()) {
// listen config ready event
configuration.addListener(new ConfigurationEventListener() {
@Override
public void onReady(ConfigurationReadyEvent event) {
configuration.removeListener(this);
SubscriptionWebSocketHandler.this.ready.set(true);
}
});
}
else {
ready.set(true);
}
}
@Override
......@@ -101,7 +127,7 @@ public class SubscriptionWebSocketHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
Map<String, Object> request;
Map<String, Object> request;
try {
request = objectMapper.readValue(message.asBytes(), Map.class);
if (debug) log.debug(I18n.t("sumaris.server.subscription.getRequest", request));
......@@ -132,6 +158,11 @@ public class SubscriptionWebSocketHandler extends TextWebSocketHandler {
/* -- protected methods -- */
protected void handleInitConnection(WebSocketSession session, Map<String, Object> request) {
// When not ready, force to stop the security chain
if (!this.ready.get()) {
throw new AuthenticationServiceException("Cannot authenticate: not ready");
}
Map<String, Object> payload = (Map<String, Object>) request.get("payload");
String token = MapUtils.getString(payload, "authToken");
......
......@@ -22,7 +22,12 @@ package net.sumaris.server.http.security;
* #L%
*/
import net.sumaris.core.event.config.ConfigurationEvent;
import net.sumaris.core.event.config.ConfigurationReadyEvent;
import net.sumaris.core.event.config.ConfigurationUpdatedEvent;
import net.sumaris.server.config.SumarisServerConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
......@@ -39,6 +44,7 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.apache.commons.lang3.StringUtils.removeStart;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
......@@ -51,17 +57,29 @@ public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter
private static final String TOKEN = "token";
private static final String BASIC = "Basic";
private boolean ready = false;
private SumarisServerConfiguration configuration;
private AtomicBoolean ready = new AtomicBoolean(false);
private boolean enableAuthBasic;
private boolean enableAuthToken;
protected AuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
protected AuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, SumarisServerConfiguration configuration) {
super(requiresAuthenticationRequestMatcher);
this.configuration = configuration;
}
public void setReady(boolean ready) {
this.ready = ready;
@EventListener({ConfigurationReadyEvent.class, ConfigurationUpdatedEvent.class})
protected void onConfigurationReady(ConfigurationEvent event) {
this.configuration = this.configuration != null ? this.configuration : SumarisServerConfiguration.getInstance();
// Update configuration
setEnableAuthBasic(configuration.enableAuthBasic());
setEnableAuthToken(configuration.enableAuthToken());
if (!this.ready.get()) {
this.ready.set(true);
}
}
public void setEnableAuthToken(boolean enableAuthToken) {
......@@ -75,8 +93,8 @@ public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// When not ready, always auth as anonymous
if (!this.ready) {
// When not ready, force to stop the security chain
if (!this.ready.get()) {
throw new AuthenticationServiceException("Cannot authenticate: not ready");
}
......
......@@ -129,20 +129,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
AuthenticationFilter restAuthenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS, configuration);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setEnableAuthBasic(configuration.enableAuthBasic());
filter.setEnableAuthToken(configuration.enableAuthToken());
filter.setReady(configurationService.isReady());
configurationService.addListener(new ConfigurationEventListener() {
@Override
public void onReady(ConfigurationReadyEvent event) {
filter.setEnableAuthBasic(configuration.enableAuthBasic());
filter.setEnableAuthToken(configuration.enableAuthToken());
filter.setReady(true);
}
});
return filter;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment