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

io.gravitee.gateway.handlers.api.security.JwtPlanBasedAuthenticationHandler Maven / Gradle / Ivy

There is a newer version: 4.5.5
Show newest version
/**
 * Copyright (C) 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.security;

import static io.gravitee.gateway.security.core.AuthenticationContext.ATTR_INTERNAL_LAST_SECURITY_HANDLER_SUPPORTING_SAME_TOKEN_TYPE;
import static io.gravitee.reporter.api.http.SecurityType.JWT;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.gravitee.definition.model.Plan;
import io.gravitee.gateway.security.core.AuthenticationContext;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.LazyJwtToken;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.SubscriptionRepository;
import io.gravitee.repository.management.api.search.SubscriptionCriteria;
import io.gravitee.repository.management.model.Subscription;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author GraviteeSource Team
 */
public class JwtPlanBasedAuthenticationHandler extends PlanBasedAuthenticationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(JwtPlanBasedAuthenticationHandler.class);
    private static final String CLIENT_ID_CLAIM_PARAMETER = "clientIdClaim";

    private static final String CONTEXT_ATTRIBUTE_JWT = "jwt";

    private static final String CLAIM_CLIENT_ID = "client_id";
    private static final String CLAIM_AUDIENCE = "aud";
    private static final String CLAIM_AUTHORIZED_PARTY = "azp";

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final SubscriptionRepository subscriptionRepository;

    public JwtPlanBasedAuthenticationHandler(AuthenticationHandler handler, Plan plan, SubscriptionRepository subscriptionRepository) {
        super(handler, plan);
        this.subscriptionRepository = subscriptionRepository;
    }

    @Override
    protected boolean preCheckSubscription(AuthenticationContext authenticationContext) {
        LazyJwtToken token = (LazyJwtToken) authenticationContext.get(CONTEXT_ATTRIBUTE_JWT);
        if (token == null || token.getClaims() == null) {
            return false;
        }

        String clientId = getClientId(token.getClaims());
        if (clientId == null) {
            return false;
        }

        return preCheckSubscription(authenticationContext.getApi(), clientId, authenticationContext);
    }

    private boolean preCheckSubscription(String api, String clientId, AuthenticationContext authenticationContext) {
        try {
            if (
                Boolean.TRUE.equals(
                    authenticationContext.getInternalAttribute(ATTR_INTERNAL_LAST_SECURITY_HANDLER_SUPPORTING_SAME_TOKEN_TYPE)
                )
            ) {
                // Last handler (jwt or oauth), no need to check the subscription, let the CheckSubscriptionPolicy do the job and return an appropriate error.
                return true;
            }

            // Find a matching subscription to try to target the good plan.
            final List subscriptions = subscriptionRepository.search(
                new SubscriptionCriteria.Builder()
                    .apis(Collections.singleton(api))
                    .plans(Collections.singleton(plan.getId()))
                    .clientId(clientId)
                    .status(Subscription.Status.ACCEPTED)
                    .build()
            );

            if (subscriptions != null && !subscriptions.isEmpty()) {
                final Subscription subscription = subscriptions.get(0);
                if (
                    subscription != null &&
                    (
                        subscription.getEndingAt() == null ||
                        subscription.getEndingAt().after(new Date(authenticationContext.request().timestamp()))
                    )
                ) {
                    authenticationContext.setApplication(subscription.getApplication());
                    authenticationContext.setPlan(subscription.getPlan());
                    authenticationContext.setSubscription(subscription.getId());
                    authenticationContext.request().metrics().setSecurityType(JWT);
                    authenticationContext.request().metrics().setSecurityToken(clientId);
                    return true;
                }
            }
        } catch (TechnicalException e) {
            LOGGER.error("Failed to check JWT plan subscription", e);
        }
        return false;
    }

    /**
     * Get clientID from JWT claims
     * FIXME : This is duplicated from JWT policy
     *
     * @param claims
     * @return clientId
     */
    protected String getClientId(Map claims) {
        if (getCustomClientIdClaimName() != null) {
            Object clientIdClaim = claims.get(getCustomClientIdClaimName());
            return extractClientId(clientIdClaim);
        }

        String clientId = null;

        // Look for the client_id of the Authorized party claim
        String authorizedParty = (String) claims.get(CLAIM_AUTHORIZED_PARTY);
        if (authorizedParty != null && !authorizedParty.isEmpty()) {
            clientId = authorizedParty;
        }

        // Look for the client_id of the audience claim
        if (clientId == null) {
            Object audClaim = claims.get(CLAIM_AUDIENCE);
            clientId = extractClientId(audClaim);
        }

        // Is there any client_id claim ?
        if (clientId == null) {
            clientId = (String) claims.get(CLAIM_CLIENT_ID);
        }

        return clientId;
    }

    /**
     * Extract client_id from claim.
     * FIXME : This is duplicated from JWT policy
     *
     * @param claim
     * @return clientId
     */
    private String extractClientId(Object claim) {
        if (claim != null) {
            if (claim instanceof List) {
                List claims = (List) claim;
                // For the moment, we took only the first value of the array
                return claims.get(0);
            } else {
                return (String) claim;
            }
        }
        return null;
    }

    /**
     * Get custom claim name containing client_id if it has been specified in JWT policy configuration.
     * FIXME : Policy configuration should be handled by the policy itself
     *
     * @return name of the claim containing client_id configured at policy level
     */
    private String getCustomClientIdClaimName() {
        try {
            if (plan.getSecurityDefinition() != null) {
                JsonNode clientIdClaimNode = objectMapper.readTree(plan.getSecurityDefinition()).get(CLIENT_ID_CLAIM_PARAMETER);
                if (clientIdClaimNode != null) {
                    return clientIdClaimNode.asText();
                }
            }
        } catch (JsonProcessingException e) {
            LOGGER.error("Failed to read plan security definition", e);
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy