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 99355e5c960..699a97f78ac 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 d0d091df032..f3669c09790 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 2fe4013823a..d2cc394427a 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 124f1e5c4c4..864f5d68014 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 4a790f18e97..cf2cb4db601 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 f3b33c70e1b..736ce68f984 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