200 lines
7.5 KiB
Java
200 lines
7.5 KiB
Java
package org.gcube.portal.oauth;
|
|
|
|
import static org.gcube.common.authorization.client.Constants.authorizationService;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.URLDecoder;
|
|
|
|
import javax.inject.Singleton;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.ws.rs.Consumes;
|
|
import javax.ws.rs.FormParam;
|
|
import javax.ws.rs.GET;
|
|
import javax.ws.rs.POST;
|
|
import javax.ws.rs.Path;
|
|
import javax.ws.rs.Produces;
|
|
import javax.ws.rs.core.Context;
|
|
import javax.ws.rs.core.MediaType;
|
|
import javax.ws.rs.core.Response;
|
|
import javax.ws.rs.core.Response.Status;
|
|
import javax.xml.bind.DatatypeConverter;
|
|
|
|
import org.gcube.common.authorization.library.ClientType;
|
|
import org.gcube.portal.oauth.cache.MemCachedBean;
|
|
import org.gcube.portal.oauth.output.AccessTokenBeanResponse;
|
|
import org.gcube.portal.oauth.output.AccessTokenErrorResponse;
|
|
import org.json.simple.JSONObject;
|
|
import org.json.simple.parser.JSONParser;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import net.spy.memcached.MemcachedClient;
|
|
|
|
|
|
@Path("/v2")
|
|
@Singleton
|
|
public class OauthService {
|
|
|
|
public static final String OAUTH_TOKEN_GET_METHOD_NAME_REQUEST = "access-token";
|
|
private static final String GRANT_TYPE_VALUE = "authorization_code";
|
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
|
|
|
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(OauthService.class);
|
|
|
|
/**
|
|
* This map contains couples <code, {qualifier-token, insert time, scope, redirect uri, client id}>
|
|
*/
|
|
private MemcachedClient entries;
|
|
|
|
|
|
|
|
/**
|
|
* 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() {
|
|
logger.info("Singleton gcube-oauth service built.");
|
|
entries = DistributedCacheClient.getInstance().getMemcachedClient();
|
|
}
|
|
|
|
@Override
|
|
protected void finalize(){
|
|
entries.shutdown();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
@GET
|
|
@Path("check")
|
|
@Produces(MediaType.TEXT_PLAIN)
|
|
public Response checkService(){
|
|
return Response.status(Status.OK).entity("Ready!").build();
|
|
}
|
|
|
|
@POST
|
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
@Path(OAUTH_TOKEN_GET_METHOD_NAME_REQUEST)
|
|
/**
|
|
* 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,
|
|
@FormParam("grant_type") String grantType, // it must always be equal to "authorization_code"
|
|
@Context HttpServletRequest request
|
|
){
|
|
|
|
Status status = Status.BAD_REQUEST;
|
|
logger.info("Request to exchange code for token");
|
|
|
|
|
|
|
|
try{
|
|
CredentialsBean credentials = new CredentialsBean(clientId, clientSecret);
|
|
|
|
if (clientId == null)
|
|
credentials = getCredentialFromBasicAuthorization(request);
|
|
else if (request.getHeader(AUTHORIZATION_HEADER)!=null)
|
|
throw new Exception("the client MUST NOT use more than one authentication method");
|
|
|
|
logger.info("Params are client_id = " + credentials.getClientId() + ", client_secret = " + credentials.getClientSecret() +
|
|
"*******************"+ ", redirect_uri = " +redirectUri + ", code = " + code + "*******************" + ", grant_type = " + grantType);
|
|
|
|
// check if something is missing
|
|
MemCachedBean cachedBean = checkRequest(credentials, redirectUri, code, grantType, request);
|
|
|
|
if(!cachedBean.isSuccess()){
|
|
String errorMessage = cachedBean.getErrorMessage();
|
|
logger.error("The request fails because of " + errorMessage);
|
|
return Response.status(status).entity(new AccessTokenErrorResponse(errorMessage, null)).build();
|
|
}else{
|
|
logger.info("The request is ok");
|
|
String tokenToReturn = cachedBean.getToken();
|
|
String scope = cachedBean.getScope();
|
|
status = Status.OK;
|
|
return Response.status(status).entity(new AccessTokenBeanResponse(tokenToReturn, scope)).build();
|
|
}
|
|
}catch(Exception e){
|
|
logger.error("Failed to perform this operation", e);
|
|
status = Status.BAD_REQUEST;
|
|
return Response.status(status).entity(new AccessTokenErrorResponse("invalid_request", null)).build();
|
|
}
|
|
}
|
|
|
|
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(":");
|
|
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();
|
|
}
|
|
return new CredentialsBean(clientId, clientSecret);
|
|
|
|
}
|
|
|
|
/**
|
|
* Check request parameters
|
|
* @param clientId
|
|
* @param clientSecret
|
|
* @param redirectUri
|
|
* @param code
|
|
* @param grantType
|
|
* @return see https://tools.ietf.org/html/rfc6749#section-5.2
|
|
*/
|
|
private MemCachedBean checkRequest(CredentialsBean credentials,
|
|
String redirectUri, String code, String grantType, HttpServletRequest request) {
|
|
try{
|
|
if(credentials.getClientId() == null || credentials.getClientSecret() == null || redirectUri == null || code == null || grantType == null )
|
|
return new MemCachedBean("invalid_request");
|
|
if(credentials.getClientId().isEmpty() || credentials.getClientSecret().isEmpty() || redirectUri.isEmpty() || code.isEmpty() || grantType.isEmpty())
|
|
return new MemCachedBean("invalid_request");
|
|
if(!checkIsapplicationTokenType(authorizationService().get(credentials.getClientSecret()).getClientInfo().getType())) // it is not an app token or it is not a token
|
|
return new MemCachedBean("invalid_client");
|
|
if(entries.get(code) == null)
|
|
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");
|
|
if(!grantType.equals(GRANT_TYPE_VALUE))
|
|
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);
|
|
}catch(Exception e){
|
|
logger.error("Failed to check the correctness of the request", e);
|
|
return new MemCachedBean("invalid_request");
|
|
}
|
|
}
|
|
}
|