org.wso2.carbon.apimgt.gateway.handlers.security.basicauth.BasicAuthCredentialValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.apimgt.gateway.handlers.security.basicauth.BasicAuthCredentialValidator.java

Source

/*
 * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * 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 org.wso2.carbon.apimgt.gateway.handlers.security.basicauth;

import io.swagger.models.Path;
import io.swagger.models.Swagger;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.rest.RESTConstants;
import org.wso2.carbon.apimgt.gateway.MethodStats;
import org.wso2.carbon.apimgt.gateway.handlers.security.APISecurityConstants;
import org.wso2.carbon.apimgt.gateway.handlers.security.APISecurityException;
import org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
import org.wso2.carbon.authenticator.stub.AuthenticationAdminStub;
import org.wso2.carbon.authenticator.stub.LoginAuthenticationExceptionException;
import org.wso2.carbon.base.ServerConfiguration;
import org.wso2.carbon.um.ws.api.stub.RemoteUserStoreManagerServiceStub;
import org.wso2.carbon.um.ws.api.stub.RemoteUserStoreManagerServiceUserStoreExceptionException;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import javax.cache.Cache;
import javax.cache.Caching;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

/**
 * This class will validate the basic auth credentials.
 */
public class BasicAuthCredentialValidator {

    private boolean gatewayKeyCacheEnabled;
    private static boolean gatewayUsernameCacheInit = false;
    private static boolean gatewayBasicAuthResourceCacheInit = false;

    protected Log log = LogFactory.getLog(getClass());
    private AuthenticationAdminStub authAdminStub;
    private RemoteUserStoreManagerServiceStub remoteUserStoreManagerServiceStub;
    private String host;

    /**
     * Initialize the validator with the synapse environment.
     *
     * @throws APISecurityException If an authentication failure or some other error occurs
     */
    BasicAuthCredentialValidator() throws APISecurityException {
        this.gatewayKeyCacheEnabled = isGatewayTokenCacheEnabled();
        this.getGatewayUsernameCache();

        ConfigurationContext configurationContext = ServiceReferenceHolder.getInstance()
                .getAxis2ConfigurationContext();
        APIManagerConfiguration config = ServiceReferenceHolder.getInstance().getAPIManagerConfiguration();
        String url = config.getFirstProperty(APIConstants.API_KEY_VALIDATOR_URL);
        if (url == null) {
            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
                    "API key manager URL unspecified");
        }

        try {
            authAdminStub = new AuthenticationAdminStub(configurationContext, url + "AuthenticationAdmin");
        } catch (AxisFault axisFault) {
            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR, axisFault.getMessage(),
                    axisFault);
        }

        try {
            remoteUserStoreManagerServiceStub = new RemoteUserStoreManagerServiceStub(configurationContext,
                    url + "RemoteUserStoreManagerService");
        } catch (AxisFault axisFault) {
            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR, axisFault.getMessage(),
                    axisFault);
        }
        ServiceClient svcClient = remoteUserStoreManagerServiceStub._getServiceClient();
        CarbonUtils.setBasicAccessSecurityHeaders(config.getFirstProperty(APIConstants.AUTH_MANAGER_USERNAME),
                config.getFirstProperty(APIConstants.AUTH_MANAGER_PASSWORD), svcClient);

        try {
            host = new URL(url).getHost();
        } catch (MalformedURLException e) {
            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR, e.getMessage(), e);
        }
    }

    /**
     * Return the resource authentication scheme of the API resource.
     *
     * @param swagger swagger of the API
     * @param synCtx  The message to be authenticated
     * @return the resource authentication scheme
     */
    public String getResourceAuthenticationScheme(Swagger swagger, MessageContext synCtx) {
        String authType = null;
        Map<String, Object> vendorExtensions = getVendorExtensions(synCtx, swagger);
        if (vendorExtensions != null) {
            authType = (String) vendorExtensions.get(APIConstants.SWAGGER_X_AUTH_TYPE);
        }

        if (StringUtils.isNotBlank(authType)) {
            return authType;
        }
        return APIConstants.NO_MATCHING_AUTH_SCHEME;
    }

    /**
     * Validates the given username and password against the users in the user store.
     *
     * @param username given username
     * @param password given password
     * @return true if the validation passed
     * @throws APISecurityException If an authentication failure or some other error occurs
     */
    @MethodStats
    public boolean validate(String username, String password) throws APISecurityException {
        String providedPasswordHash = null;
        if (gatewayKeyCacheEnabled) {
            providedPasswordHash = hashString(password);
            String cachedPasswordHash = (String) getGatewayUsernameCache().get(username);
            if (cachedPasswordHash != null && cachedPasswordHash.equals(providedPasswordHash)) {
                log.debug("Basic Authentication: <Valid Username Cache> Username & password authenticated");
                return true; //If (username->password) is in the valid cache
            } else {
                String invalidCachedPasswordHash = (String) getInvalidUsernameCache().get(username);
                if (invalidCachedPasswordHash != null && invalidCachedPasswordHash.equals(providedPasswordHash)) {
                    log.debug(
                            "Basic Authentication: <Invalid Username Cache> Username & password authentication failed");
                    return false; //If (username->password) is in the invalid cache
                }
            }
        }

        boolean authenticated;
        try {
            authenticated = authAdminStub.login(username, password, host);
        } catch (RemoteException | LoginAuthenticationExceptionException e) {
            log.debug("Basic Authentication: Username and Password authentication failure");
            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR, e.getMessage(), e);
        }

        if (gatewayKeyCacheEnabled) {
            if (authenticated) {
                // put (username->password) into the valid cache
                getGatewayUsernameCache().put(username, providedPasswordHash);
            } else {
                // put (username->password) into the invalid cache
                getInvalidUsernameCache().put(username, providedPasswordHash);
            }
        }

        return authenticated;
    }

    /**
     * Validates the roles of the given user against the roles of the scopes of the API resource.
     *
     * @param username given username
     * @param swagger  swagger of the API
     * @param synCtx   The message to be authenticated
     * @return true if the validation passed
     * @throws APISecurityException If an authentication failure or some other error occurs
     */
    @MethodStats
    public boolean validateScopes(String username, Swagger swagger, MessageContext synCtx)
            throws APISecurityException {
        String apiContext = (String) synCtx.getProperty(RESTConstants.REST_API_CONTEXT);
        String apiVersion = (String) synCtx.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);
        String apiElectedResource = (String) synCtx.getProperty(APIConstants.API_ELECTED_RESOURCE);
        if (swagger != null) {
            org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) synCtx)
                    .getAxis2MessageContext();
            String httpMethod = (String) axis2MessageContext
                    .getProperty(APIConstants.DigestAuthConstants.HTTP_METHOD);
            String resourceKey = apiElectedResource + ":" + httpMethod;
            String resourceCacheKey = resourceKey + ":" + username;
            if (gatewayKeyCacheEnabled && getGatewayBasicAuthResourceCache().get(resourceCacheKey) != null) {
                return true;
            } else {
                // retrieve the user roles related to the scope of the API resource
                String resourceRoles = null;
                Map<String, Object> vendorExtensions = getVendorExtensions(synCtx, swagger);
                if (vendorExtensions != null) {
                    resourceRoles = (String) vendorExtensions.get(APIConstants.SWAGGER_X_ROLES);
                }

                if (StringUtils.isNotBlank(resourceRoles)) {
                    String[] userRoles;

                    String tenantDomain = MultitenantUtils.getTenantDomain(username);
                    if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
                        try {
                            userRoles = remoteUserStoreManagerServiceStub
                                    .getRoleListOfUser(MultitenantUtils.getTenantAwareUsername(username));
                        } catch (RemoteException | RemoteUserStoreManagerServiceUserStoreExceptionException e) {
                            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
                                    e.getMessage());
                        }
                    } else {
                        try {
                            int tenantId = ServiceReferenceHolder.getInstance().getRealmService().getTenantManager()
                                    .getTenantId(tenantDomain);

                            UserStoreManager manager = ServiceReferenceHolder.getInstance().getRealmService()
                                    .getTenantUserRealm(tenantId).getUserStoreManager();

                            userRoles = manager
                                    .getRoleListOfUser(MultitenantUtils.getTenantAwareUsername(username));
                        } catch (UserStoreException e) {
                            throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
                                    e.getMessage());
                        }
                    }
                    // check if the roles related to the API resource contains any of the role of the user
                    for (String role : userRoles) {
                        if (resourceRoles.contains(role)) {
                            if (gatewayKeyCacheEnabled) {
                                getGatewayBasicAuthResourceCache().put(resourceCacheKey, resourceKey);
                            }
                            return true;
                        }
                    }
                } else {
                    // No scopes for the requested resource
                    if (gatewayKeyCacheEnabled) {
                        getGatewayBasicAuthResourceCache().put(resourceCacheKey, resourceKey);
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("Basic Authentication: No scopes for the API resource: ".concat(resourceKey));
                    }
                    return true;
                }
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Basic Authentication: No swagger found in the gateway for the API: ".concat(apiContext)
                        .concat(":").concat(apiVersion));
            }
            return true;
        }
        if (log.isDebugEnabled()) {
            log.debug("Basic Authentication: Scope validation failed for the API resource: "
                    .concat(apiElectedResource));
        }
        throw new APISecurityException(APISecurityConstants.INVALID_SCOPE, "Scope validation failed");
    }

    /**
     * Return the throttling tier of the API resource.
     *
     * @param swagger swagger of the API
     * @param synCtx  The message to be authenticated
     * @return the resource throttling tier
     */
    public String getResourceThrottlingTier(Swagger swagger, MessageContext synCtx) {
        String throttlingTier = null;
        Map<String, Object> vendorExtensions = getVendorExtensions(synCtx, swagger);
        if (vendorExtensions != null) {
            throttlingTier = (String) vendorExtensions.get(APIConstants.SWAGGER_X_THROTTLING_TIER);
        }
        if (StringUtils.isNotBlank(throttlingTier)) {
            return throttlingTier;
        }
        return APIConstants.UNLIMITED_TIER;
    }

    private Map<String, Object> getVendorExtensions(MessageContext synCtx, Swagger swagger) {
        if (swagger != null) {
            String apiElectedResource = (String) synCtx.getProperty(APIConstants.API_ELECTED_RESOURCE);
            org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) synCtx)
                    .getAxis2MessageContext();
            String httpMethod = (String) axis2MessageContext
                    .getProperty(APIConstants.DigestAuthConstants.HTTP_METHOD);
            Path path = swagger.getPath(apiElectedResource);
            if (path != null) {
                switch (httpMethod) {
                case APIConstants.HTTP_GET:
                    return path.getGet().getVendorExtensions();
                case APIConstants.HTTP_POST:
                    return path.getPost().getVendorExtensions();
                case APIConstants.HTTP_PUT:
                    return path.getPut().getVendorExtensions();
                case APIConstants.HTTP_DELETE:
                    return path.getDelete().getVendorExtensions();
                case APIConstants.HTTP_HEAD:
                    return path.getHead().getVendorExtensions();
                case APIConstants.HTTP_OPTIONS:
                    return path.getOptions().getVendorExtensions();
                case APIConstants.HTTP_PATCH:
                    return path.getPatch().getVendorExtensions();
                }
            }
        }
        return null;
    }

    /**
     * Returns the md5 hash of a given string.
     *
     * @param str the string input to be hashed
     * @return hashed string
     */
    private String hashString(String str) {
        String generatedHash = null;
        try {
            // Create MessageDigest instance for MD5
            MessageDigest md = MessageDigest.getInstance("MD5");
            //Add str bytes to digest
            md.update(str.getBytes());
            //Get the hash's bytes
            byte[] bytes = md.digest();
            //This bytes[] has bytes in decimal format;
            //Convert it to hexadecimal format
            StringBuilder sb = new StringBuilder();
            for (byte aByte : bytes) {
                sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
            }
            //Get complete hashed str in hex format
            generatedHash = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        }
        return generatedHash;
    }

    /**
     * Returns the basic authenticated resource request cache.
     *
     * @return the resource cache
     */
    private Cache getGatewayBasicAuthResourceCache() {
        String apimGWCacheExpiry = getApiManagerConfiguration().getFirstProperty(APIConstants.TOKEN_CACHE_EXPIRY);
        if (!gatewayBasicAuthResourceCacheInit) {
            gatewayBasicAuthResourceCacheInit = true;
            if (apimGWCacheExpiry != null) {
                return createCache(APIConstants.GATEWAY_BASIC_AUTH_RESOURCE_CACHE_NAME,
                        Long.parseLong(apimGWCacheExpiry), Long.parseLong(apimGWCacheExpiry));
            } else {
                long defaultCacheTimeout = getDefaultCacheTimeout();
                return createCache(APIConstants.GATEWAY_BASIC_AUTH_RESOURCE_CACHE_NAME, defaultCacheTimeout,
                        defaultCacheTimeout);
            }
        }
        return getCacheFromCacheManager(APIConstants.GATEWAY_BASIC_AUTH_RESOURCE_CACHE_NAME);
    }

    /**
     * Returns the valid username cache.
     *
     * @return the valid username cache
     */
    private Cache getGatewayUsernameCache() {
        String apimGWCacheExpiry = getApiManagerConfiguration().getFirstProperty(APIConstants.TOKEN_CACHE_EXPIRY);
        if (!gatewayUsernameCacheInit) {
            gatewayUsernameCacheInit = true;
            if (apimGWCacheExpiry != null) {
                return createCache(APIConstants.GATEWAY_USERNAME_CACHE_NAME, Long.parseLong(apimGWCacheExpiry),
                        Long.parseLong(apimGWCacheExpiry));
            } else {
                long defaultCacheTimeout = getDefaultCacheTimeout();
                return createCache(APIConstants.GATEWAY_USERNAME_CACHE_NAME, defaultCacheTimeout,
                        defaultCacheTimeout);
            }
        }
        return getCacheFromCacheManager(APIConstants.GATEWAY_USERNAME_CACHE_NAME);
    }

    /**
     * Returns the invalid username cache.
     *
     * @return the invalid username cache
     */
    private Cache getInvalidUsernameCache() {
        String apimGWCacheExpiry = getApiManagerConfiguration().getFirstProperty(APIConstants.TOKEN_CACHE_EXPIRY);

        if (!gatewayUsernameCacheInit) {
            gatewayUsernameCacheInit = true;
            if (apimGWCacheExpiry != null) {
                return createCache(APIConstants.GATEWAY_INVALID_USERNAME_CACHE_NAME,
                        Long.parseLong(apimGWCacheExpiry), Long.parseLong(apimGWCacheExpiry));
            } else {
                long defaultCacheTimeout = getDefaultCacheTimeout();
                return createCache(APIConstants.GATEWAY_INVALID_USERNAME_CACHE_NAME, defaultCacheTimeout,
                        defaultCacheTimeout);
            }
        }
        return getCacheFromCacheManager(APIConstants.GATEWAY_INVALID_USERNAME_CACHE_NAME);
    }

    /**
     * Create the Cache object from the given parameters.
     *
     * @param cacheName   name of the Cache
     * @param modifiedExp value of the modified expiry type
     * @param accessExp   value of the accessed expiry type
     * @return the cache object
     */
    private Cache createCache(final String cacheName, final long modifiedExp, long accessExp) {
        return APIUtil.getCache(APIConstants.API_MANAGER_CACHE_MANAGER, cacheName, modifiedExp, accessExp);
    }

    /**
     * Returns the API Manager Configuration.
     *
     * @return the API Manager Configuration
     */
    private APIManagerConfiguration getApiManagerConfiguration() {
        return ServiceReferenceHolder.getInstance().getAPIManagerConfiguration();
    }

    /**
     * Returns the Cache object of the given name.
     *
     * @param cacheName name of the Cache
     * @return the cache object
     */
    private Cache getCacheFromCacheManager(String cacheName) {
        return Caching.getCacheManager(APIConstants.API_MANAGER_CACHE_MANAGER).getCache(cacheName);
    }

    /**
     * Returns the default cache timeout.
     *
     * @return the default cache timeout
     */
    private long getDefaultCacheTimeout() {
        return Long.valueOf(ServerConfiguration.getInstance().getFirstProperty(APIConstants.DEFAULT_CACHE_TIMEOUT))
                * 60;
    }

    /**
     * Returns whether the gateway token cache is enabled.
     *
     * @return true if the gateway token cache is enabled
     */
    private boolean isGatewayTokenCacheEnabled() {
        try {
            APIManagerConfiguration config = getApiManagerConfiguration();
            String cacheEnabled = config.getFirstProperty(APIConstants.GATEWAY_TOKEN_CACHE_ENABLED);
            return Boolean.parseBoolean(cacheEnabled);
        } catch (Exception e) {
            log.error("Did not found valid API Validation Information cache configuration."
                    + " Use default configuration " + e, e);
        }
        return true;
    }
}