TokenService.java

/*
 * Copyright (c) 2002-2021, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.oauth2.service;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import fr.paris.lutece.plugins.oauth2.business.AuthClientConf;
import fr.paris.lutece.plugins.oauth2.business.AuthServerConf;
import fr.paris.lutece.plugins.oauth2.business.Token;
import fr.paris.lutece.plugins.oauth2.jwt.JWTParser;
import fr.paris.lutece.plugins.oauth2.jwt.TokenValidationException;
import fr.paris.lutece.plugins.oauth2.web.Constants;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.util.httpaccess.HttpAccess;
import fr.paris.lutece.util.httpaccess.HttpAccessException;

/**
 * TokenService
 */
public final class TokenService
{
    AuthClientConf _defaultClientConfig;
    AuthServerConf _defaultauthServerConfig;

    private static Logger _logger = Logger.getLogger( Constants.LOGGER_OAUTH2 );

    private static final String BEAN_AUTH_CLIENT_CONF = "oauth2.client";
    private static final String BEAN_AUTH_SERVER_CONF = "oauth2.server";

    private static TokenService _instance;

    /**
     * private constructor
     */
    private TokenService( )
    {
    }

    /**
     * private constructor
     */
    private TokenService( AuthClientConf defaultClientConfig, AuthServerConf defaultauthServerConfig )
    {
        _defaultClientConfig = defaultClientConfig;
        _defaultauthServerConfig = defaultauthServerConfig;
    }

    /**
     * Retieve a token using an authorization code
     * 
     * @param strAuthorizationCode
     *            The authorization code
     * @param session
     *            The HTTP session
     * @return The token
     * @throws IOException
     *             if an error occurs
     * @throws HttpAccessException
     *             if an error occurs
     * @throws TokenValidationException
     *             If the token validation failed
     */
    public Token getToken( String strAuthorizationCode, HttpSession session, JWTParser jWTParser, String strStoredNonce )
            throws IOException, HttpAccessException, TokenValidationException
    {

        return getToken( null, _instance._defaultClientConfig, _instance._defaultauthServerConfig, strAuthorizationCode, session, jWTParser, strStoredNonce,null );
    }

    /**
     * Retieve a token using an authorization code
     * 
     * @param the
     *            strRedirectUri
     * @param strAuthorizationCode
     *            The authorization code
     * @param session
     *            The HTTP session
     * @return The token
     * @throws IOException
     *             if an error occurs
     * @throws HttpAccessException
     *             if an error occurs
     * @throws TokenValidationException
     *             If the token validation failed
     */
    public Token getToken( String strRedirectUri, AuthClientConf clientConfig, AuthServerConf authServerConf, String strAuthorizationCode, HttpSession session,
            JWTParser jWTParser, String strStoredNonce,String strCodeVerifier ) throws IOException, HttpAccessException, TokenValidationException
    {

        Token token = null;
        if ( strRedirectUri == null )
        {
            strRedirectUri = clientConfig.getRedirectUri( );
        }
        Map<String, String> mapParameters = new ConcurrentHashMap<String, String>( );
        mapParameters.put( Constants.PARAMETER_GRANT_TYPE, Constants.GRANT_TYPE_AUTHORIZATION_CODE );
        mapParameters.put( Constants.PARAMETER_CODE, strAuthorizationCode );
        mapParameters.put( Constants.PARAMETER_CLIENT_ID, clientConfig.getClientId( ) );
        if(!clientConfig.isPublic())
        {
        	mapParameters.put( Constants.PARAMETER_CLIENT_SECRET, clientConfig.getClientSecret( ) );
        }
        
        if( clientConfig.isPkce())
        {
        	mapParameters.put( Constants.PARAMETER_CODE_VERIFIER, strCodeVerifier );
        }
        
        if ( strRedirectUri != null )
        {
            mapParameters.put( Constants.PARAMETER_REDIRECT_URI, strRedirectUri );
        }

        HttpAccess httpAccess = new HttpAccess( );
        String strUrl = authServerConf.getTokenEndpointUri( );

        _logger.debug( "Posted URL : " + strUrl + "\nParameters :\n" + OauthUtils.traceMap( mapParameters ) );

        String strResponse = httpAccess.doPost( strUrl, mapParameters );
        _logger.debug( "Oauth2 response : " + strResponse );

        if ( !StringUtils.isEmpty( strResponse ) )
        {
            token = TokenService.getService( ).parse( strResponse, clientConfig, authServerConf, jWTParser, strStoredNonce );
        }
        return token;
    }

    /**
     *
     * Validate refresh token
     * 
     * @param clientConfig
     *            ClientConf
     * @param authServerConf
     *            AutConf
     * @param strRefreshToken
     *            refreshToken
     * @return true if the refresh token is already good
     *
     */
    public boolean validateRefreshToken( String strRefreshToken )
    {

        return validateRefreshToken( _instance._defaultClientConfig, _instance._defaultauthServerConfig, strRefreshToken );
    }

    /**
     *
     * Validate refresh token
     * 
     * @param clientConfig
     *            ClientConf
     * @param authServerConf
     *            AutConf
     * @param strRefreshToken
     *            refreshToken
     * @return true if the refresh token is already good
     *
     */
    public boolean validateRefreshToken( AuthClientConf clientConfig, AuthServerConf authServerConf, String strRefreshToken )
    {

        Map<String, String> mapParameters = new ConcurrentHashMap<String, String>( );
        Map<String, String> mapResponseHeader = new ConcurrentHashMap<String, String>( );
        mapParameters.put( Constants.PARAMETER_GRANT_TYPE, Constants.GRANT_TYPE_REFRESH_TOKEN );
        mapParameters.put( Constants.PARAMETER_REFRESH_TOKEN, strRefreshToken );
        mapParameters.put( Constants.PARAMETER_CLIENT_ID, clientConfig.getClientId( ) );
        mapParameters.put( Constants.PARAMETER_CLIENT_SECRET, clientConfig.getClientSecret( ) );
        HttpAccess httpAccess = new HttpAccess( );
        String strUrl = authServerConf.getTokenEndpointUri( );

        _logger.debug( "Validate Refresh Token : call URL  " + strUrl + "\nParameters :\n" + OauthUtils.traceMap( mapParameters ) );

        try
        {
            String strResponse = httpAccess.doPost( strUrl, mapParameters, null, null, mapResponseHeader );
            if ( !strResponse.contains( "\"error\"" ) )
            {
                return true;
            }
        }
        catch( HttpAccessException e )
        {

        }

        return false;
    }

    /**
     *
     * Get new Token using refresh token
     * 
     * @param clientConfig
     *            ClientConf
     * @param authServerConf
     *            AutConf
     * @param strRefreshToken
     *            refreshToken
     * @return true if the refresh token is already good
     *
     */
    public Token getTokenByRefreshToken( String strRefreshToken )
    {
        return getTokenByRefreshToken( _instance._defaultClientConfig, _instance._defaultauthServerConfig, strRefreshToken );
    }

    /**
     *
     * Get new Token using refresh token
     * 
     * @param clientConfig
     *            ClientConf
     * @param authServerConf
     *            AutConf
     * @param strRefreshToken
     *            refreshToken
     * @return true if the refresh token is already good
     *
     */
    public Token getTokenByRefreshToken( AuthClientConf clientConfig, AuthServerConf authServerConf, String strRefreshToken )
    {

        Map<String, String> mapParameters = new ConcurrentHashMap<String, String>( );
        Map<String, String> mapResponseHeader = new ConcurrentHashMap<String, String>( );
        mapParameters.put( Constants.PARAMETER_GRANT_TYPE, Constants.GRANT_TYPE_REFRESH_TOKEN );
        mapParameters.put( Constants.PARAMETER_REFRESH_TOKEN, strRefreshToken );
        mapParameters.put( Constants.PARAMETER_CLIENT_ID, clientConfig.getClientId( ) );
        mapParameters.put( Constants.PARAMETER_CLIENT_SECRET, clientConfig.getClientSecret( ) );
        Token newToken = null;
        HttpAccess httpAccess = new HttpAccess( );
        String strUrl = authServerConf.getTokenEndpointUri( );

        _logger.debug( "Get Token By Refresh Token : call URL  " + strUrl + "\nParameters :\n" + OauthUtils.traceMap( mapParameters ) );

        try
        {
            String strResponse = httpAccess.doPost( strUrl, mapParameters, null, null, mapResponseHeader );
            if ( !StringUtils.isEmpty( strResponse ) && !strResponse.contains( "\"error\"" ) )
            {
                newToken = TokenService.getService( ).parse( strResponse, clientConfig, authServerConf, null, null );
            }
        }
        catch( IOException e )
        {
            // TODO Auto-generated catch block
            _logger.error( "Error getting new Token using refresh token", e );
        }
        catch( TokenValidationException e )
        {
            // TODO Auto-generated catch block
            _logger.error( "Error getting new Token using refresh token", e );
        }
        catch( HttpAccessException e )
        {

        }

        return newToken;
    }

    /**
     * parse the JSON for a token
     *
     * @param strJson
     *            The JSON
     * @param clientConfig
     *            The client configuration
     * @param serverConfig
     *            The server configuration
     * @param strStoredNonce
     *            The stored nonce
     * @return The Token
     * @throws java.io.IOException
     *             if an error occurs
     * @throws TokenValidationException
     *             If the token validation failed
     */
    public Token parse( String strJson, AuthClientConf clientConfig, AuthServerConf serverConfig, JWTParser jwtParser, String strStoredNonce )
            throws IOException, TokenValidationException
    {
        Token token = parseToken( strJson );

        _logger.debug( token );

        if ( jwtParser != null && serverConfig.isEnableJwtParser( ) )
        {
            jwtParser.parseJWT( token, clientConfig, serverConfig, strStoredNonce, _logger );
        }
        return token;
    }

    /**
     * Parse the Token from a JSON string
     * 
     * @param strJson
     *            The JSON string
     * @return The Token
     * @throws IOException
     *             if an error occurs
     */
    Token parseToken( String strJson ) throws IOException
    {
        return MapperService.parse( strJson, Token.class );
    }

    public static TokenService getService( )
    {
        if ( _instance == null )
        {

            _instance = new TokenService( SpringContextService.getBean( BEAN_AUTH_CLIENT_CONF ), SpringContextService.getBean( BEAN_AUTH_SERVER_CONF ) );
        }
        return _instance;

    }

}