com.vmware.identity.openidconnect.client.OIDCClient.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.openidconnect.client.OIDCClient.java

Source

/*
 *  Copyright (c) 2012-2015 VMware, Inc.  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 com.vmware.identity.openidconnect.client;

import java.net.URI;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.UUID;

import org.apache.commons.lang3.Validate;

import com.nimbusds.jose.JOSEException;
import com.vmware.identity.openidconnect.common.AuthorizationCode;
import com.vmware.identity.openidconnect.common.ClientID;
import com.vmware.identity.openidconnect.common.CorrelationID;
import com.vmware.identity.openidconnect.common.Issuer;
import com.vmware.identity.openidconnect.common.JWTID;
import com.vmware.identity.openidconnect.common.Nonce;
import com.vmware.identity.openidconnect.common.PersonUserAssertionSigner;
import com.vmware.identity.openidconnect.common.ResponseMode;
import com.vmware.identity.openidconnect.common.ResponseType;
import com.vmware.identity.openidconnect.common.ResponseTypeValue;
import com.vmware.identity.openidconnect.common.Scope;
import com.vmware.identity.openidconnect.common.State;
import com.vmware.identity.openidconnect.protocol.AuthenticationRequest;
import com.vmware.identity.openidconnect.protocol.AuthorizationCodeGrant;
import com.vmware.identity.openidconnect.protocol.AuthorizationGrant;
import com.vmware.identity.openidconnect.protocol.ClientAssertion;
import com.vmware.identity.openidconnect.protocol.ClientCredentialsGrant;
import com.vmware.identity.openidconnect.protocol.HttpResponse;
import com.vmware.identity.openidconnect.protocol.LogoutRequest;
import com.vmware.identity.openidconnect.protocol.PasswordGrant;
import com.vmware.identity.openidconnect.protocol.PersonUserAssertion;
import com.vmware.identity.openidconnect.protocol.PersonUserCertificateGrant;
import com.vmware.identity.openidconnect.protocol.RefreshTokenGrant;
import com.vmware.identity.openidconnect.protocol.SolutionUserCredentialsGrant;
import com.vmware.identity.openidconnect.protocol.URIUtils;

/**
 * OIDC Client
 *
 * @author Jun Sun
 * @author Yehia Zayour
 */
public final class OIDCClient {

    private final URI authorizationEndpointURI;
    private final URI tokenEndpointURI;
    private final URI endSessionEndpointURI;
    private final RSAPublicKey providerPublicKey;
    private final Issuer issuer;

    private final ClientID clientId;
    private final HolderOfKeyConfig holderOfKeyConfig;
    private final HighAvailabilityConfig highAvailabilityConfig;
    private final long clockToleranceInSeconds;
    private final KeyStore keyStore;

    /**
     * Constructor
     *
     * @param clientConfig              Configuration required in creating a client
     */
    public OIDCClient(ClientConfig clientConfig) {
        Validate.notNull(clientConfig, "clientConfig");

        this.authorizationEndpointURI = clientConfig.getConnectionConfig().getAuthorizationEndpointURI();
        this.tokenEndpointURI = clientConfig.getConnectionConfig().getTokenEndpointURI();
        this.endSessionEndpointURI = clientConfig.getConnectionConfig().getEndSessionEndpointURI();
        this.providerPublicKey = clientConfig.getConnectionConfig().getProviderPublicKey();
        this.issuer = clientConfig.getConnectionConfig().getIssuer();

        this.clientId = clientConfig.getClientId();
        this.holderOfKeyConfig = clientConfig.getHolderOfKeyConfig();
        this.highAvailabilityConfig = clientConfig.getHighAvailabilityConfig();
        this.clockToleranceInSeconds = clientConfig.getClockToleranceInSeconds();
        this.keyStore = clientConfig.getConnectionConfig().getKeyStore();
    }

    /**
     * Build an authentication request URI
     *
     * @param redirectEndpointURI       URI where OIDC server will sends response to.
     * @param responseType              Response type specifying whether code or id token (optionally with access token) is requested.
     * @param responseMode              Response mode. For code flow, it should be FORM_POST; For implicit flow, it can be either FORM_POST or FRAGMENT.
     * @param tokenSpec                 Specification of tokens requested.
     * @param state                     State value used in a request, it is optional.
     * @param nonce                     Nonce value used in a request, it is optional.
     * @return                          Authentication request URI.
     * @throws OIDCClientException      Client side exception.
     */
    public URI buildAuthenticationRequestURI(URI redirectEndpointURI, ResponseType responseType,
            ResponseMode responseMode, TokenSpec tokenSpec, State state, Nonce nonce) throws OIDCClientException {
        Validate.notNull(redirectEndpointURI, "redirectEndpointURI");
        Validate.notNull(responseType, "responseType");
        Validate.notNull(responseMode, "responseMode");
        Validate.notNull(tokenSpec, "tokenSpec");
        Validate.notNull(state, "state");
        Validate.notNull(nonce, "nonce");

        if (responseType.contains(ResponseTypeValue.AUTHORIZATION_CODE)) {
            if (this.holderOfKeyConfig == null) {
                throw new OIDCClientException("HolderOfKeyConfig is required when response type is code.");
            }
            if (responseMode != ResponseMode.QUERY && responseMode != ResponseMode.FORM_POST) {
                throw new OIDCClientException(
                        "Only 'QUERY' or 'FORM_POST' response mode is supported when response type is code.");
            }
        } else if (responseType.contains(ResponseTypeValue.ID_TOKEN)) {
            if (responseMode != ResponseMode.FRAGMENT && responseMode != ResponseMode.FORM_POST) {
                throw new OIDCClientException(
                        "Only 'FRAGMENT' or 'FORM_POST' response mode is supported when response type includes id_token.");
            }
        }

        URI authorizationEndpointURI = this.authorizationEndpointURI;
        if (highAvailabilityEnabled()) {
            String domainController = getAvailableDomainController();
            authorizationEndpointURI = URIUtils.changeHostComponent(authorizationEndpointURI, domainController);
        }

        Scope scope = OIDCClientUtils.buildScopeFromTokenSpec(tokenSpec);

        ClientAssertion clientAssertion = null;
        if (this.holderOfKeyConfig != null) {
            clientAssertion = OIDCClientUtils.createClientAssertion(this.clientId, this.holderOfKeyConfig,
                    authorizationEndpointURI);
        }

        AuthenticationRequest authenticationRequest = new AuthenticationRequest(authorizationEndpointURI,
                responseType, responseMode, this.clientId, redirectEndpointURI, scope, state, nonce,
                clientAssertion, new CorrelationID());
        return authenticationRequest.toHttpRequest().getURI();
    }

    OIDCTokens acquireTokens(AuthorizationGrant authorizationGrant, TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(authorizationGrant, "authorizationGrant");
        Validate.notNull(tokenSpec, "tokenSpec");
        if ((authorizationGrant instanceof AuthorizationCodeGrant
                || authorizationGrant instanceof RefreshTokenGrant) && tokenSpec != TokenSpec.EMPTY) {
            throw new IllegalArgumentException(
                    "tokenSpec must be TokenSpec.EMPTY for authz code and refresh token grants");
        }

        HttpResponse httpResponse = OIDCClientUtils.buildAndSendTokenRequest(authorizationGrant, tokenSpec,
                getTokenEndpointURI(), this.clientId, this.holderOfKeyConfig, this.keyStore);

        return OIDCClientUtils.parseTokenResponse(httpResponse, this.providerPublicKey, this.issuer, this.clientId,
                this.clockToleranceInSeconds);
    }

    /**
     * tokens by smart card certificate that represents a PersonUser
     *
     * @param personUserCertificate     smart card cert
     * @param signer                    client-implemented interface that signs an object with the smart card private key
     * @param tokenSpec                 Specification of tokens requested.
     */
    public OIDCTokens acquireTokensByPersonUserCertificate(X509Certificate personUserCertificate,
            PersonUserAssertionSigner signer, TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(personUserCertificate, "personUserCertificate");
        Validate.notNull(signer, "signer");
        Validate.notNull(tokenSpec, "tokenSpec");

        Date issueTime = new Date(); // now
        URI tokenEndpointURI = getTokenEndpointURI();

        PersonUserAssertion personUserAssertion;
        try {
            personUserAssertion = new PersonUserAssertion(signer, new JWTID(),
                    personUserCertificate.getSubjectDN().getName(), tokenEndpointURI, issueTime);
        } catch (JOSEException e) {
            throw new OIDCClientException("failed to construct PersonUserAssertion", e);
        }

        return acquireTokens(new PersonUserCertificateGrant(personUserCertificate, personUserAssertion), tokenSpec);
    }

    /**
     * Get tokens by GSSNegotiationHandler which handles multi-legged GSSTicketGrant
     *
     * @param gssNegotiationHandler     client-implemented interface that provides us with the next gss ticket
     * @param tokenSpec                 Specification of tokens requested.
     * @return                          OIDC Tokens.
     * @throws OIDCClientException      Client side exception.
     * @throws OIDCServerException      Server side exception.
     * @throws TokenValidationException Token validation exception.
     * @throws SSLConnectionException   SSL connection exception.
     */
    public OIDCTokens acquireTokensByGSS(GSSNegotiationHandler gssNegotiationHandler, TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(gssNegotiationHandler, "gssNegotiationHandler");
        Validate.notNull(tokenSpec, "tokenSpec");

        HttpResponse httpResponse = OIDCClientUtils.negotiateGssResponse(gssNegotiationHandler, tokenSpec,
                getTokenEndpointURI(), this.clientId, this.holderOfKeyConfig, this.keyStore,
                UUID.randomUUID().toString());

        return OIDCClientUtils.parseTokenResponse(httpResponse, this.providerPublicKey, this.issuer, this.clientId,
                this.clockToleranceInSeconds);
    }

    /**
     * Get tokens by SecurIDRetriever which handles multi-legged SecurIDGrant
     *
     * @param usename
     * @param passcode
     * @param securIdRetriever          client-implemented class that provides us with the next RSA SecurID passcode
     * @param tokenSpec                 Specification of tokens requested.
     * @return                          OIDC Tokens.
     * @throws OIDCClientException      Client side exception.
     * @throws OIDCServerException      Server side exception.
     * @throws TokenValidationException Token validation exception.
     * @throws SSLConnectionException   SSL connection exception.
     */
    public OIDCTokens acquireTokensBySecurID(String username, String passcode, SecurIDRetriever securIdRetriever,
            TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notEmpty(username, "username");
        Validate.notEmpty(passcode, "passcode");
        Validate.notNull(securIdRetriever, "securIdRetriever");
        Validate.notNull(tokenSpec, "tokenSpec");

        HttpResponse httpResponse = OIDCClientUtils.handleSecurIDMultiLeggedGrant(username, passcode,
                securIdRetriever, tokenSpec, getTokenEndpointURI(), this.clientId, this.holderOfKeyConfig,
                this.keyStore);

        return OIDCClientUtils.parseTokenResponse(httpResponse, this.providerPublicKey, this.issuer, this.clientId,
                this.clockToleranceInSeconds);
    }

    public OIDCTokens acquireTokensByPassword(String username, String password, TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notEmpty(username, "username");
        Validate.notEmpty(password, "password");
        Validate.notNull(tokenSpec, "tokenSpec");
        return acquireTokens(new PasswordGrant(username, password), tokenSpec);
    }

    public OIDCTokens acquireTokensByRefreshToken(RefreshToken refreshToken)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(refreshToken, "refreshToken");
        return acquireTokens(new RefreshTokenGrant(refreshToken.getRefreshToken()), TokenSpec.EMPTY);
    }

    public OIDCTokens acquireTokensByAuthorizationCode(AuthorizationCode code, URI redirectUri)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(code, "code");
        Validate.notNull(redirectUri, "redirectUri");
        return acquireTokens(new AuthorizationCodeGrant(code, redirectUri), TokenSpec.EMPTY);
    }

    public OIDCTokens acquireTokensBySolutionUserCredentials(TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(tokenSpec, "tokenSpec");
        Validate.notNull(this.holderOfKeyConfig, "this.holderOfKeyConfig");
        return acquireTokens(new SolutionUserCredentialsGrant(), tokenSpec);
    }

    public OIDCTokens acquireTokensByClientCredentials(TokenSpec tokenSpec)
            throws OIDCClientException, OIDCServerException, TokenValidationException, SSLConnectionException {
        Validate.notNull(tokenSpec, "tokenSpec");
        Validate.notNull(this.holderOfKeyConfig, "this.holderOfKeyConfig");
        Validate.notNull(this.clientId, "this.clientId");
        return acquireTokens(new ClientCredentialsGrant(), tokenSpec);
    }

    /**
     * Build a logout request URI
     *
     * @param postLogoutRedirectEndpointURI     Post logout URI.
     * @param idToken                           ID token received from a previous request.
     * @param state                             State value used in a logout request, it is optional.
     * @return                                  Logout request URI.
     * @throws OIDCClientException              Client side exception.
     */
    public URI buildLogoutRequestURI(URI postLogoutRedirectEndpointURI, IDToken idToken, State state)
            throws OIDCClientException {
        Validate.notNull(postLogoutRedirectEndpointURI, "postLogoutRedirectEndpointURI");
        Validate.notNull(idToken, "idToken");

        LogoutRequest logoutRequest = buildLogoutRequest(postLogoutRedirectEndpointURI, idToken, state);
        return logoutRequest.toHttpRequest().getURI();
    }

    /**
     * Build a logout request html form (client returns form that is auto-submitted to the Authorization Server)
     *
     * @param postLogoutRedirectEndpointURI     Post logout URI.
     * @param idToken                           ID token received from a previous request.
     * @param state                             State value used in a logout request, it is optional.
     * @return                                  Logout request html form.
     * @throws OIDCClientException              Client side exception.
     */
    public String buildLogoutRequestHtmlForm(URI postLogoutRedirectEndpointURI, IDToken idToken, State state)
            throws OIDCClientException {
        Validate.notNull(postLogoutRedirectEndpointURI, "postLogoutRedirectEndpointURI");
        Validate.notNull(idToken, "idToken");

        LogoutRequest logoutRequest = buildLogoutRequest(postLogoutRedirectEndpointURI, idToken, state);
        return logoutRequest.toHtmlForm();
    }

    private LogoutRequest buildLogoutRequest(URI postLogoutRedirectEndpointURI, IDToken idToken, State state)
            throws OIDCClientException {
        URI endSessionEndpointURI = this.endSessionEndpointURI;
        if (highAvailabilityEnabled()) {
            String domainController = getAvailableDomainController();
            endSessionEndpointURI = URIUtils.changeHostComponent(endSessionEndpointURI, domainController);
        }

        ClientAssertion clientAssertion = null;
        if (this.holderOfKeyConfig != null) {
            clientAssertion = OIDCClientUtils.createClientAssertion(this.clientId, this.holderOfKeyConfig,
                    endSessionEndpointURI);
        }

        return new LogoutRequest(endSessionEndpointURI, idToken.getIDToken(), postLogoutRedirectEndpointURI, state,
                clientAssertion, new CorrelationID());
    }

    private URI getTokenEndpointURI() throws OIDCClientException {
        URI tokenEndpointURI = this.tokenEndpointURI;
        if (highAvailabilityEnabled()) {
            String domainController = getAvailableDomainController();
            tokenEndpointURI = URIUtils.changeHostComponent(tokenEndpointURI, domainController);
        }
        return tokenEndpointURI;
    }

    private boolean highAvailabilityEnabled() {
        return (this.highAvailabilityConfig != null);
    }

    private String getAvailableDomainController() throws OIDCClientException {
        String result;
        try (ClientDCCache session = this.highAvailabilityConfig.getClientDCCacheFactory().createSession()) {
            result = session.getAvailableDC();
        }
        return result;
    }
}