blob: 38251dcd66b440c76403451b5e0670e19e138696 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.sra;
import io.netty.channel.unix.Errors.NativeIoException;
import java.net.ConnectException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.to.SRARouteTO;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
@Component
@Order(-2)
public class SyncopeSRAWebExceptionHandler implements WebExceptionHandler, ApplicationListener<RefreshRoutesEvent> {
private static final Logger LOG = LoggerFactory.getLogger(SyncopeSRAWebExceptionHandler.class);
private static final Map<String, Optional<URI>> CACHE = new ConcurrentHashMap<>();
@Autowired
private RouteProvider routeProvider;
@Autowired
private SRAProperties props;
@Override
public void onApplicationEvent(final RefreshRoutesEvent event) {
CACHE.clear();
}
private URI getError(final ServerWebExchange exchange) {
URI error = props.getGlobal().getError();
String routeId = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
if (StringUtils.isNotBlank(routeId)) {
Optional<URI> routeError = Optional.ofNullable(CACHE.get(routeId)).orElseGet(() -> {
URI uri = null;
Optional<SRARouteTO> route = routeProvider.getRouteTOs().stream().
filter(r -> routeId.equals(r.getKey())).findFirst();
if (route.isPresent()) {
uri = route.get().getError();
}
return CACHE.put(routeId, Optional.ofNullable(uri));
});
if (routeError.isPresent()) {
error = routeError.get();
}
}
return error;
}
private boolean acceptsTextHtml(final ServerHttpRequest request) {
try {
List<MediaType> acceptedMediaTypes = request.getHeaders().getAccept();
acceptedMediaTypes.remove(MediaType.ALL);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream().anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
} catch (InvalidMediaTypeException e) {
LOG.debug("Unexpected exception", e);
return false;
}
}
private Mono<Void> doHandle(final ServerWebExchange exchange, final Throwable throwable, final HttpStatus status) {
try {
if (acceptsTextHtml(exchange.getRequest())) {
exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
URI error = getError(exchange);
exchange.getResponse().getHeaders().add(HttpHeaders.LOCATION, error.toASCIIString());
} else {
exchange.getResponse().setStatusCode(status);
exchange.getResponse().getHeaders().add(
RESTHeaders.ERROR_CODE, HttpStatus.NOT_FOUND.toString());
exchange.getResponse().getHeaders().add(
RESTHeaders.ERROR_INFO, throwable.getMessage().replace("\n", " "));
}
} catch (UnsupportedOperationException e) {
LOG.debug("Could not perform, ignoring", e);
}
return exchange.getResponse().setComplete();
}
@Override
public Mono<Void> handle(final ServerWebExchange exchange, final Throwable throwable) {
if (throwable instanceof ConnectException
|| throwable instanceof NativeIoException
|| throwable instanceof NotFoundException) {
LOG.error("ConnectException thrown", throwable);
return doHandle(exchange, throwable, HttpStatus.NOT_FOUND);
} else if (throwable instanceof OAuth2AuthorizationException) {
LOG.error("OAuth2AuthorizationException thrown", throwable);
return doHandle(exchange, throwable, HttpStatus.INTERNAL_SERVER_ERROR);
}
return Mono.error(throwable);
}
}