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

io.gravitee.gateway.handlers.api.services.SubscriptionCacheService Maven / Gradle / Ivy

There is a newer version: 4.5.5
Show newest version
/*
 * Copyright © 2015 The Gravitee team (http://gravitee.io)
 *
 * 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 io.gravitee.gateway.handlers.api.services;

import static io.gravitee.repository.management.model.Subscription.Status.ACCEPTED;

import io.gravitee.gateway.api.service.ApiKeyService;
import io.gravitee.gateway.api.service.Subscription;
import io.gravitee.gateway.api.service.SubscriptionService;
import io.gravitee.gateway.handlers.api.manager.ApiManager;
import io.gravitee.gateway.reactive.api.policy.SecurityToken;
import io.gravitee.gateway.reactive.handlers.api.v4.Api;
import io.gravitee.gateway.reactor.ReactableApi;
import io.gravitee.gateway.security.core.SubscriptionTrustStoreLoaderManager;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class SubscriptionCacheService implements SubscriptionService {

    private final ApiKeyService apiKeyService;
    private final SubscriptionTrustStoreLoaderManager subscriptionTrustStoreLoaderManager;
    private final ApiManager apiManager;

    // Caches only contains active subscriptions
    private final Map cacheByApiClientId = new ConcurrentHashMap<>();
    private final Map cacheByApiClientCertificate = new ConcurrentHashMap<>();
    private final Map cacheBySubscriptionId = new ConcurrentHashMap<>();
    private final Map> cacheByApiId = new ConcurrentHashMap<>();

    @Override
    public Optional getByApiAndSecurityToken(String api, SecurityToken securityToken, String plan) {
        return switch (SecurityToken.TokenType.valueOfOrNone(securityToken.getTokenType())) {
            case API_KEY -> apiKeyService
                .getByApiAndKey(api, securityToken.getTokenValue())
                .flatMap(apiKey -> getById(apiKey.getSubscription()));
            case CLIENT_ID -> getByApiAndClientIdAndPlan(api, securityToken.getTokenValue(), plan);
            case CERTIFICATE -> subscriptionTrustStoreLoaderManager.getByCertificate(api, securityToken.getTokenValue(), plan);
            default -> Optional.empty();
        };
    }

    @Override
    public Optional getByApiAndClientIdAndPlan(String api, String clientId, String plan) {
        return Optional.ofNullable(cacheByApiClientId.get(buildCacheKeyFromClientInfo(api, clientId, plan)));
    }

    @Override
    public Optional getById(String subscriptionId) {
        return Optional.ofNullable(cacheBySubscriptionId.get(subscriptionId));
    }

    @Override
    public void register(final Subscription subscription) {
        if (ACCEPTED.name().equals(subscription.getStatus())) {
            if (subscription.getClientCertificate() != null) {
                registerFromClientCertificate(subscription);
            } else if (subscription.getClientId() != null) {
                registerFromClientId(subscription);
            } else {
                registerFromId(subscription);
            }
        } else {
            unregister(subscription);
        }
    }

    private void registerFromClientCertificate(final Subscription subscription) {
        final String idKey = subscription.getId();
        final String clientCertificateKey = buildClientCertificateCacheKey(subscription);
        // Index the subscription without plan id to allow search without plan criteria.
        final String clientCertificateKeyWithoutPlan = buildCacheKeyFromClientInfo(
            subscription.getApi(),
            subscription.getClientCertificate(),
            null
        );

        Subscription cachedSubscription = cacheBySubscriptionId.get(idKey);

        // remove previous subscription client_id from cache if client_id has changed
        if (
            cachedSubscription != null &&
            cachedSubscription.getClientCertificate() != null &&
            !cachedSubscription.getClientCertificate().equals(subscription.getClientCertificate())
        ) {
            unregisterFromClientCertificate(cachedSubscription);
        }

        log.debug(
            "Load accepted subscription with client Id  [id: {}] [api: {}] [plan: {}] [application: {}]",
            subscription.getId(),
            subscription.getApi(),
            subscription.getPlan(),
            subscription.getApi()
        );
        final Set servers = extractApiServersId(subscription);
        subscriptionTrustStoreLoaderManager.registerSubscription(subscription, servers);
        // Update subscription
        cacheBySubscriptionId.put(idKey, subscription);
        addKeyForApi(subscription.getApi(), idKey);
        // Put new client_id
        cacheByApiClientCertificate.put(clientCertificateKey, subscription);
        addKeyForApi(subscription.getApi(), clientCertificateKey);
        cacheByApiClientCertificate.put(clientCertificateKeyWithoutPlan, subscription);
        addKeyForApi(subscription.getApi(), clientCertificateKeyWithoutPlan);
    }

    private void registerFromClientId(final Subscription subscription) {
        final String idKey = subscription.getId();
        final String clientIdKey = buildClientIdCacheKey(subscription);
        // Index the subscription without plan id to allow search without plan criteria.
        final String clientIdKeyWithoutPlan = buildCacheKeyFromClientInfo(subscription.getApi(), subscription.getClientId(), null);

        Subscription cachedSubscription = cacheBySubscriptionId.get(idKey);

        // remove previous subscription client_id from cache if client_id has changed
        if (
            cachedSubscription != null &&
            cachedSubscription.getClientId() != null &&
            !cachedSubscription.getClientId().equals(subscription.getClientId())
        ) {
            unregisterFromClientId(cachedSubscription);
        }

        log.debug(
            "Load accepted subscription with client Id  [id: {}] [api: {}] [plan: {}] [application: {}]",
            subscription.getId(),
            subscription.getApi(),
            subscription.getPlan(),
            subscription.getApi()
        );
        // Update subscription
        cacheBySubscriptionId.put(idKey, subscription);
        addKeyForApi(subscription.getApi(), idKey);
        // Put new client_id
        cacheByApiClientId.put(clientIdKey, subscription);
        addKeyForApi(subscription.getApi(), clientIdKey);
        cacheByApiClientId.put(clientIdKeyWithoutPlan, subscription);
        addKeyForApi(subscription.getApi(), clientIdKeyWithoutPlan);
    }

    private void registerFromId(final Subscription subscription) {
        String cacheKey = subscription.getId();
        log.debug(
            "Load accepted subscription [id: {}] [api: {}] [plan: {}] [application: {}]",
            subscription.getId(),
            subscription.getApi(),
            subscription.getPlan(),
            subscription.getApi()
        );
        cacheBySubscriptionId.put(cacheKey, subscription);
        addKeyForApi(subscription.getApi(), cacheKey);
    }

    private void addKeyForApi(final String apiId, final String cacheKey) {
        Set subscriptionsByApi = cacheByApiId.get(apiId);
        if (subscriptionsByApi == null) {
            subscriptionsByApi = new HashSet<>();
        }
        subscriptionsByApi.add(cacheKey);
        cacheByApiId.put(apiId, subscriptionsByApi);
    }

    private void removeKeyForApi(final String apiId, final String cacheKey) {
        Set keysByApi = cacheByApiId.get(apiId);
        if (keysByApi != null && keysByApi.remove(cacheKey)) {
            if (keysByApi.isEmpty()) {
                cacheByApiId.remove(apiId);
            } else {
                cacheByApiId.put(apiId, keysByApi);
            }
        }
    }

    @Override
    public void unregister(final Subscription subscription) {
        log.debug(
            "Unload subscription [id: {}] [api: {}] [plan: {}] [application: {}]",
            subscription.getId(),
            subscription.getApi(),
            subscription.getPlan(),
            subscription.getApi()
        );
        final String idKey = subscription.getId();
        Subscription removeSubscription = cacheBySubscriptionId.remove(idKey);
        if (removeSubscription != null) {
            removeKeyForApi(subscription.getApi(), idKey);
            unregisterFromClientId(removeSubscription);
            unregisterFromClientCertificate(removeSubscription);
        }
        // In case new one has different client id than the one in cache
        unregisterFromClientId(subscription);
        unregisterFromClientCertificate(subscription);
    }

    private void unregisterFromClientId(final Subscription subscription) {
        if (subscription.getClientId() != null) {
            final String clientIdKey = buildClientIdCacheKey(subscription);
            if (cacheByApiClientId.remove(clientIdKey) != null) {
                removeKeyForApi(subscription.getApi(), clientIdKey);
            }
            final String clientIdKeyWithoutPlan = buildCacheKeyFromClientInfo(subscription.getApi(), subscription.getClientId(), null);
            if (cacheByApiClientId.remove(clientIdKeyWithoutPlan) != null) {
                removeKeyForApi(subscription.getApi(), clientIdKeyWithoutPlan);
            }
        }
    }

    private void unregisterFromClientCertificate(final Subscription subscription) {
        if (subscription.getClientCertificate() != null) {
            final String clientCertificateKey = buildClientCertificateCacheKey(subscription);
            subscriptionTrustStoreLoaderManager.unregisterSubscription(subscription);
            if (cacheByApiClientCertificate.remove(clientCertificateKey) != null) {
                removeKeyForApi(subscription.getApi(), clientCertificateKey);
            }
            final String clientCertificateKeyWithoutPlan = buildCacheKeyFromClientInfo(
                subscription.getApi(),
                subscription.getClientCertificate(),
                null
            );
            if (cacheByApiClientCertificate.remove(clientCertificateKeyWithoutPlan) != null) {
                removeKeyForApi(subscription.getApi(), clientCertificateKeyWithoutPlan);
            }
        }
    }

    @Override
    public void unregisterByApiId(final String apiId) {
        log.debug("Unload all subscriptions by api [api_id: {}]", apiId);
        Set subscriptionsByApi = cacheByApiId.remove(apiId);
        if (subscriptionsByApi != null) {
            subscriptionsByApi.forEach(cacheKey -> {
                Subscription subscription = cacheBySubscriptionId.remove(cacheKey);
                if (subscription != null) {
                    log.debug(
                        "Unload subscription [id: {}] [api: {}] [plan: {}] [application: {}]",
                        subscription.getId(),
                        subscription.getApi(),
                        subscription.getPlan(),
                        subscription.getApi()
                    );
                }
                cacheByApiClientId.remove(cacheKey);
            });
        }
    }

    String buildClientCertificateCacheKey(Subscription subscription) {
        return buildCacheKeyFromClientInfo(subscription.getApi(), subscription.getClientCertificate(), subscription.getPlan());
    }

    String buildClientIdCacheKey(Subscription subscription) {
        return buildCacheKeyFromClientInfo(subscription.getApi(), subscription.getClientId(), subscription.getPlan());
    }

    String buildCacheKeyFromClientInfo(String api, String clientIdOrCertificate, String plan) {
        return String.format("%s.%s.%s", api, clientIdOrCertificate, plan);
    }

    private Set extractApiServersId(Subscription subscription) {
        final ReactableApi reactableApi = apiManager.get(subscription.getApi());
        final Set servers;
        if (reactableApi instanceof Api api) {
            servers =
                api
                    .getDefinition()
                    .getListeners()
                    .stream()
                    .flatMap(l -> l.getServers() != null ? l.getServers().stream() : Stream.empty())
                    .collect(Collectors.toSet());
        } else {
            servers = Set.of();
            if (reactableApi == null) {
                log.debug("API {} not found, deploying subscription {} on every servers", subscription.getApi(), subscription.getId());
            } else {
                log.debug("V2 APIs do not support subscription using client certificate");
            }
        }
        return servers;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy