All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.predic8.membrane.core.interceptor.apimanagement.ApiManagementInterceptor Maven / Gradle / Ivy

There is a newer version: 5.6.0
Show newest version
/*
 * 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;

@MCElement(name = "apiManagement")
public class ApiManagementInterceptor extends AbstractInterceptor {

    public static final String APPLICATION_JSON = "application/json"; // @TODO use constant somewhere else
    private static 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 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy