VREApp-Integration-portlet/src/main/java/org/gcube/portlets/user/bluecloud/DdasVreIntegration.java

382 lines
15 KiB
Java

package org.gcube.portlets.user.bluecloud;
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.servlet.http.HttpServletRequest;
import org.gcube.common.encryption.encrypter.StringEncrypter;
import org.gcube.common.portal.PortalContext;
import org.gcube.common.resources.gcore.ServiceEndpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.oidc.rest.JWTToken;
import org.gcube.oidc.rest.OpenIdConnectConfiguration;
import org.gcube.oidc.rest.OpenIdConnectRESTHelper;
import org.gcube.oidc.rest.OpenIdConnectRESTHelperException;
import org.gcube.portal.oidc.lr62.LiferayOpenIdConnectConfiguration;
import org.gcube.portal.oidc.lr62.OIDCUmaUtil;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.PrefsPropsUtil;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.bridges.mvc.MVCPortlet;
import net.spy.memcached.MemcachedClient;
/**
* Portlet implementation class DdasVreIntegration
*/
public class DdasVreIntegration extends MVCPortlet {
private static com.liferay.portal.kernel.log.Log log = LogFactoryUtil.getLog(DdasVreIntegration.class);
private static final String BC_BROKER_RESOURCE_NAME = "Blue-Cloud-DataDownloadAndAccess";
private static final String CATEGORY_NAME = "Service";
private static final String BROKER_LOGIN_PROPERTY_NAME = "login";
private static final String BROKER_FEEDBACK_PROPERTY_NAME = "download-ready";
public static final String CONDUCTOR_WORKFLOW_NAME = "da_cache_to_shub";
private static final int CONNECTION_TIMEOUT = 10000;
private static final int READ_TIMEOUT = 15000;
private static String ENCODED_OTP_PARAM = "b3Rw";
private static final String OPERATION_ERROR = "/html/error_pages/operation-error.jsp";
private static final String TOKEN_EXPIRED = "/html/error_pages/bc-token-expired.jsp";
/**
*
*/
public void render(RenderRequest renderRequest, RenderResponse response) throws PortletException, IOException {
HttpServletRequest httpReq = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(renderRequest));
String[] params = ParamUtil.getParameterValues(httpReq, ENCODED_OTP_PARAM);
boolean result = false;
if (params != null && params.length > 0) {
String otp = params[0];
log.debug("Read Blue-Cloud cache OTP="+otp);
if (otp != null && !otp.equals("")) {
JsonElement brokerRequest = checkRequest(otp);
if (brokerRequest == null) {//the token is expired or there was no request, return to the JSP token expired page
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(TOKEN_EXPIRED);
dispatcher.include(renderRequest, response);
return;
}
GCubeUser currentUser = getCurrentUser(renderRequest);
log.debug("current user is + " + currentUser.getUsername() + " sending request to conductor ...");
try {
Integer userOrderNumber = brokerRequest.getAsJsonObject().get("orderNumber").getAsInt();
String userOrderName = brokerRequest.getAsJsonObject().get("userOrderName").getAsString();
renderRequest.setAttribute("orderData", userOrderName + ", order number " + userOrderNumber); //pass to the JSP
result = sendRequestToConductor(brokerRequest, currentUser, renderRequest);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
renderRequest.setAttribute("theResult", result); //pass to the JSP
if (!result) { //redirect to error page
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(OPERATION_ERROR);
dispatcher.include(renderRequest, response);
return;
}
} else {
//the token is missing in the request URL
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(TOKEN_EXPIRED);
dispatcher.include(renderRequest, response);
return;
}
}
response.setContentType("text/html");
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher("/html/ddasvreintegration/view.jsp");
dispatcher.include(renderRequest, response);
}
/**
*
* @param brokerRequest
* @param currentUser
* @param renderRequest
* @return
* @throws Exception
*/
private boolean sendRequestToConductor(JsonElement brokerRequest, GCubeUser currentUser, RenderRequest renderRequest) throws Exception {
String userEmail = currentUser.getEmail();
log.debug("current user email is + " + userEmail);
Callback theCallback = getVRECallbackURLFromServiceEndpoint(userEmail);
HttpServletRequest httpReq = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(renderRequest));
String context = "/"+PortalContext.getConfiguration().getInfrastructureName();
JWTToken userUMAToken = OIDCUmaUtil.getUMAToken(httpReq, currentUser.getUsername(), context);
ConductorRequestBean req = new ConductorRequestBean(userUMAToken.getAccessTokenString(), theCallback);
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(req);
jsonElement.getAsJsonObject().add("descriptor", brokerRequest); //append the original request as descriptor object
log.debug("\n\nsendRequestToConductor\n"+jsonElement.toString());
URL eventPublisherURL = new URL(PrefsPropsUtil.getString(PortalUtil.getDefaultCompanyId(), "d4science.event-broker-endpoint"));
return doPost(jsonElement, new URL(eventPublisherURL+CONDUCTOR_WORKFLOW_NAME));
}
/**
*
* @param payload
* @param endpoint
* @return
*/
private boolean doPost(JsonElement payload, URL endpoint) {
try {
OpenIdConnectConfiguration openIdConnectConfiguration = LiferayOpenIdConnectConfiguration.getConfiguration();
String clientId = openIdConnectConfiguration.getPortalClientId();
log.debug("Getting auth token for client '{}' if needed" + clientId);
JWTToken token = getAuthorizationToken(openIdConnectConfiguration);
log.debug("Performing HTTP POST to: {}" + endpoint);
HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(CONNECTION_TIMEOUT);
log.trace("HTTP connection timeout set to:" + connection.getConnectTimeout());
connection.setReadTimeout(READ_TIMEOUT);
log.trace("HTTP connection Read timeout set to:" + connection.getReadTimeout());
connection.setRequestProperty("Content-Type", "application/json");
// Commented out as per the Conductor issue: https://github.com/Netflix/conductor/issues/376
// connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
if (token != null) {
log.debug("Setting authorization header as:" + token.getAccessTokenAsBearer());
connection.setRequestProperty("Authorization", token.getAccessTokenAsBearer());
} else {
log.debug("Sending request without authorization header");
}
OutputStream os = connection.getOutputStream();
String jsonString = payload.toString();
log.trace("Sending event JSON:"+jsonString);
os.write(jsonString.getBytes("UTF-8"));
os.flush();
os.close();
StringBuilder sb = new StringBuilder();
boolean ok = true;
int httpResultCode = -1;
try {
httpResultCode = connection.getResponseCode();
log.trace("HTTP Response code: " + httpResultCode);
log.trace("Reading response");
InputStreamReader isr = null;
if (httpResultCode == HttpURLConnection.HTTP_OK) {
isr = new InputStreamReader(connection.getInputStream(), "UTF-8");
} else {
ok = false;
isr = new InputStreamReader(connection.getErrorStream(), "UTF-8");
}
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
isr.close();
} catch (java.net.SocketTimeoutException e) {
log.warn("Read timout may have occurred, however I think it went through anyways.");
return true;
}
if (ok) {
log.info("Post to Conductor is OK" + httpResultCode);
log.trace("Response message from server: {}" + sb.toString());
return true;
} else {
log.info("Post to Conductor is NOT OK" + httpResultCode);
log.trace("Response message from server: {}" + sb.toString());
return false;
}
} catch (Exception e) {
log.error("POSTing JSON to: " + endpoint, e);
return false;
}
}
protected JWTToken getAuthorizationToken(OpenIdConnectConfiguration openIdConnectConfiguration ) throws OpenIdConnectRESTHelperException {
String clientId = openIdConnectConfiguration.getPortalClientId();
String clientSecret = openIdConnectConfiguration.getPortalClientSecret();
URL tokenURL = openIdConnectConfiguration.getTokenURL();
if (clientId != null && clientSecret != null && tokenURL != null) {
log.debug("Getting OIDC token for clientId " + clientId + " from: "+ tokenURL);
return OpenIdConnectRESTHelper.queryClientToken(clientId, clientSecret, tokenURL);
} else {
log.debug("Can't get OIDC token since not all the required params were provied");
return null;
}
}
/**
* 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 JsonElement checkRequest(String code) {
try{
MemcachedClient entries = new DistributedCacheClient().getMemcachedClient();
if(entries.get(code) == null) {
log.error("Could not find OTP key in memcache");
return null;
}
log.debug("Got temp code (OTP) and looking into memcached for correspondance, "+code);
String brokerRequest = (String) entries.get(code);
System.out.println("\n\nbrokerRequest="+brokerRequest);
JsonElement jelement = new JsonParser().parse(brokerRequest);
JsonObject jobject = jelement.getAsJsonObject();
log.debug("Found tempCode into memcached, broker request="+jobject.toString());
log.debug("Invalidating tempCode into memcached with key="+code);
entries.delete(code);
log.debug("Invalidated key "+ code + " with success.");
return jelement;
} catch(Exception e){
log.error("Failed to check the correctness of the broker request", e);
return null;
}
}
public static GCubeUser getCurrentUser(RenderRequest request) {
long userId;
try {
userId = PortalUtil.getUser(request).getUserId();
return getCurrentUser(userId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static GCubeUser getCurrentUser(long userId) {
try {
return new LiferayUserManager().getUserById(userId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unused")
private class Callback {
URL url; // BROKER_FEEDBACK_ENDPOINT
String email; //where to send the email with the result report
String username; //the username to use to login using bearer token method
String password; //the password to use to login using bearer token method
URL authorize_url; //the endpoint to login and start the oAuth2 Bearer Token flow with the above credentials
public Callback(URL url, String email, String username, String password, URL authorize_url) {
super();
this.url = url;
this.email = email;
this.username = username;
this.password = password;
this.authorize_url = authorize_url;
}
}
@SuppressWarnings("unused")
private class ConductorRequestBean {
String token;
Callback callback;
public ConductorRequestBean(String token, Callback callback) {
super();
this.token = token;
this.callback = callback;
}
}
/**
* Retrieve the vre callbac URL to pass to the broker service in the response from the endpoint resource on IS
* @throws Exception
*/
private Callback getVRECallbackURLFromServiceEndpoint(String userEmail){
String currentScope = ScopeProvider.instance.get();
String infrastructure = "/"+PortalContext.getConfiguration().getInfrastructureName();
ScopeProvider.instance.set(infrastructure);
try{
SimpleQuery query = queryFor(ServiceEndpoint.class);
query.addCondition("$resource/Profile/Name/text() eq '"+ BC_BROKER_RESOURCE_NAME +"'");
query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY_NAME +"'");
DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
try {
List<ServiceEndpoint> list = client.submit(query);
if (list.size() > 1) {
System.out.println("Too many Service Endpoints having name " + BC_BROKER_RESOURCE_NAME +" in this scope having Category " + CATEGORY_NAME);
}
else if (list.size() == 0){
System.out.println("There is no Service Endpoint having name " + BC_BROKER_RESOURCE_NAME +" and Category " + CATEGORY_NAME + " in this context " + infrastructure);
}
else {
for (ServiceEndpoint res : list) {
AccessPoint[] accessPoints = (AccessPoint[]) res.profile().accessPoints().toArray(new AccessPoint[res.profile().accessPoints().size()]);
String username = null;
String password = null;
String brokerApiURL = null;
String brokerfeedbackEndpoint = null;
String brokerLoginEndpoint = null;
for (AccessPoint found : accessPoints) {
brokerApiURL = found.address();
log.debug("\nBC DD&AS AccessPoint found URL:" + brokerApiURL);
username = found.username();
String passEncrypted = found.password();
password = StringEncrypter.getEncrypter().decrypt(passEncrypted);
for (ServiceEndpoint.Property prop : found.properties()) {
if (prop.name().compareTo(BROKER_FEEDBACK_PROPERTY_NAME) == 0)
brokerfeedbackEndpoint = prop.value();
if (prop.name().compareTo(BROKER_LOGIN_PROPERTY_NAME) == 0)
brokerLoginEndpoint = prop.value();
}
}
String brokerAPILoginURL = brokerApiURL + brokerLoginEndpoint; //https://data.blue-cloud.org/api + /login
String brokerFeedbackURL = brokerApiURL + brokerfeedbackEndpoint; //https://data.blue-cloud.org/api + /vre/download-ready
Callback toReturn = new Callback(new URL(brokerFeedbackURL), userEmail, username, password, new URL(brokerAPILoginURL));
return toReturn;
}
}
} catch (Exception e) {
e.printStackTrace();
}
} catch(Exception e){
log.error("There is no Service Endpoint having name: " + BC_BROKER_RESOURCE_NAME + " and Category " + CATEGORY_NAME + " on root context");
}finally{
ScopeProvider.instance.set(currentScope);
}
ScopeProvider.instance.set(currentScope);
return null;
}
}