org.wso2.carbon.apimgt.authenticator.oidc.OIDCAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.apimgt.authenticator.oidc.OIDCAuthenticator.java

Source

/*
*  Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you 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.authenticator.oidc;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jwt.*;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.core.security.AuthenticatorsConfiguration;
import org.wso2.carbon.core.services.authentication.CarbonServerAuthenticator;
import org.wso2.carbon.core.services.util.CarbonAuthenticationUtil;
import org.wso2.carbon.core.util.AnonymousSessionUtil;
import org.wso2.carbon.core.util.PermissionUpdateUtil;
import org.wso2.carbon.apimgt.authenticator.oidc.common.AuthenticationToken;
import org.wso2.carbon.apimgt.authenticator.oidc.common.ServerConfiguration;
import org.wso2.carbon.apimgt.authenticator.oidc.common.AuthClient;
import org.wso2.carbon.apimgt.authenticator.oidc.internal.OIDCAuthBEDataHolder;
import org.wso2.carbon.apimgt.authenticator.oidc.util.Util;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.AuthenticationObserver;
import org.wso2.carbon.utils.ServerConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.*;

public class OIDCAuthenticator implements CarbonServerAuthenticator {

    private static final int DEFAULT_PRIORITY_LEVEL = 3;
    private static final String AUTHENTICATOR_NAME = OIDCAuthenticatorBEConstants.OIDC_AUTHENTICATOR_NAME;

    public static final Log log = LogFactory.getLog(OIDCAuthenticator.class);

    /**
     * Login method
     *
     * @param code  code value
     * @param nonce nonce value
     * @return user name of authenticated user
     */
    public String login(String code, String nonce) {

        String userName;

        try {

            HttpSession httpSession = getHttpSession();
            RealmService realmService = OIDCAuthBEDataHolder.getInstance().getRealmService();
            RegistryService registryService = OIDCAuthBEDataHolder.getInstance().getRegistryService();

            ServerConfiguration serverConfiguration = getServerConfiguration();
            AuthClient authClient = getClientConfiguration();
            String jsonResponse = getTokenFromTokenEP(serverConfiguration, authClient, code);
            AuthenticationToken oidcAuthenticationToken = getAuthenticationToken(jsonResponse);
            userName = getUserName(oidcAuthenticationToken, serverConfiguration);

            if (userName == null || userName.equals("")) {
                log.error("Authentication Request is rejected. " + "User Name is Null");
                return null;
            }

            String tenantDomain = MultitenantUtils.getTenantDomain(userName);
            int tenantId = realmService.getTenantManager().getTenantId(tenantDomain);

            // Start Authentication
            handleAuthenticationStarted(tenantId);
            if (isResponseSignatureValidationEnabled()) {

                boolean isSignatureValid = validateSignature(serverConfiguration, authClient,
                        oidcAuthenticationToken, nonce);

                if (!isSignatureValid) {
                    log.error("Authentication Request is rejected. " + " Signature validation failed.");
                    CarbonAuthenticationUtil.onFailedAdminLogin(httpSession, userName, tenantId,
                            "OIDC Authentication", "Invalid Signature");
                    handleAuthenticationCompleted(tenantId, false);
                    return null;
                }
            }

            userName = MultitenantUtils.getTenantAwareUsername(userName);
            UserRealm realm = AnonymousSessionUtil.getRealmByTenantDomain(registryService, realmService,
                    tenantDomain);

            // Starting Authorization
            PermissionUpdateUtil.updatePermissionTree(tenantId);
            boolean isAuthorized = realm.getAuthorizationManager().isUserAuthorized(userName,
                    "/permission/admin/login", CarbonConstants.UI_PERMISSION_ACTION);
            if (isAuthorized) {
                CarbonAuthenticationUtil.onSuccessAdminLogin(httpSession, userName, tenantId, tenantDomain,
                        "OIDC Authentication");
                handleAuthenticationCompleted(tenantId, true);
            } else {
                log.error("Authentication Request is rejected. Authorization Failure.");
                CarbonAuthenticationUtil.onFailedAdminLogin(httpSession, userName, tenantId, "OIDC Authentication",
                        "Invalid credential");
                handleAuthenticationCompleted(tenantId, false);
                return null;
            }
        } catch (Exception e) {
            String msg = "System error while Authenticating/Authorizing User : " + e.getMessage();
            log.error(msg, e);
            return null;
        }

        return userName;
    }

    /**
     * HTTP post against token endpoint of OIDC server.
     *
     * @param serverConfiguration ServerConfiguration
     * @param code                code
     * @return json String
     * @throws IOException
     */
    private String getTokenFromTokenEP(ServerConfiguration serverConfiguration, AuthClient authClient, String code)
            throws IOException {

        // Client details
        String clientId = authClient.getClientId();
        String clientSecret = authClient.getClientSecret();
        String authorizationType = authClient.getAuthorizationType();
        String redirectURI = authClient.getRedirectURI();

        HttpClient client = new DefaultHttpClient();

        HttpPost post = new HttpPost(serverConfiguration.getTokenEndpointUri());

        List<NameValuePair> nvps = new ArrayList<NameValuePair>();

        nvps.add(new BasicNameValuePair("grant_type", authorizationType));
        nvps.add(new BasicNameValuePair("code", code));
        nvps.add(new BasicNameValuePair("redirect_uri", redirectURI));
        post.setEntity(new UrlEncodedFormEntity(nvps));

        post.setHeader(HttpHeaders.AUTHORIZATION,
                String.format("Basic %s", Base64.encode(String.format("%s:%s", clientId, clientSecret))).trim());

        HttpResponse response = client.execute(post);
        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        String jsonString = "";
        String line;
        while ((line = rd.readLine()) != null) {
            jsonString = jsonString + line;
            log.debug("Response from Token Endpoint : " + jsonString);
        }
        return jsonString;
    }

    /**
     * Create AuthClient bean to hold client information
     * @return AuthClient
     */
    private AuthClient getClientConfiguration() {
        AuthClient authClient = new AuthClient();

        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration
                .getAuthenticatorConfig(OIDCAuthenticatorBEConstants.OIDC_AUTHENTICATOR_NAME);

        Map<String, String> parameters = authenticatorConfig.getParameters();

        // Client details
        authClient.setClientId(parameters.get(OIDCAuthenticatorBEConstants.CLIENT_ID));
        authClient.setClientSecret(parameters.get(OIDCAuthenticatorBEConstants.CLIENT_SECRET));
        authClient.setAuthorizationType(parameters.get(OIDCAuthenticatorBEConstants.CLIENT_AUTHORIZATION_TYPE));
        authClient.setRedirectURI(parameters.get(OIDCAuthenticatorBEConstants.CLIENT_REDIRECT_URI));
        authClient.setClientAlgorithm(parameters.get(OIDCAuthenticatorBEConstants.CLIENT_ALGORITHM));

        return authClient;

    }

    /**
     * Get OIDC Server Configuration
     * @return ServerConfiguration
     */
    private ServerConfiguration getServerConfiguration() {

        ServerConfiguration serverConfiguration = new ServerConfiguration();

        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration
                .getAuthenticatorConfig(OIDCAuthenticatorBEConstants.OIDC_AUTHENTICATOR_NAME);

        Map<String, String> parameters = authenticatorConfig.getParameters();
        serverConfiguration.setIssuer(parameters.get(OIDCAuthenticatorBEConstants.IDENTITY_PROVIDER_URI));
        serverConfiguration.setJwksUri(parameters.get(OIDCAuthenticatorBEConstants.JWKS_URL));
        serverConfiguration.setUserInfoUri(parameters.get(OIDCAuthenticatorBEConstants.USER_INFO_URI));
        serverConfiguration.setTokenEndpointUri(parameters.get(OIDCAuthenticatorBEConstants.TOKEN_ENDPOINT_URI));

        return serverConfiguration;
    }

    private AuthenticationToken getAuthenticationToken(String jsonTokenResponse) throws Exception {

        JsonElement jsonRoot = new JsonParser().parse(jsonTokenResponse);
        if (!jsonRoot.isJsonObject()) {
            throw new Exception("Token Endpoint did not return a JSON object: " + jsonRoot);
        }
        JsonObject tokenResponse = jsonRoot.getAsJsonObject();

        if (tokenResponse.get("error") != null) {

            // Handle error
            String error = tokenResponse.get("error").getAsString();
            log.error("Token Endpoint returned: " + error);
            throw new Exception("Unable to obtain Access Token.  Token Endpoint returned: " + error);

        } else {

            // get out all the token strings
            String accessTokenValue;
            String idTokenValue;
            String refreshTokenValue = null;

            if (tokenResponse.has("access_token")) {
                accessTokenValue = tokenResponse.get("access_token").getAsString();
            } else {
                throw new Exception("Token Endpoint did not return an access_token: " + jsonTokenResponse);
            }

            if (tokenResponse.has("id_token")) {
                idTokenValue = tokenResponse.get("id_token").getAsString();
            } else {
                log.error("Token Endpoint did not return an id_token");
                throw new Exception("Token Endpoint did not return an id_token");
            }

            if (tokenResponse.has("refresh_token")) {
                refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
            }

            return new AuthenticationToken(idTokenValue, accessTokenValue, refreshTokenValue);

        }
    }

    private String getUserName(AuthenticationToken authenticationToken, ServerConfiguration serverConfiguration)
            throws Exception {

        String userName;

        String userInfoJson = Util.getUserInfo(serverConfiguration, authenticationToken);

        JsonElement jsonRoot = new JsonParser().parse(userInfoJson);

        if (!jsonRoot.isJsonObject()) {
            log.error("User Info Json did not return a JSON object: " + jsonRoot);
            throw new Exception("User Info Json did not return a JSON object: " + jsonRoot);
        }

        JsonObject jsonResponse = jsonRoot.getAsJsonObject();

        if (jsonResponse.has("preferred_username")) {
            userName = jsonResponse.get("preferred_username").getAsString();
            log.debug("User name taken from user info endpoint : " + userName);
        } else {
            throw new Exception("User Info JSON did not return an preferred_username");
        }
        return userName;
    }

    private boolean validateSignature(ServerConfiguration serverConfiguration, AuthClient authClient,
            AuthenticationToken oidcAuthenticationToken, String nonce) throws Exception {

        boolean isSignatureValid;
        JWT idToken = JWTParser.parse(oidcAuthenticationToken.getIdTokenValue());
        ReadOnlyJWTClaimsSet idClaims = idToken.getJWTClaimsSet();

        // Supports only signedJWT
        if (idToken instanceof SignedJWT) {
            SignedJWT signedIdToken = (SignedJWT) idToken;
            isSignatureValid = Util.verifySignature(signedIdToken, serverConfiguration);

        } else if (idToken instanceof PlainJWT) {
            log.error("Plain JWT not supported");
            throw new Exception("Plain JWT not supported");

        } else {
            log.error("JWT type not supported");
            throw new Exception("JWT type not supported");
        }

        boolean isValidClaimSet = Util.validateIdClaims(serverConfiguration, authClient, idToken, nonce, idClaims);
        return isSignatureValid && isValidClaimSet;
    }

    private void handleAuthenticationStarted(int tenantId) {
        BundleContext bundleContext = OIDCAuthBEDataHolder.getInstance().getBundleContext();
        if (bundleContext != null) {
            ServiceTracker tracker = new ServiceTracker(bundleContext, AuthenticationObserver.class.getName(),
                    null);
            tracker.open();
            Object[] services = tracker.getServices();
            if (services != null) {
                for (Object service : services) {
                    ((AuthenticationObserver) service).startedAuthentication(tenantId);
                }
            }
            tracker.close();
        }
    }

    private void handleAuthenticationCompleted(int tenantId, boolean isSuccessful) {
        BundleContext bundleContext = OIDCAuthBEDataHolder.getInstance().getBundleContext();
        if (bundleContext != null) {
            ServiceTracker tracker = new ServiceTracker(bundleContext, AuthenticationObserver.class.getName(),
                    null);
            tracker.open();
            Object[] services = tracker.getServices();
            if (services != null) {
                for (Object service : services) {
                    ((AuthenticationObserver) service).completedAuthentication(tenantId, isSuccessful);
                }
            }
            tracker.close();
        }
    }

    public void logout() {
        String loggedInUser;
        String delegatedBy;
        Date currentTime = Calendar.getInstance().getTime();
        SimpleDateFormat date = new SimpleDateFormat("'['yyyy-MM-dd HH:mm:ss,SSSS']'");
        HttpSession session = getHttpSession();

        if (session != null) {
            loggedInUser = (String) session.getAttribute(ServerConstants.USER_LOGGED_IN);
            delegatedBy = (String) session.getAttribute("DELEGATED_BY");
            if (delegatedBy == null) {
                log.info("'" + loggedInUser + "' logged out at " + date.format(currentTime));
            } else {
                log.info("'" + loggedInUser + "' logged out at " + date.format(currentTime) + " delegated by "
                        + delegatedBy);
            }
            session.invalidate();
        }
    }

    public boolean isHandle(MessageContext messageContext) {
        return false;
    }

    public boolean isAuthenticated(MessageContext messageContext) {
        HttpServletRequest request = (HttpServletRequest) messageContext
                .getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);
        HttpSession httpSession = request.getSession();
        String loginStatus = (String) httpSession.getAttribute(ServerConstants.USER_LOGGED_IN);

        return (loginStatus != null);
    }

    public boolean authenticateWithRememberMe(MessageContext messageContext) {
        return false;
    }

    public int getPriority() {
        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration
                .getAuthenticatorConfig(AUTHENTICATOR_NAME);
        if (authenticatorConfig != null && authenticatorConfig.getPriority() > 0) {
            return authenticatorConfig.getPriority();
        }
        return DEFAULT_PRIORITY_LEVEL;
    }

    public String getAuthenticatorName() {
        return AUTHENTICATOR_NAME;
    }

    public boolean isDisabled() {
        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration
                .getAuthenticatorConfig(AUTHENTICATOR_NAME);

        return authenticatorConfig != null && authenticatorConfig.isDisabled();
    }

    /**
     * Check whether signature validation is enabled in the authenticators.xml configuration file
     *
     * @return false only if SSOAuthenticator configuration has the parameter
     * <Parameter name="ResponseSignatureValidationEnabled">false</Parameter>, true otherwise
     */
    private boolean isResponseSignatureValidationEnabled() {
        AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance();
        AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration
                .getAuthenticatorConfig(AUTHENTICATOR_NAME);
        if (authenticatorConfig != null) {
            String responseSignatureValidation = authenticatorConfig.getParameters()
                    .get(OIDCAuthenticatorBEConstants.PropertyConfig.RESPONSE_SIGNATURE_VALIDATION_ENABLED);
            if (responseSignatureValidation != null && responseSignatureValidation.equalsIgnoreCase("false")) {
                if (log.isDebugEnabled()) {
                    log.debug("Signature validation is disabled in the configuration");
                }
                return false;
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Signature validation is enabled in the configuration");
        }
        return true;
    }

    private HttpSession getHttpSession() {
        MessageContext msgCtx = MessageContext.getCurrentMessageContext();
        HttpSession httpSession = null;
        if (msgCtx != null) {
            HttpServletRequest request = (HttpServletRequest) msgCtx
                    .getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);
            httpSession = request.getSession();
        }
        return httpSession;
    }

}