175 lines
6.1 KiB
Java
175 lines
6.1 KiB
Java
|
package org.gcube.portal.oauth;
|
||
|
|
||
|
import static org.gcube.common.authorization.client.Constants.authorizationService;
|
||
|
|
||
|
import java.util.Map;
|
||
|
import java.util.concurrent.ConcurrentHashMap;
|
||
|
|
||
|
import javax.inject.Singleton;
|
||
|
import javax.ws.rs.Consumes;
|
||
|
import javax.ws.rs.FormParam;
|
||
|
import javax.ws.rs.POST;
|
||
|
import javax.ws.rs.Path;
|
||
|
import javax.ws.rs.Produces;
|
||
|
import javax.ws.rs.core.MediaType;
|
||
|
import javax.ws.rs.core.Response;
|
||
|
import javax.ws.rs.core.Response.Status;
|
||
|
|
||
|
import org.gcube.common.authorization.library.ClientType;
|
||
|
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
|
||
|
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
|
||
|
import org.gcube.common.authorization.library.utils.Caller;
|
||
|
import org.gcube.portal.oauth.cache.CacheBean;
|
||
|
import org.gcube.portal.oauth.cache.CacheCleaner;
|
||
|
import org.gcube.portal.oauth.input.PushCodeBean;
|
||
|
import org.gcube.portal.oauth.output.AccessTokenBeanResponse;
|
||
|
import org.gcube.portal.oauth.output.AccessTokenErrorResponse;
|
||
|
import org.slf4j.LoggerFactory;
|
||
|
|
||
|
|
||
|
@Path("v2/")
|
||
|
@Singleton
|
||
|
public class OauthService {
|
||
|
|
||
|
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(OauthService.class);
|
||
|
|
||
|
/**
|
||
|
* This map contains couples <code, {qualifier-token, insert time}>
|
||
|
*/
|
||
|
private Map<String, CacheBean> entries = new ConcurrentHashMap<String, CacheBean>();
|
||
|
|
||
|
/**
|
||
|
* 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() {
|
||
|
CacheCleaner cleaner = new CacheCleaner(entries);
|
||
|
cleaner.start();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used to check that the token type is of type user
|
||
|
* @param clientType
|
||
|
* @param token
|
||
|
* @return
|
||
|
*/
|
||
|
private boolean checkIsQualifierTokenType(ClientType clientType){
|
||
|
return clientType.equals(ClientType.USER);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
|
|
||
|
@POST
|
||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||
|
@Produces(MediaType.APPLICATION_JSON)
|
||
|
@Path("push-authentication-code")
|
||
|
/**
|
||
|
* The portal will push a qualified token together a code
|
||
|
* @return Response with status 201 if the code has been saved correctly
|
||
|
*/
|
||
|
public Response pushAuthCode(PushCodeBean bean) {
|
||
|
|
||
|
logger.info("Request to push ");
|
||
|
|
||
|
Caller caller = AuthorizationProvider.instance.get();
|
||
|
String token = SecurityTokenProvider.instance.get();
|
||
|
Status status = Status.CREATED;
|
||
|
|
||
|
if(!checkIsQualifierTokenType(caller.getClient().getType())){
|
||
|
status = Status.FORBIDDEN;
|
||
|
logger.warn("Trying to access users method via a token different than USER is not allowed");
|
||
|
return Response.status(status).entity("{\"error\"=\"Trying to access push-authentication-code method via a token different than USER is not allowed\"").build();
|
||
|
}else{
|
||
|
|
||
|
logger.info("Saving entry defined by " + bean + " in cache, token is " + token.substring(0, 10));
|
||
|
entries.put(bean.getCode(), new CacheBean(token, bean.getRedirectUri(), bean.getClientId(), System.currentTimeMillis()));
|
||
|
return Response.status(status).build();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
@POST
|
||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||
|
@Produces(MediaType.APPLICATION_JSON)
|
||
|
@Path("access-token")
|
||
|
/**
|
||
|
* 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"
|
||
|
){
|
||
|
|
||
|
Status status = Status.BAD_REQUEST;
|
||
|
logger.info("Request to exchange code for token");
|
||
|
logger.info("Params are + client_id = " + clientId + ", client_secret = " + clientSecret.substring(0, 10) + ", redirect_uri = " +redirectUri + ", code = " + code.substring(0, 10));
|
||
|
|
||
|
try{
|
||
|
// check if something is missing
|
||
|
String errorMessage = checkRequest(clientId, clientSecret, redirectUri, code, grantType);
|
||
|
if(errorMessage != null){
|
||
|
logger.error("The request fails because of " + errorMessage);
|
||
|
return Response.status(status).entity(new AccessTokenErrorResponse(errorMessage, null, null)).build();
|
||
|
}else{
|
||
|
logger.info("The request is ok");
|
||
|
String tokenToReturn = entries.get(code).getToken();
|
||
|
status = Status.OK;
|
||
|
return Response.status(status).entity(new AccessTokenBeanResponse(tokenToReturn, authorizationService().get(tokenToReturn).getContext())).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, null)).build();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 String checkRequest(String clientId, String clientSecret,
|
||
|
String redirectUri, String code, String grantType) {
|
||
|
|
||
|
try{
|
||
|
if(clientId == null || clientSecret == null || redirectUri == null || code == null || grantType == null)
|
||
|
return "invalid_request";
|
||
|
if(clientId.isEmpty() || clientSecret.isEmpty() || redirectUri.isEmpty() || code.isEmpty() || grantType.isEmpty())
|
||
|
return "invalid_request";
|
||
|
if(!checkIsapplicationTokenType(authorizationService().get(clientSecret).getClientInfo().getType())) // it is not an app token or it is not a token
|
||
|
return "invalid_client";
|
||
|
if(!entries.containsKey(code) || CacheBean.isExpired(entries.get(code)))
|
||
|
return "invalid_grant";
|
||
|
if(!grantType.equals("authorization_code"))
|
||
|
return "unsupported_grant_type";
|
||
|
return null;
|
||
|
}catch(Exception e){
|
||
|
logger.error("Failed to check the correctness of the request", e);
|
||
|
return "invalid_request";
|
||
|
}
|
||
|
}
|
||
|
}
|