com.predic8.membrane.core.interceptor.apimanagement.ApiManagementInterceptor Maven / Gradle / Ivy
/*
* Copyright 2015 predic8 GmbH, www.predic8.com
* Licensed 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 com.predic8.membrane.core.interceptor.apimanagement;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.predic8.membrane.annot.MCAttribute;
import com.predic8.membrane.annot.MCChildElement;
import com.predic8.membrane.annot.MCElement;
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.http.Response;
import com.predic8.membrane.core.interceptor.AbstractInterceptor;
import com.predic8.membrane.core.interceptor.Interceptor;
import com.predic8.membrane.core.interceptor.Outcome;
import com.predic8.membrane.core.interceptor.administration.AdminConsoleInterceptor;
import com.predic8.membrane.core.interceptor.apimanagement.apiconfig.ApiConfig;
import com.predic8.membrane.core.interceptor.apimanagement.apiconfig.EtcdRegistryApiConfig;
import com.predic8.membrane.core.interceptor.apimanagement.apiconfig.SimpleApiConfig;
import com.predic8.membrane.core.interceptor.apimanagement.policy.Policy;
import com.predic8.membrane.core.interceptor.apimanagement.quota.AMQuota;
import com.predic8.membrane.core.interceptor.apimanagement.rateLimiter.AMRateLimiter;
import com.predic8.membrane.core.interceptor.apimanagement.statistics.AMStatisticsCollector;
import com.predic8.membrane.core.rules.AbstractServiceProxy;
import com.predic8.membrane.core.rules.Rule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Map;
import static com.predic8.membrane.core.http.MimeType.APPLICATION_JSON;
@MCElement(name = "apiManagement")
public class ApiManagementInterceptor extends AbstractInterceptor {
private static final Logger log = LoggerFactory.getLogger(ApiManagementInterceptor.class);
private ApiConfig apiConfig;
private AMRateLimiter amRateLimiter = null;
private AMQuota amQuota = null;
private AMStatisticsCollector amStatisticsCollector;
private ApiManagementConfiguration apiManagementConfiguration = null;
private String config = "api.yaml";
private final ApiKeyRetriever apiKeyRetriever = new HeaderKeyRetriever();
@Override
public void init(Router router) throws Exception {
super.init(router);
Map apiConfigs = router.getBeanFactory().getBeansOfType(ApiConfig.class);
String etcdRegistryApiConfigCorrected = getCorrectedName(EtcdRegistryApiConfig.class.getSimpleName());
String simpleApiConfigCorrected = getCorrectedName(SimpleApiConfig.class.getSimpleName());
if (apiConfigs.containsKey(etcdRegistryApiConfigCorrected)) {
apiConfig = apiConfigs.get(etcdRegistryApiConfigCorrected);
} else if (apiConfigs.containsKey(simpleApiConfigCorrected)) {
apiConfig = apiConfigs.get(simpleApiConfigCorrected);
}
if (apiConfig != null) {
log.info("used apiConfig: " + apiConfig.getClass().getSimpleName());
apiManagementConfiguration = apiConfig.getConfiguration();
} else {
log.info("No ApiConfig set. Using default");
apiManagementConfiguration = new ApiManagementConfiguration(router.getBaseLocation(), config);
}
addInterceptors();
}
private void addInterceptors() {
StringBuilder nameBuilder = new StringBuilder();
nameBuilder.append("Api Management Interceptor");
ArrayList interceptors = new ArrayList<>();
nameBuilder.append(" { ");
if (amRateLimiter != null) {
amRateLimiter.setAmc(apiManagementConfiguration);
interceptors.add("RateLimiter");
}
if (amQuota != null) {
amQuota.setAmc(apiManagementConfiguration);
interceptors.add("Quota");
}
//temp
interceptors.add("Statistics");
if (interceptors.size() > 0) {
nameBuilder.append(interceptors.get(0));
for (int i = 1; i < interceptors.size(); i++) {
nameBuilder.append(", ").append(interceptors.get(i));
}
}
nameBuilder.append(" }");
this.name = nameBuilder.toString();
}
private String getCorrectedName(String etcdRegistryApiConfig) {
return etcdRegistryApiConfig.substring(0, 1).toLowerCase() + etcdRegistryApiConfig.substring(1);
}
@Override
public Outcome handleRequest(Exchange exc) throws Exception {
if(getAmStatisticsCollector() != null)
return getAmStatisticsCollector().handleRequest(exc,handleRequest2(exc) );
return handleRequest2(exc);
}
private Outcome handleRequest2(Exchange exc) throws Exception {
String key = getAndRemoveKey(exc);
if (key == null || !apiManagementConfiguration.getKeys().containsKey(key) || isAdminConsoleCall(exc)) {
return processUnauthorizedRequest(exc);
}
exc.setProperty(Exchange.API_KEY, key);
AuthorizationResult auth = getAuthorization(exc, key);
if (auth.isAuthorized()) {
return processAuthorizedRequest(exc);
}
setResponsePolicyDenied(exc, auth);
return Outcome.RETURN;
}
private boolean keyExists(String key) {
return apiManagementConfiguration.getKeys().containsKey(key);
}
private Outcome processAuthorizedRequest(Exchange exc) throws Exception {
if (getAmRateLimiter() != null && getAmRateLimiter().handleRequest(exc) == Outcome.RETURN) {
return Outcome.RETURN;
}
if (getAmQuota() != null && getAmQuota().handleRequest(exc) == Outcome.RETURN) {
return Outcome.RETURN;
}
return Outcome.CONTINUE;
}
private Outcome processUnauthorizedRequest(Exchange exc) throws Exception {
String ip = exc.getRemoteAddrIp();
exc.setProperty(Exchange.API_KEY, ip);
apiManagementConfiguration.addIpAsApiKeyIfNeeded(ip);
if(isAdminConsoleCall(exc))
return Outcome.CONTINUE;
if(hasUnauthorizedPolicy(exc))
return processAuthorizedRequest(exc);
setResponseNoAuthKey(exc);
return Outcome.RETURN;
}
private String getAndRemoveKey(Exchange exc) {
String key = apiKeyRetriever.getKey(exc);
apiKeyRetriever.removeKey(exc);
return key;
}
private boolean isAdminConsoleCall(Exchange exc) {
for (Rule r : router.getRuleManager().getRules()) {
if (!(r instanceof AbstractServiceProxy)) continue;
for (Interceptor i : r.getInterceptors()) {
if (i instanceof AdminConsoleInterceptor) {
for(Interceptor interceptor : exc.getRule().getInterceptors()){
if(interceptor == i)
return true;
}
}
}
}
return false;
}
@Override
public Outcome handleResponse(Exchange exc) throws Exception {
if(getAmStatisticsCollector() != null)
return getAmStatisticsCollector().handleResponse(exc, handleResponse2(exc));
return handleResponse2(exc);
}
private Outcome handleResponse2(Exchange exc) {
if (!(hasUnauthorizedPolicy(exc) || isAdminConsoleCall(exc)) && getAmQuota() != null) {
getAmQuota().handleResponse(exc);
}
return Outcome.CONTINUE;
}
private boolean hasUnauthorizedPolicy(Exchange exc) {
String requestedServiceProxy = exc.getRule().getName();
for(Policy pol : apiManagementConfiguration.getPolicies().values())
if(pol.getServiceProxies().contains(requestedServiceProxy) && pol.isUnauthenticated())
return true;
return false;
}
public AuthorizationResult getAuthorization(Exchange exc, String apiKey) {
if (!keyExists(apiKey))
return AuthorizationResult.getAuthorizedFalse("API key not found");
if(keyIsExpired(apiKey))
return AuthorizationResult.getAuthorizedFalse("API key expired");
for (Policy policy : getKey(apiKey).getPolicies()) {
if (policy.getServiceProxies().contains(exc.getRule().getName())) {
return AuthorizationResult.getAuthorizedTrue();
}
}
return AuthorizationResult.getAuthorizedFalse("Service not available: " + exc.getRule().getName());
}
private boolean keyIsExpired(String apiKey) {
if(getKey(apiKey).getExpiration() == null)
return false;
if(getKey(apiKey).getExpiration().isBefore(Instant.now()))
return true;
return false;
}
private Key getKey(String apiKey) {
return apiManagementConfiguration.getKeys().get(apiKey);
}
private byte[] buildJsonErrorMessage(Response res) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
JsonGenerator jgen;
try {
jgen = new JsonFactory().createGenerator(os);
jgen.writeStartObject();
jgen.writeObjectField("Statuscode", res.getStatusCode());
jgen.writeObjectField("Message", res.getStatusMessage());
jgen.writeEndObject();
jgen.close();
} catch (IOException e) {
e.printStackTrace();
}
return os.toByteArray();
}
private void setResponsePolicyDenied(Exchange exc, AuthorizationResult auth) {
setErrorResponse(exc, Response.badRequest(auth.getReason()));
}
private void setResponseNoAuthKey(Exchange exc) {
setErrorResponse(exc, Response.unauthorized());
}
private void setErrorResponse(Exchange exc, Response.ResponseBuilder builder) {
Response res = builder.contentType(APPLICATION_JSON).build();
res.setBodyContent(buildJsonErrorMessage(res));
exc.setResponse(res);
}
public AMRateLimiter getAmRateLimiter() {
return amRateLimiter;
}
@MCChildElement(order = 0)
public void setAmRateLimiter(AMRateLimiter amRateLimiter) {
this.amRateLimiter = amRateLimiter;
}
public AMQuota getAmQuota() {
return amQuota;
}
@MCChildElement(order = 1)
public void setAmQuota(AMQuota amQuota) {
this.amQuota = amQuota;
}
public String getConfig() {
return config;
}
/**
* @description the location of the configuration
* @default api.yaml
*/
@MCAttribute
public void setConfig(String config) {
this.config = config;
}
public AMStatisticsCollector getAmStatisticsCollector() {
return amStatisticsCollector;
}
@MCChildElement(order = 2)
public void setAmStatisticsCollector(AMStatisticsCollector amStatisticsCollector) {
this.amStatisticsCollector = amStatisticsCollector;
}
}