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); //The Blue-Cloud DD&AS Service endpoint for feedback about downloads public static final String BROKERf_FEEDBACK_ENDPOINT = "https://data.blue-cloud.org/api/vre/download-ready"; 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]; System.out.println("Read 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()); 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 client = clientFor(ServiceEndpoint.class); try { List 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; } }