diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/InvokerHandler.java b/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/InvokerHandler.java deleted file mode 100644 index cff1725b50..0000000000 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/InvokerHandler.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. - * - * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -package io.entgra.request.handler; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.entgra.request.handler.beans.AuthData; -import io.entgra.request.handler.util.HandlerConstants; -import io.entgra.request.handler.util.HandlerUtil; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.wso2.carbon.device.application.mgt.common.ProxyResponse; - -import javax.servlet.annotation.MultipartConfig; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; - -import static io.entgra.request.handler.util.HandlerUtil.execute; -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; - -@MultipartConfig -@WebServlet("/invoke") -public class InvokerHandler extends HttpServlet { - private static final Log log = LogFactory.getLog(LoginHandler.class); - private static final long serialVersionUID = -6508020875358160165L; - private static AuthData authData; - private static String apiEndpoint; - private static String method; - private static String serverUrl; - private static String platform; - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) { - try { - if (!validateRequest(req, resp)) { - return; - } - HttpRequestBase executor = constructExecutor(req); - if (executor == null) { - resp.sendError(HTTP_BAD_REQUEST, "Bad Request, method: " + method + " is not supported"); - return; - } - executor.setHeader(HandlerConstants.AUTHORIZATION_HEADER_KEY, "Bearer " + authData.getAccessToken()); - ProxyResponse proxyResponse = execute(executor); - - if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - if (!refreshToken(req, resp)) { - return; - } - executor.setHeader(HandlerConstants.AUTHORIZATION_HEADER_KEY, "Bearer " + authData.getAccessToken()); - proxyResponse = execute(executor); - if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while invoking the API after refreshing the token."); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return; - } - } - if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while invoking the API endpoint."); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return; - } - HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); - } catch (IOException e) { - log.error("Error occured when processing invoke call.", e); - } - } - - /*** - * - * @param req {@link HttpServletRequest} - * @return {@link HttpRequestBase} if method equals to either GET, POST, PUT or DELETE otherwise returns NULL. - */ - private HttpRequestBase constructExecutor(HttpServletRequest req) { - String payload = req.getParameter("payload"); - String contentType = req.getParameter("content-type"); - if (contentType == null || contentType.isEmpty()) { - contentType = ContentType.APPLICATION_JSON.toString(); - } - - HttpRequestBase executor; - if (HttpGet.METHOD_NAME.equalsIgnoreCase(method)) { - executor = new HttpGet(serverUrl + HandlerConstants.API_COMMON_CONTEXT + apiEndpoint); - } else if (HttpPost.METHOD_NAME.equalsIgnoreCase(method)) { - executor = new HttpPost(serverUrl + HandlerConstants.API_COMMON_CONTEXT + apiEndpoint); - StringEntity payloadEntity = new StringEntity(payload, ContentType.create(contentType)); - ((HttpPost) executor).setEntity(payloadEntity); - } else if (HttpPut.METHOD_NAME.equalsIgnoreCase(method)) { - executor = new HttpPut(serverUrl + HandlerConstants.API_COMMON_CONTEXT + apiEndpoint); - StringEntity payloadEntity = new StringEntity(payload, ContentType.create(contentType)); - ((HttpPut) executor).setEntity(payloadEntity); - } else if (HttpDelete.METHOD_NAME.equalsIgnoreCase(method)) { - executor = new HttpDelete(serverUrl + HandlerConstants.API_COMMON_CONTEXT + apiEndpoint); - } else { - return null; - } - return executor; - } - - /*** - * - * @param req {@link HttpServletRequest} - * @param resp {@link HttpServletResponse} - * @return If request is a valid one, returns TRUE, otherwise return FALSE - * @throws IOException If and error occurs while witting error response to client side - */ - private static boolean validateRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException { - serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort(); - apiEndpoint = req.getParameter("api-endpoint"); - method = req.getParameter("method"); - HttpSession session = req.getSession(false); - if (session == null) { - log.error("Unauthorized, You are not logged in. Please log in to the portal"); - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return false; - } - authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY); - platform = (String) session.getAttribute(HandlerConstants.PLATFORM); - if (authData == null) { - log.error("Unauthorized, Access token couldn't found in the current session"); - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return false; - } - - if (apiEndpoint == null || method == null) { - log.error("Bad Request, Either api-endpoint or method is empty"); - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_BAD_REQUEST); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_BAD_REQUEST)); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return false; - } - return true; - } - - /*** - * - * @param req {@link HttpServletRequest} - * @param resp {@link HttpServletResponse} - * @return If successfully renew tokens, returns TRUE otherwise return FALSE - * @throws IOException If and error occurs while witting error response to client side or invoke token renewal API - */ - private static boolean refreshToken(HttpServletRequest req, HttpServletResponse resp) throws IOException { - log.debug("refreshing the token"); - HttpPost tokenEndpoint = new HttpPost( - serverUrl + HandlerConstants.API_COMMON_CONTEXT + HandlerConstants.TOKEN_ENDPOINT); - HttpSession session = req.getSession(false); - if (session == null) { - log.error("Couldn't find a session, hence it is required to login and proceed."); - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return false; - } - - StringEntity tokenEndpointPayload = new StringEntity( - "grant_type=refresh_token&refresh_token=" + authData.getRefreshToken() + "&scope=PRODUCTION", - ContentType.APPLICATION_FORM_URLENCODED); - - tokenEndpoint.setEntity(tokenEndpointPayload); - String encodedClientApp = authData.getEncodedClientApp(); - tokenEndpoint.setHeader("Authorization", "Basic " + encodedClientApp); - tokenEndpoint.setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.toString()); - - ProxyResponse tokenResultResponse = execute(tokenEndpoint); - if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while refreshing access token."); - HandlerUtil.handleError(req, resp, serverUrl, platform, tokenResultResponse); - return false; - } - - JsonParser jsonParser = new JsonParser(); - JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); - - if (jTokenResult.isJsonObject()) { - JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); - AuthData newAuthData = new AuthData(); - - newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); - newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); - newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); - newAuthData.setClientId(authData.getClientId()); - newAuthData.setClientSecret(authData.getClientSecret()); - newAuthData.setEncodedClientApp(authData.getEncodedClientApp()); - newAuthData.setUsername(authData.getUsername()); - authData = newAuthData; - session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData); - return true; - } - - log.error("Error Occurred in token renewal process."); - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_INTERNAL_SERVER_ERROR)); - HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); - return false; - } -} diff --git a/components/request-handler/io.entgra.request.handler/pom.xml b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml similarity index 95% rename from components/request-handler/io.entgra.request.handler/pom.xml rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml index 88f207fd96..16090c509e 100644 --- a/components/request-handler/io.entgra.request.handler/pom.xml +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml @@ -21,13 +21,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - request-handler + ui-request-interceptor io.entgra.devicemgt 3.2.5-SNAPSHOT 4.0.0 - io.entgra.request.handler + io.entgra.ui.request.interceptor 3.2.5-SNAPSHOT war @@ -41,7 +41,7 @@ maven-war-plugin WEB-INF/lib/*cxf*.jar - api#application-mgt-handler#v1.0 + ui-request-handler @@ -66,7 +66,7 @@ - + diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java new file mode 100644 index 0000000000..1dd968ecd5 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.entgra.ui.request.interceptor; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.entgra.ui.request.interceptor.beans.AuthData; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.cookie.SM; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.entity.StringEntity; +import org.wso2.carbon.device.application.mgt.common.ProxyResponse; + +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Enumeration; + +import static io.entgra.ui.request.interceptor.util.HandlerUtil.execute; + +@MultipartConfig +@WebServlet( + name = "RequestHandlerServlet", + description = "This servlet intercepts the api requests initiated from the user interface and validate before" + + " forwarding to the backend", + urlPatterns = { + "/invoke/*" + } +) +public class InvokerHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(LoginHandler.class); + private static final long serialVersionUID = -6508020875358160165L; +// private static final HeaderGroup nonForwardingHeaders = new HeaderGroup(); + private static AuthData authData; + private static String apiEndpoint; + private static String serverUrl; + private static String platform; + +// static { +// // Initializing hop-by-hop headers to omit them from forwarding to the backend +// String[] headers = {HttpHeaders.CONNECTION, HttpHeaders.TRANSFER_ENCODING, HttpHeaders.PROXY_AUTHENTICATE, +// HttpHeaders.PROXY_AUTHORIZATION, HttpHeaders.UPGRADE, HttpHeaders.TE, HttpHeaders.TRAILER, +// HandlerConstants.KEEP_ALIVE, HandlerConstants.PUBLIC}; +// for (String header : headers) { +// nonForwardingHeaders.addHeader(new BasicHeader(header, null)); +// } +// } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpPost postRequest = new HttpPost(generateBackendRequestURL(req)); + if (StringUtils.isNotEmpty(req.getHeader(HttpHeaders.CONTENT_LENGTH)) || + StringUtils.isNotEmpty(req.getHeader(HttpHeaders.TRANSFER_ENCODING))) { + InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), + Long.parseLong(req.getHeader(HttpHeaders.CONTENT_LENGTH))); + postRequest.setEntity(entity); + } + copyRequestHeaders(req, postRequest); + postRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse proxyResponse = execute(postRequest); + + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { + proxyResponse = retryRequestWithRefreshedToken(req, resp, postRequest); + if (proxyResponse == null) { + return; + } + } + if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API endpoint."); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return; + } + HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); + } + } catch (IOException e) { + log.error("Error occurred when processing POST request.", e); + } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpGet getRequest = new HttpGet(generateBackendRequestURL(req)); + copyRequestHeaders(req, getRequest); + getRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse proxyResponse = execute(getRequest); + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { + proxyResponse = retryRequestWithRefreshedToken(req, resp, getRequest); + if (proxyResponse == null) { + return; + } + } + if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API endpoint."); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return; + } + HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); + } + } catch (IOException e) { + log.error("Error occurred when processing GET request.", e); + } + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpPut putRequest = new HttpPut(generateBackendRequestURL(req)); + if ((StringUtils.isNotEmpty(req.getHeader(HttpHeaders.CONTENT_LENGTH)) && + Double.parseDouble(req.getHeader(HttpHeaders.CONTENT_LENGTH)) > 0) || + StringUtils.isNotEmpty(req.getHeader(HttpHeaders.TRANSFER_ENCODING))) { + InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), + Long.parseLong(req.getHeader(HttpHeaders.CONTENT_LENGTH))); + putRequest.setEntity(entity); + } + copyRequestHeaders(req, putRequest); + putRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse proxyResponse = execute(putRequest); + + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { + proxyResponse = retryRequestWithRefreshedToken(req, resp, putRequest); + if (proxyResponse == null) { + return; + } + } + if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API endpoint."); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return; + } + HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); + } + } catch (IOException e) { + log.error("Error occurred when processing PUT request.", e); + } + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpDelete deleteRequest = new HttpDelete(generateBackendRequestURL(req)); + copyRequestHeaders(req, deleteRequest); + deleteRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse proxyResponse = execute(deleteRequest); + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { + proxyResponse = retryRequestWithRefreshedToken(req, resp, deleteRequest); + if (proxyResponse == null) { + return; + } + } + if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API endpoint."); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return; + } + HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); + } + } catch (IOException e) { + log.error("Error occurred when processing DELETE request.", e); + } + } + + private String generateBackendRequestURL(HttpServletRequest req) { + StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.append(serverUrl).append(HandlerConstants.API_COMMON_CONTEXT).append(apiEndpoint); + if (StringUtils.isNotEmpty(req.getQueryString())) { + urlBuilder.append("?").append(req.getQueryString()); + } + return urlBuilder.toString(); + } + + private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest) { + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || + headerName.equalsIgnoreCase(SM.COOKIE)) { + continue; + } + Enumeration headerValues = req.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + httpRequest.setHeader(headerName, headerValues.nextElement()); + } + } + } + /*** + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + * @return If request is a valid one, returns TRUE, otherwise return FALSE + * @throws IOException If and error occurs while witting error response to client side + */ + private static boolean validateRequest(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort(); + apiEndpoint = req.getPathInfo(); + HttpSession session = req.getSession(false); + if (session == null) { + log.error("Unauthorized, You are not logged in. Please log in to the portal"); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return false; + } + authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY); + platform = (String) session.getAttribute(HandlerConstants.PLATFORM); + if (authData == null) { + log.error("Unauthorized, Access token not found in the current session"); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return false; + } + + if (apiEndpoint == null || req.getMethod() == null) { + log.error("Bad Request, Either destination api-endpoint or method is empty"); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_BAD_REQUEST); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_BAD_REQUEST)); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return false; + } + return true; + } + + /** + * Retry request again after refreshing the access token + * + * @param req incoming {@link HttpServletRequest} + * @param resp resp {@link HttpServletResponse} + * @param httpRequest subclass of {@link HttpRequestBase} related to the current request. + * @return {@link ProxyResponse} if successful and null if failed. + * @throws IOException If an error occurs when try to retry the request. + */ + private static ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpServletResponse resp, + HttpRequestBase httpRequest) throws IOException { + if (refreshToken(req, resp)) { + httpRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse proxyResponse = execute(httpRequest); + if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API after refreshing the token."); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return null; + } + return proxyResponse; + } + return null; + } + + /*** + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + * @return If successfully renew tokens, returns TRUE otherwise return FALSE + * @throws IOException If an error occurs while witting error response to client side or invoke token renewal API + */ + private static boolean refreshToken(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + if (log.isDebugEnabled()) { + log.debug("refreshing the token"); + } + HttpPost tokenEndpoint = new HttpPost( + serverUrl + HandlerConstants.API_COMMON_CONTEXT + HandlerConstants.TOKEN_ENDPOINT); + HttpSession session = req.getSession(false); + if (session == null) { + log.error("Couldn't find a session, hence it is required to login and proceed."); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return false; + } + + StringEntity tokenEndpointPayload = new StringEntity( + "grant_type=refresh_token&refresh_token=" + authData.getRefreshToken() + "&scope=PRODUCTION", + ContentType.APPLICATION_FORM_URLENCODED); + + tokenEndpoint.setEntity(tokenEndpointPayload); + String encodedClientApp = authData.getEncodedClientApp(); + tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + + encodedClientApp); + tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + + ProxyResponse tokenResultResponse = execute(tokenEndpoint); + if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while refreshing access token."); + HandlerUtil.handleError(req, resp, serverUrl, platform, tokenResultResponse); + return false; + } + + JsonParser jsonParser = new JsonParser(); + JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); + + if (jTokenResult.isJsonObject()) { + JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); + AuthData newAuthData = new AuthData(); + + newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); + newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); + newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); + newAuthData.setClientId(authData.getClientId()); + newAuthData.setClientSecret(authData.getClientSecret()); + newAuthData.setEncodedClientApp(authData.getEncodedClientApp()); + newAuthData.setUsername(authData.getUsername()); + authData = newAuthData; + session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData); + return true; + } + + log.error("Error Occurred in token renewal process."); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_INTERNAL_SERVER_ERROR)); + HandlerUtil.handleError(req, resp, serverUrl, platform, proxyResponse); + return false; + } +} diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/LoginHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java similarity index 96% rename from components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/LoginHandler.java rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java index 051ee35504..5f1167073d 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/LoginHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java @@ -16,20 +16,21 @@ * under the License. */ -package io.entgra.request.handler; +package io.entgra.ui.request.interceptor; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; -import io.entgra.request.handler.beans.AuthData; -import io.entgra.request.handler.exceptions.LoginException; -import io.entgra.request.handler.util.HandlerConstants; -import io.entgra.request.handler.util.HandlerUtil; +import io.entgra.ui.request.interceptor.beans.AuthData; +import io.entgra.ui.request.interceptor.exceptions.LoginException; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.ui.request.interceptor.util.HandlerUtil; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -49,7 +50,7 @@ import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Base64; -import static io.entgra.request.handler.util.HandlerUtil.execute; +import static io.entgra.ui.request.interceptor.util.HandlerUtil.execute; @MultipartConfig @WebServlet("/login") @@ -120,7 +121,7 @@ public class LoginHandler extends HttpServlet { } else { // default login HttpPost apiRegEndpoint = new HttpPost(serverUrl + HandlerConstants.APP_REG_ENDPOINT); - apiRegEndpoint.setHeader(HandlerConstants.AUTHORIZATION, HandlerConstants.BASIC + Base64.getEncoder() + apiRegEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + Base64.getEncoder() .encodeToString((adminUsername + HandlerConstants.COLON + adminPwd).getBytes())); apiRegEndpoint.setHeader(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); apiRegEndpoint.setEntity(constructAppRegPayload(tags)); diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/beans/AuthData.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/AuthData.java similarity index 97% rename from components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/beans/AuthData.java rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/AuthData.java index 3ad73d6e8e..be6a8b570b 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/beans/AuthData.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/AuthData.java @@ -16,7 +16,7 @@ * under the License. */ -package io.entgra.request.handler.beans; +package io.entgra.ui.request.interceptor.beans; public class AuthData implements java.io.Serializable { diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/exceptions/LoginException.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/exceptions/LoginException.java similarity index 95% rename from components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/exceptions/LoginException.java rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/exceptions/LoginException.java index d792924fa4..b33b74da73 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/exceptions/LoginException.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/exceptions/LoginException.java @@ -16,7 +16,7 @@ * under the License. */ -package io.entgra.request.handler.exceptions; +package io.entgra.ui.request.interceptor.exceptions; public class LoginException extends Exception { public LoginException(String message) { diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java similarity index 90% rename from components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerConstants.java rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java index 6088adfae7..aa80d7fd66 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java @@ -16,20 +16,21 @@ * under the License. */ -package io.entgra.request.handler.util; +package io.entgra.ui.request.interceptor.util; public class HandlerConstants { public static final String PUBLISHER_APPLICATION_NAME = "application-mgt-publisher"; public static final String APP_REG_ENDPOINT = "/api-application-registration/register"; public static final String UI_CONFIG_ENDPOINT = "/api/application-mgt/v1.0/config/ui-config"; public static final String TOKEN_ENDPOINT = "/oauth2/token"; - public static final String AUTHORIZATION = "Authorization"; + public static final String PUBLIC = "Public"; + public static final String KEEP_ALIVE = "Keep-Alive"; public static final String BASIC = "Basic "; + public static final String BEARER = "Bearer "; public static final String COLON = ":"; public static final String TAGS_KEY = "tags"; public static final String APP_NAME_KEY = "applicationName"; public static final String SESSION_AUTH_DATA_KEY = "application-mgt"; - public static final String AUTHORIZATION_HEADER_KEY = "Authorization"; public static final String UI_CONFIG_KEY = "ui-config"; public static final String PLATFORM = "platform"; public static final String SERVER_HOST = "server-host"; diff --git a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerUtil.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java similarity index 92% rename from components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerUtil.java rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java index 357a9ac1ed..f3baea315f 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/java/io/entgra/request/handler/util/HandlerUtil.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java @@ -16,7 +16,7 @@ * under the License. */ -package io.entgra.request.handler.util; +package io.entgra.ui.request.interceptor.util; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -25,8 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.json.JSONException; @@ -47,23 +46,15 @@ public class HandlerUtil { /*** * - * @param httpMethod - httpMethod e.g:- HttpPost, HttpGet - * @param - HttpPost or HttpGet class + * @param httpRequest - httpMethod e.g:- HttpPost, HttpGet * @return response as string * @throws IOException IO exception returns if error occurs when executing the httpMethod */ - public static ProxyResponse execute(T httpMethod) throws IOException { + public static ProxyResponse execute(HttpRequestBase httpRequest) throws IOException { try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpResponse response = null; - if (httpMethod instanceof HttpPost) { - HttpPost method = (HttpPost) httpMethod; - response = client.execute(method); - } else if (httpMethod instanceof HttpGet) { - HttpGet method = (HttpGet) httpMethod; - response = client.execute(method); - } - + HttpResponse response = client.execute(httpRequest); ProxyResponse proxyResponse = new ProxyResponse(); + if (response == null) { proxyResponse.setCode(HandlerConstants.INTERNAL_ERROR_CODE); proxyResponse.setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey( diff --git a/components/request-handler/io.entgra.request.handler/src/main/webapp/META-INF/webapp-classloading.xml b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/META-INF/webapp-classloading.xml similarity index 100% rename from components/request-handler/io.entgra.request.handler/src/main/webapp/META-INF/webapp-classloading.xml rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/META-INF/webapp-classloading.xml diff --git a/components/request-handler/io.entgra.request.handler/src/main/webapp/WEB-INF/web.xml b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml similarity index 97% rename from components/request-handler/io.entgra.request.handler/src/main/webapp/WEB-INF/web.xml rename to components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml index c4e45edcf6..066baa8fa6 100644 --- a/components/request-handler/io.entgra.request.handler/src/main/webapp/WEB-INF/web.xml +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml @@ -19,7 +19,7 @@ - Entgra Request Handler Webapp + Entgra UI Request Interceptor Webapp 60 diff --git a/features/request-handler/pom.xml b/components/ui-request-interceptor/pom.xml similarity index 80% rename from features/request-handler/pom.xml rename to components/ui-request-interceptor/pom.xml index 33c8984d0c..f362dfd78e 100644 --- a/features/request-handler/pom.xml +++ b/components/ui-request-interceptor/pom.xml @@ -29,11 +29,15 @@ 4.0.0 io.entgra.devicemgt - request-handler-feature - Entgra - Request Handling Proxy Feature + ui-request-interceptor + Entgra - UI Request Interceptor Servlet + + Proxy servlet to handle requests generated from UI applications to communicate with backend + APIs + pom - io.entgra.request.handler.feature + io.entgra.ui.request.interceptor diff --git a/features/request-handler/io.entgra.request.handler.feature/src/main/resources/build.properties b/features/request-handler/io.entgra.request.handler.feature/src/main/resources/build.properties deleted file mode 100644 index 9c86577d76..0000000000 --- a/features/request-handler/io.entgra.request.handler.feature/src/main/resources/build.properties +++ /dev/null @@ -1 +0,0 @@ -custom = true diff --git a/features/request-handler/io.entgra.request.handler.feature/src/main/resources/p2.inf b/features/request-handler/io.entgra.request.handler.feature/src/main/resources/p2.inf deleted file mode 100644 index 4b6600f708..0000000000 --- a/features/request-handler/io.entgra.request.handler.feature/src/main/resources/p2.inf +++ /dev/null @@ -1,3 +0,0 @@ -instructions.configure = \ -org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ -org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.request.handler_${feature.version}/webapps/api#application-mgt-handler#v1.0.war,target:${installFolder}/../../deployment/server/webapps/api#application-mgt-handler#v1.0.war,overwrite:true);\ diff --git a/features/request-handler/io.entgra.request.handler.feature/pom.xml b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/pom.xml similarity index 87% rename from features/request-handler/io.entgra.request.handler.feature/pom.xml rename to features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/pom.xml index 13b2fa10d3..2080341a2d 100644 --- a/features/request-handler/io.entgra.request.handler.feature/pom.xml +++ b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/pom.xml @@ -21,14 +21,19 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - request-handler-feature + ui-request-interceptor-feature io.entgra.devicemgt 3.2.5-SNAPSHOT 4.0.0 - io.entgra.request.handler.feature - Entgra - Request Handling Proxy Feature + io.entgra.ui.request.interceptor.feature + Entgra - UI Request Interceptor Feature + + Proxy servlet to handle requests generated from UI applications to communicate with backend + APIs + + @@ -36,7 +41,7 @@ maven-dependency-plugin - request-handler-copy + request-handler-copy-war package copy @@ -45,14 +50,14 @@ io.entgra.devicemgt - io.entgra.request.handler + io.entgra.ui.request.interceptor ${project.version} war true ${project.build.directory}/maven-shared-archive-resources/webapps - api#application-mgt-handler#v1.0.war + ui-request-handler.war @@ -95,7 +100,7 @@ p2-feature-gen - io.entgra.request.handler + io.entgra.ui.request.interceptor ../../../features/etc/feature.properties @@ -109,4 +114,4 @@ - \ No newline at end of file + diff --git a/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/build.properties b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/build.properties new file mode 100644 index 0000000000..fcc54d01b6 --- /dev/null +++ b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/build.properties @@ -0,0 +1,3 @@ +instructions.configure = \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.ui.request.interceptor_${feature.version}/webapps/ui=request-handler.war,target:${installFolder}/../../deployment/server/webapps/ui=request-handler.war,overwrite:true);\ diff --git a/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/p2.inf b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/p2.inf new file mode 100644 index 0000000000..0875ae94ba --- /dev/null +++ b/features/ui-request-interceptor/io.entgra.ui.request.interceptor.feature/src/main/resources/p2.inf @@ -0,0 +1,3 @@ +instructions.configure = \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.ui.request.interceptor_${feature.version}/webapps/ui-request-handler.war,target:${installFolder}/../../deployment/server/webapps/ui-request-handler.war,overwrite:true);\ diff --git a/components/request-handler/pom.xml b/features/ui-request-interceptor/pom.xml similarity index 79% rename from components/request-handler/pom.xml rename to features/ui-request-interceptor/pom.xml index 19004986bb..dd3086e5d0 100644 --- a/components/request-handler/pom.xml +++ b/features/ui-request-interceptor/pom.xml @@ -29,12 +29,14 @@ 4.0.0 io.entgra.devicemgt - request-handler - Entgra - Request Handling Proxy + ui-request-interceptor-feature + Entgra - UI Request Interceptor Feature + + Proxy servlet to handle requests generated from UI applications to communicate with backend + APIs + pom - io.entgra.request.handler + io.entgra.ui.request.interceptor.feature - - - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index a4e06e74b4..029e7a01d6 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ components/certificate-mgt components/webapp-authenticator-framework components/email-sender - components/request-handler + components/ui-request-interceptor features/device-mgt features/apimgt-extensions features/application-mgt @@ -53,7 +53,7 @@ features/certificate-mgt features/oauth-extensions features/email-sender - features/request-handler + features/ui-request-interceptor features/jwt-client features/device-mgt-extensions