2017-01-27 16:12:57 +01:00
package org.gcube.portal.oauth ;
import static org.gcube.common.authorization.client.Constants.authorizationService ;
2019-08-21 09:46:15 +02:00
import java.io.UnsupportedEncodingException ;
2019-03-21 14:39:33 +01:00
import java.net.URLDecoder ;
2017-01-27 16:12:57 +01:00
import javax.inject.Singleton ;
2019-03-21 14:39:33 +01:00
import javax.servlet.http.HttpServletRequest ;
2017-01-27 16:12:57 +01:00
import javax.ws.rs.Consumes ;
import javax.ws.rs.FormParam ;
2017-01-29 18:11:55 +01:00
import javax.ws.rs.GET ;
2017-01-27 16:12:57 +01:00
import javax.ws.rs.POST ;
import javax.ws.rs.Path ;
import javax.ws.rs.Produces ;
2019-03-21 14:39:33 +01:00
import javax.ws.rs.core.Context ;
2017-01-27 16:12:57 +01:00
import javax.ws.rs.core.MediaType ;
import javax.ws.rs.core.Response ;
import javax.ws.rs.core.Response.Status ;
2019-03-21 14:39:33 +01:00
import javax.xml.bind.DatatypeConverter ;
2017-01-27 16:12:57 +01:00
import org.gcube.common.authorization.library.ClientType ;
2019-09-16 16:37:05 +02:00
import org.gcube.portal.oauth.cache.MemCachedBean ;
2017-01-27 16:12:57 +01:00
import org.gcube.portal.oauth.output.AccessTokenBeanResponse ;
import org.gcube.portal.oauth.output.AccessTokenErrorResponse ;
2019-09-16 16:37:05 +02:00
import org.json.simple.JSONObject ;
import org.json.simple.parser.JSONParser ;
2017-01-27 16:12:57 +01:00
import org.slf4j.LoggerFactory ;
2019-08-21 09:46:15 +02:00
import net.spy.memcached.MemcachedClient ;
2017-01-27 16:12:57 +01:00
2017-01-29 18:11:55 +01:00
@Path ( " /v2 " )
2017-01-27 16:12:57 +01:00
@Singleton
public class OauthService {
2017-01-29 18:11:55 +01:00
public static final String OAUTH_TOKEN_GET_METHOD_NAME_REQUEST = " access-token " ;
private static final String GRANT_TYPE_VALUE = " authorization_code " ;
2019-03-21 14:39:33 +01:00
private static final String AUTHORIZATION_HEADER = " Authorization " ;
2019-09-16 16:37:05 +02:00
2017-01-27 16:12:57 +01:00
private static final org . slf4j . Logger logger = LoggerFactory . getLogger ( OauthService . class ) ;
/ * *
2017-02-01 16:03:48 +01:00
* This map contains couples < code , { qualifier - token , insert time , scope , redirect uri , client id } >
2017-01-27 16:12:57 +01:00
* /
2019-08-21 09:46:15 +02:00
private MemcachedClient entries ;
2017-01-27 16:12:57 +01:00
2017-01-29 18:54:43 +01:00
2017-01-27 16:12:57 +01:00
/ * *
* Since this is a singleton sub - service , there will be just one call to this constructor and one running thread
* to clean up expired codes .
* /
public OauthService ( ) {
2017-01-29 18:11:55 +01:00
logger . info ( " Singleton gcube-oauth service built. " ) ;
2019-08-21 09:46:15 +02:00
entries = DistributedCacheClient . getInstance ( ) . getMemcachedClient ( ) ;
2017-01-27 16:12:57 +01:00
}
2017-01-29 18:54:43 +01:00
@Override
protected void finalize ( ) {
2019-08-21 09:46:15 +02:00
entries . shutdown ( ) ;
2017-01-29 18:54:43 +01:00
}
2017-01-27 16:12:57 +01:00
/ * *
* Used to check that the token type is of type application
* @param clientType
* @param token
* @return
* /
private boolean checkIsapplicationTokenType ( ClientType clientType ) {
return clientType . equals ( ClientType . EXTERNALSERVICE ) ;
}
2017-01-29 18:54:43 +01:00
2017-01-29 18:11:55 +01:00
@GET
@Path ( " check " )
@Produces ( MediaType . TEXT_PLAIN )
public Response checkService ( ) {
return Response . status ( Status . OK ) . entity ( " Ready! " ) . build ( ) ;
}
2017-01-27 16:12:57 +01:00
@POST
@Consumes ( MediaType . APPLICATION_FORM_URLENCODED )
@Produces ( MediaType . APPLICATION_JSON )
2017-01-29 18:11:55 +01:00
@Path ( OAUTH_TOKEN_GET_METHOD_NAME_REQUEST )
2017-01-27 16:12:57 +01:00
/ * *
* The method should accept input values or in a json object or as FormParam . The request is validated here and not from SmartGears .
* @param requestInJson
* @param clientId
* @param clientSecret
* @param redirectUri
* @param code
* @param grantType
* @return
* /
public Response tokenRequest (
@FormParam ( " client_id " ) String clientId ,
@FormParam ( " client_secret " ) String clientSecret , // i.e., application token
@FormParam ( " redirect_uri " ) String redirectUri ,
@FormParam ( " code " ) String code ,
2019-03-21 14:39:33 +01:00
@FormParam ( " grant_type " ) String grantType , // it must always be equal to "authorization_code"
@Context HttpServletRequest request
2017-01-27 16:12:57 +01:00
) {
Status status = Status . BAD_REQUEST ;
logger . info ( " Request to exchange code for token " ) ;
2019-03-21 14:39:33 +01:00
2017-01-27 16:12:57 +01:00
try {
2019-03-21 14:39:33 +01:00
CredentialsBean credentials = new CredentialsBean ( clientId , clientSecret ) ;
if ( clientId = = null )
credentials = getCredentialFromBasicAuthorization ( request ) ;
else if ( request . getHeader ( AUTHORIZATION_HEADER ) ! = null )
2019-03-21 17:16:39 +01:00
throw new Exception ( " the client MUST NOT use more than one authentication method " ) ;
2019-03-21 14:39:33 +01:00
logger . info ( " Params are client_id = " + credentials . getClientId ( ) + " , client_secret = " + credentials . getClientSecret ( ) +
" ******************* " + " , redirect_uri = " + redirectUri + " , code = " + code + " ******************* " + " , grant_type = " + grantType ) ;
2017-01-27 16:12:57 +01:00
// check if something is missing
2019-09-16 16:37:05 +02:00
MemCachedBean cachedBean = checkRequest ( credentials , redirectUri , code , grantType , request ) ;
2019-03-21 14:39:33 +01:00
2019-09-16 16:37:05 +02:00
if ( ! cachedBean . isSuccess ( ) ) {
String errorMessage = cachedBean . getErrorMessage ( ) ;
2017-01-27 16:12:57 +01:00
logger . error ( " The request fails because of " + errorMessage ) ;
2017-02-03 15:46:49 +01:00
return Response . status ( status ) . entity ( new AccessTokenErrorResponse ( errorMessage , null ) ) . build ( ) ;
2017-01-27 16:12:57 +01:00
} else {
logger . info ( " The request is ok " ) ;
2019-09-16 16:37:05 +02:00
String tokenToReturn = cachedBean . getToken ( ) ;
String scope = cachedBean . getScope ( ) ;
2017-01-27 16:12:57 +01:00
status = Status . OK ;
2017-01-27 16:19:39 +01:00
return Response . status ( status ) . entity ( new AccessTokenBeanResponse ( tokenToReturn , scope ) ) . build ( ) ;
2017-01-27 16:12:57 +01:00
}
} catch ( Exception e ) {
logger . error ( " Failed to perform this operation " , e ) ;
status = Status . BAD_REQUEST ;
2017-02-03 15:46:49 +01:00
return Response . status ( status ) . entity ( new AccessTokenErrorResponse ( " invalid_request " , null ) ) . build ( ) ;
2017-01-27 16:12:57 +01:00
}
}
2019-03-21 14:39:33 +01:00
private CredentialsBean getCredentialFromBasicAuthorization ( HttpServletRequest request ) {
String basicAuthorization = request . getHeader ( AUTHORIZATION_HEADER ) ;
String base64Credentials = basicAuthorization . substring ( " Basic " . length ( ) ) . trim ( ) ;
String credentials = new String ( DatatypeConverter . parseBase64Binary ( base64Credentials ) ) ;
// credentials = username:password
String [ ] splitCreds = credentials . split ( " : " ) ;
2019-08-21 09:46:15 +02:00
String clientId = null ;
String clientSecret = null ;
try {
clientId = URLDecoder . decode ( splitCreds [ 0 ] , java . nio . charset . StandardCharsets . UTF_8 . toString ( ) ) ;
clientSecret = URLDecoder . decode ( splitCreds [ 1 ] , java . nio . charset . StandardCharsets . UTF_8 . toString ( ) ) ;
} catch ( UnsupportedEncodingException e ) {
e . printStackTrace ( ) ;
}
2019-03-21 14:39:33 +01:00
return new CredentialsBean ( clientId , clientSecret ) ;
}
2017-01-27 16:12:57 +01:00
/ * *
* Check request parameters
* @param clientId
* @param clientSecret
* @param redirectUri
* @param code
* @param grantType
* @return see https : //tools.ietf.org/html/rfc6749#section-5.2
* /
2019-09-16 16:37:05 +02:00
private MemCachedBean checkRequest ( CredentialsBean credentials ,
2019-03-21 14:39:33 +01:00
String redirectUri , String code , String grantType , HttpServletRequest request ) {
2017-01-27 16:12:57 +01:00
try {
2019-03-21 14:39:33 +01:00
if ( credentials . getClientId ( ) = = null | | credentials . getClientSecret ( ) = = null | | redirectUri = = null | | code = = null | | grantType = = null )
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " invalid_request " ) ;
2019-03-21 14:39:33 +01:00
if ( credentials . getClientId ( ) . isEmpty ( ) | | credentials . getClientSecret ( ) . isEmpty ( ) | | redirectUri . isEmpty ( ) | | code . isEmpty ( ) | | grantType . isEmpty ( ) )
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " invalid_request " ) ;
2019-03-21 14:39:33 +01:00
if ( ! checkIsapplicationTokenType ( authorizationService ( ) . get ( credentials . getClientSecret ( ) ) . getClientInfo ( ) . getType ( ) ) ) // it is not an app token or it is not a token
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " invalid_client " ) ;
2019-08-21 09:46:15 +02:00
if ( entries . get ( code ) = = null )
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " invalid_grant " ) ;
logger . debug ( " Got tempCode and looking into memcached for correspondance, " + code ) ;
JSONParser parser = new JSONParser ( ) ;
JSONObject json = ( JSONObject ) parser . parse ( ( String ) entries . get ( code ) ) ;
String cachedRedirectUri = ( String ) json . get ( " redirect_uri " ) ;
String cachedClientId = ( String ) json . get ( " client_id " ) ;
logger . debug ( " Found tempCode into memcached, cachedClientId= " + cachedClientId ) ;
if ( ! cachedRedirectUri . equals ( redirectUri ) | | ! cachedClientId . equals ( credentials . getClientId ( ) ) )
return new MemCachedBean ( " invalid_grant " ) ;
2017-01-29 18:11:55 +01:00
if ( ! grantType . equals ( GRANT_TYPE_VALUE ) )
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " unsupported_grant_type " ) ;
String cachedToken = ( String ) json . get ( " token " ) ;
String cachedContext = ( String ) json . get ( " context " ) ;
logger . debug ( " Returning cachedToken= " + cachedToken + " and cachedContext= " + cachedContext ) ;
return new MemCachedBean ( cachedToken , cachedContext , null ) ;
2017-01-27 16:12:57 +01:00
} catch ( Exception e ) {
logger . error ( " Failed to check the correctness of the request " , e ) ;
2019-09-16 16:37:05 +02:00
return new MemCachedBean ( " invalid_request " ) ;
2017-01-27 16:12:57 +01:00
}
}
}