diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementPublisherAPI.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementPublisherAPI.java index 99355e5c96..699a97f78a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementPublisherAPI.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementPublisherAPI.java @@ -273,7 +273,7 @@ public interface ApplicationManagementPublisherAPI { @POST @Path("/ent-app") @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @ApiOperation( consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON, @@ -342,7 +342,7 @@ public interface ApplicationManagementPublisherAPI { @POST @Path("/web-app") @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @ApiOperation( consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON, @@ -406,7 +406,7 @@ public interface ApplicationManagementPublisherAPI { @POST @Path("/public-app") @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @ApiOperation( consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON, @@ -467,7 +467,7 @@ public interface ApplicationManagementPublisherAPI { @POST @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/ent-app/{appId}") @ApiOperation( consumes = MediaType.APPLICATION_JSON, @@ -583,7 +583,7 @@ public interface ApplicationManagementPublisherAPI { @PUT @Path("/image-artifacts/{uuid}") @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @ApiOperation( consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON, @@ -653,7 +653,7 @@ public interface ApplicationManagementPublisherAPI { @PUT @Path("/app-artifacts/{deviceType}/{appType}/{appId}/{uuid}") @Produces(MediaType.APPLICATION_JSON) - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @ApiOperation( consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementPublisherAPIImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementPublisherAPIImpl.java index d0d091df03..f3669c0979 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementPublisherAPIImpl.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementPublisherAPIImpl.java @@ -164,7 +164,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem } @POST - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/ent-app") public Response createEntApp( @Multipart("application") ApplicationWrapper applicationWrapper, @@ -204,7 +204,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem } @POST - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/web-app") public Response createWebApp( @Multipart("webapp") WebAppWrapper webAppWrapper, @@ -242,7 +242,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem } @POST - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/public-app") public Response createPubApp( @Multipart("public-app") PublicAppWrapper publicAppWrapper, @@ -280,7 +280,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem } @POST - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/ent-app/{appId}") public Response createEntAppRelease( @PathParam("appId") int appId, @@ -320,7 +320,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem @Override @PUT - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Produces(MediaType.APPLICATION_JSON) @Path("/image-artifacts/{uuid}") public Response updateApplicationImageArtifacts( @@ -357,7 +357,7 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem @Override @PUT - @Consumes("multipart/mixed") + @Consumes({"multipart/mixed", MediaType.MULTIPART_FORM_DATA}) @Path("/app-artifact/{deviceType}/{appType}/{uuid}") public Response updateApplicationArtifact( @PathParam("deviceType") String deviceType, diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml index 2fe4013823..d2cc394427 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/pom.xml @@ -151,5 +151,10 @@ org.wso2.carbon.device.application.mgt.common provided + + org.apache.httpcomponents + httpmime + compile + \ No newline at end of file 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 index 124f1e5c4c..864f5d6801 100644 --- 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 @@ -24,12 +24,17 @@ 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.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; 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.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; @@ -38,6 +43,9 @@ 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.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.InputStreamBody; import org.wso2.carbon.device.application.mgt.common.ProxyResponse; import javax.servlet.annotation.MultipartConfig; @@ -48,6 +56,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Enumeration; +import java.util.List; import static io.entgra.ui.request.interceptor.util.HandlerUtil.execute; @@ -61,25 +70,19 @@ import static io.entgra.ui.request.interceptor.util.HandlerUtil.execute; } ) public class InvokerHandler extends HttpServlet { - private static final Log log = LogFactory.getLog(LoginHandler.class); + private static final Log log = LogFactory.getLog(InvokerHandler.class); private static final long serialVersionUID = -6508020875358160165L; - private static AuthData authData; - private static String apiEndpoint; - private static String serverUrl; - private static String platform; + private AuthData authData; + private String apiEndpoint; + private String serverUrl; + private String platform; @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); + generateRequestEntity(req, postRequest); postRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = execute(postRequest); @@ -96,6 +99,8 @@ public class InvokerHandler extends HttpServlet { } HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); } + } catch (FileUploadException e) { + log.error("Error occurred when processing Multipart POST request.", e); } catch (IOException e) { log.error("Error occurred when processing POST request.", e); } @@ -106,7 +111,7 @@ public class InvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpGet getRequest = new HttpGet(generateBackendRequestURL(req)); - copyRequestHeaders(req, getRequest); + copyRequestHeaders(req, getRequest, false); getRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = execute(getRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { @@ -132,14 +137,7 @@ public class InvokerHandler extends HttpServlet { 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); + generateRequestEntity(req, putRequest); putRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = execute(putRequest); @@ -156,6 +154,8 @@ public class InvokerHandler extends HttpServlet { } HandlerUtil.handleSuccess(req, resp, serverUrl, platform, proxyResponse); } + } catch (FileUploadException e) { + log.error("Error occurred when processing Multipart PUT request.", e); } catch (IOException e) { log.error("Error occurred when processing PUT request.", e); } @@ -166,7 +166,7 @@ public class InvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpDelete deleteRequest = new HttpDelete(generateBackendRequestURL(req)); - copyRequestHeaders(req, deleteRequest); + copyRequestHeaders(req, deleteRequest, false); deleteRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = execute(deleteRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { @@ -187,6 +187,49 @@ public class InvokerHandler extends HttpServlet { } } + /** + * Generate te request entity for POST and PUT requests from the incoming request. + * + * @param req incoming {@link HttpServletRequest}. + * @param proxyRequest proxy request instance. + * @throws FileUploadException If unable to parse the incoming request for multipart content extraction. + * @throws IOException If error occurred while generating the request body. + */ + private void generateRequestEntity(HttpServletRequest req, HttpEntityEnclosingRequestBase proxyRequest) + throws FileUploadException, IOException { + if (ServletFileUpload.isMultipartContent(req)) { + ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); + List fileItemList = servletFileUpload.parseRequest(req); + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create(); + entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + for (FileItem item : fileItemList) { + if (!item.isFormField()) { + entityBuilder.addPart(item.getFieldName(), new InputStreamBody(item.getInputStream(), + ContentType.create(item.getContentType()), item.getName())); + } else { + entityBuilder.addTextBody(item.getFieldName(), item.getString(), + ContentType.create(item.getContentType())); + } + } + proxyRequest.setEntity(entityBuilder.build()); + copyRequestHeaders(req, proxyRequest, false); + } else { + 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))); + proxyRequest.setEntity(entity); + } + copyRequestHeaders(req, proxyRequest, true); + } + } + + /** + * Generates the target URL for the proxy request. + * + * @param req incoming {@link HttpServletRequest} + * @return Target URL + */ private String generateBackendRequestURL(HttpServletRequest req) { StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append(serverUrl).append(HandlerConstants.API_COMMON_CONTEXT).append(apiEndpoint); @@ -196,12 +239,22 @@ public class InvokerHandler extends HttpServlet { return urlBuilder.toString(); } - private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest) { + /** + * Copy incoming request headers to the proxy request. + * + * @param req incoming {@link HttpServletRequest} + * @param httpRequest proxy request instance. + * @param preserveContentType true if content type header needs to be preserved. + * This should be set to false when handling multipart requests as Http + * client will generate the Content-Type header automatically. + */ + private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) { Enumeration headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || - headerName.equalsIgnoreCase(SM.COOKIE)) { + headerName.equalsIgnoreCase(SM.COOKIE) || + (!preserveContentType && headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE))) { continue; } Enumeration headerValues = req.getHeaders(headerName); @@ -212,33 +265,35 @@ public class InvokerHandler extends HttpServlet { } /*** + * Validates the incoming request. * * @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) + private boolean validateRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException { serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort(); apiEndpoint = req.getPathInfo(); - String sessionAuthDataKey = req.getHeader(HandlerConstants.X_PLATFORM_HEADER); + platform = req.getHeader(HandlerConstants.X_PLATFORM_HEADER); HttpSession session = req.getSession(false); + if (session == null) { log.error("Unauthorized, You are not logged in. Please log in to the portal"); handleError(req, resp, HttpStatus.SC_UNAUTHORIZED); return false; } - if (StringUtils.isEmpty(sessionAuthDataKey)) { + if (StringUtils.isEmpty(platform)) { log.error("\"X-Platform\" header is empty in the request. Header is required to obtain the auth data from" + " session."); handleError(req, resp, HttpStatus.SC_BAD_REQUEST); return false; } - authData = (AuthData) session.getAttribute(sessionAuthDataKey); - platform = (String) session.getAttribute(HandlerConstants.PLATFORM); + authData = (AuthData) session.getAttribute(platform); + if (authData == null) { log.error("Unauthorized, Access token not found in the current session"); handleError(req, resp, HttpStatus.SC_UNAUTHORIZED); @@ -262,7 +317,7 @@ public class InvokerHandler extends HttpServlet { * @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, + private ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpServletResponse resp, HttpRequestBase httpRequest) throws IOException { if (refreshToken(req, resp)) { httpRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); @@ -284,7 +339,7 @@ public class InvokerHandler extends HttpServlet { * @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) + private boolean refreshToken(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (log.isDebugEnabled()) { log.debug("refreshing the token"); @@ -347,7 +402,7 @@ public class InvokerHandler extends HttpServlet { * @param errorCode HTTP error status code * @throws IOException If error occurred when trying to send the error response. */ - private static void handleError(HttpServletRequest req, HttpServletResponse resp, int errorCode) + private void handleError(HttpServletRequest req, HttpServletResponse resp, int errorCode) throws IOException { ProxyResponse proxyResponse = new ProxyResponse(); proxyResponse.setCode(errorCode); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java index 4a790f18e9..cf2cb4db60 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java @@ -23,7 +23,6 @@ public class HandlerConstants { 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 X_PLATFORM_HEADER = "X-Platform"; public static final String BASIC = "Basic "; public static final String BEARER = "Bearer "; public static final String COLON = ":"; @@ -39,6 +38,8 @@ public class HandlerConstants { public static final String EXECUTOR_EXCEPTION_PREFIX = "ExecutorException-"; public static final String TOKEN_IS_EXPIRED = "ACCESS_TOKEN_IS_EXPIRED"; + public static final String X_PLATFORM_HEADER = "X-Platform"; + public static final int INTERNAL_ERROR_CODE = 500; public static final long TIMEOUT = 1200; } diff --git a/pom.xml b/pom.xml index f3b33c70e1..736ce68f98 100644 --- a/pom.xml +++ b/pom.xml @@ -1310,6 +1310,11 @@ httpcore ${apache.http.core.version} + + org.apache.httpcomponents + httpmime + ${apache.http.mime.version} + commons-lang.wso2 commons-lang @@ -2137,6 +2142,7 @@ 4.5.6 4.4.10 + 4.5.8 1.9