Added proxy configuration and check for absolute URL in link header

Change-Id: I1cdf9a9ec1365744a283a9174d5f4bd2137a14b4
Signed-off-by: Fauth Dirk <Dirk.Fauth@de.bosch.com>
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/App4McCloudManagerApplication.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/App4McCloudManagerApplication.java
index bfe85c2..d4399b6 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/App4McCloudManagerApplication.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/App4McCloudManagerApplication.java
@@ -15,11 +15,27 @@
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.util.StringUtils;
+
+import kong.unirest.Unirest;
 
 @SpringBootApplication
 public class App4McCloudManagerApplication {
 
 	public static void main(String[] args) {
+        String proxyHost = System.getProperty("proxy.host");
+        String proxyPort = System.getProperty("proxy.port");
+        String proxyUser = System.getProperty("proxy.user");
+        String proxyPwd = System.getProperty("proxy.pwd");
+        
+        if (!StringUtils.isEmpty(proxyHost) && !StringUtils.isEmpty(proxyPort)) {
+        	if (!StringUtils.isEmpty(proxyUser) && !StringUtils.isEmpty(proxyPwd)) {
+        		Unirest.config().proxy(proxyHost, Integer.valueOf(proxyPort), proxyUser, proxyPwd);
+        	} else {
+        		Unirest.config().proxy(proxyHost, Integer.valueOf(proxyPort));
+        	}
+        }
+
 		SpringApplication.run(App4McCloudManagerApplication.class, args);
 	}
 }
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
index 7b21e2d..201c8ac 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
@@ -207,11 +207,14 @@
 				statusUrl = uploadResponse.getHeaders().getFirst("Location");
 				if (StringUtils.isEmpty(statusUrl)) {
 					// fallback check for Link header if Location header is not set
-					statusUrl = getUrlFromLink(uploadResponse.getHeaders().get("Link"), "status");
+					statusUrl = getUrlFromLink(uploadResponse.getHeaders().get("Link"), "status", baseUrl);
+				} else if (!statusUrl.startsWith(baseUrl)) {
+					throw new ProcessingFailedException(
+							"The status URL from the Location header does not match with the service base URL '" + baseUrl + "': " + statusUrl);
 				}
 			} else if (uploadResponse.getStatus() == 200) {
 				// fallback if return code is 200, then follow up needs to be placed in Link header
-				statusUrl = getUrlFromLink(uploadResponse.getHeaders().get("Link"), "status");
+				statusUrl = getUrlFromLink(uploadResponse.getHeaders().get("Link"), "status", baseUrl);
 			} else {
 				// error
 				workflowStatus.addError("Upload to " + serviceName + " failed! Error code: " + uploadResponse.getStatus() + " - Workflow stopped!");
@@ -250,7 +253,7 @@
 				}
 				
 				// first check if there is a result link
-				String downloadUrl = getUrlFromLink(linkHeaders, "result");
+				String downloadUrl = getUrlFromLink(linkHeaders, "result", baseUrl);
 				String deleteUrl = null;
 				if (downloadUrl != null) {
 					workflowStatus.addMessage(serviceName + " processing finished");
@@ -294,9 +297,9 @@
 					}
 
 					// extract delete
-					deleteUrl = getUrlFromLink(downloadResponse.getHeaders().get("Link"), "delete");
+					deleteUrl = getUrlFromLink(downloadResponse.getHeaders().get("Link"), "delete", baseUrl);
 				} else {
-					String errorUrl = getUrlFromLink(linkHeaders, "error");
+					String errorUrl = getUrlFromLink(linkHeaders, "error", baseUrl);
 					if (errorUrl != null) {
 						workflowStatus.addError(serviceName + " processing finished with error");
 						
@@ -322,7 +325,7 @@
 										migrationError.getFileName().toString()).build().toUri().toString());
 						
 						// extract delete
-						deleteUrl = getUrlFromLink(errorResponse.getHeaders().get("Link"), "delete");
+						deleteUrl = getUrlFromLink(errorResponse.getHeaders().get("Link"), "delete", baseUrl);
 					}
 				}
 
@@ -371,10 +374,11 @@
 	 * @param linkHeaders The link headers to parse.
 	 * @param rel         The value for the rel param for which the url is
 	 *                    requested.
+	 * @param baseUrl     The base URL of the service.
 	 * @return The url for the specified rel param or <code>null</code> if there is
 	 *         no link for the given rel.
 	 */
-	public static String getUrlFromLink(List<String> linkHeaders, String rel) {
+	public static String getUrlFromLink(List<String> linkHeaders, String rel, String baseUrl) {
 		
 		for (String linkHeader : linkHeaders) {
 			String[] links = linkHeader.split(LINK_DELIMITER);
@@ -404,6 +408,12 @@
 					}
 					
 					if (rel.equals(relValue)) {
+						// SECURITY: ensure that host in link matches host in configured service
+						if (!url.startsWith(baseUrl)) {
+							throw new ProcessingFailedException(
+									"The link for rel '" + rel + "' does not match with the service base URL '" + baseUrl + "': " + url);
+						}
+						
 						return url;
 					}
 				}
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
index c86570c..908659d 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
@@ -24,6 +24,10 @@
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import kong.unirest.Proxy;
+import kong.unirest.Unirest;
 
 @Controller
 public class AdminController {
@@ -38,11 +42,17 @@
 		
 		model.addAttribute("dto", dto);
 		
+		Proxy proxy = Unirest.config().getProxy();
+		model.addAttribute("proxy_host", proxy != null ? proxy.getHost() : "");
+		model.addAttribute("proxy_port", proxy != null ? proxy.getPort() : "");
+		model.addAttribute("proxy_user", proxy != null ? proxy.getUsername() : "");
+		model.addAttribute("proxy_pwd", proxy != null ? proxy.getPassword() : "");
+		
         return "admin";
     }
 	
 	@PostMapping("/admin/save")
-	public String saveBooks(@ModelAttribute CloudServiceDefinitionDTO dto, Model model) {
+	public String saveServices(@ModelAttribute CloudServiceDefinitionDTO dto, Model model) {
 		// TODO save to DB
 		cloudServiceDefinitions.clear();
 		cloudServiceDefinitions.addAll(dto.getServices().stream()
@@ -53,6 +63,24 @@
 	    return "redirect:/admin";
 	}
 	
+	@PostMapping("/admin/saveProxy")
+	public String saveProxy(Model model,
+			@RequestParam(name = "proxy_host", required = false) String proxyHost, 
+			@RequestParam(name = "proxy_port", required = false) String proxyPort, 
+			@RequestParam(name = "proxy_user", required = false) String proxyUser, 
+			@RequestParam(name = "proxy_pwd", required = false) String proxyPwd) {
+		
+		// TODO save to DB and load from DB in App4McCloudManagerApplication
+		Unirest.config().reset();
+        if (!StringUtils.isEmpty(proxyHost) && !StringUtils.isEmpty(proxyPort)) {
+       		Unirest.config().proxy(proxyHost, Integer.valueOf(proxyPort), proxyUser, proxyPwd);
+        } else {
+        	Unirest.config().proxy(null);
+        }
+        
+		return "redirect:/admin";
+	}
+	
 	@ModelAttribute("cloudServiceDefinitions")
 	public List<CloudServiceDefinition> cloudServiceDefinitions() {
 		return cloudServiceDefinitions;
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
index cd6cdee..a6ae0b8 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
@@ -1,3 +1,16 @@
+/*********************************************************************************
+ * Copyright (c) 2020 Robert Bosch GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Robert Bosch GmbH - initial API and implementation
+ ********************************************************************************
+ */
 package org.eclipse.app4mc.cloud.manager.administration;
 
 import java.util.ArrayList;
@@ -5,11 +18,8 @@
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.util.StringUtils;
 import org.springframework.web.context.annotation.ApplicationScope;
 
-import kong.unirest.Unirest;
-
 @Configuration
 public class ApplicationConfig {
 
@@ -20,20 +30,6 @@
         definitions.add(new CloudServiceDefinition("Migration", "http://localhost:8080/app4mc/converter/", "Model Migration Service"));
         definitions.add(new CloudServiceDefinition("Validation", "http://localhost:8181/app4mc/validation/", "Model Validation Service"));
         
-        // TODO make this configurable via admin ui
-        String proxyHost = System.getProperty("proxy.host");
-        String proxyPort = System.getProperty("proxy.port");
-        String proxyUser = System.getProperty("proxy.user");
-        String proxyPwd = System.getProperty("proxy.pwd");
-        
-        if (!StringUtils.isEmpty(proxyHost) && !StringUtils.isEmpty(proxyPort)) {
-        	if (!StringUtils.isEmpty(proxyUser) && !StringUtils.isEmpty(proxyPwd)) {
-        		Unirest.config().proxy(proxyHost, Integer.valueOf(proxyPort), proxyUser, proxyPwd);
-        	} else {
-        		Unirest.config().proxy(proxyHost, Integer.valueOf(proxyPort));
-        	}
-        }
-        
         return definitions;
     }
 }
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java
index f1069bc..ce77321 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java
@@ -1,3 +1,16 @@
+/*********************************************************************************
+ * Copyright (c) 2020 Robert Bosch GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Robert Bosch GmbH - initial API and implementation
+ ********************************************************************************
+ */
 package org.eclipse.app4mc.cloud.manager.administration;
 
 public class CloudServiceDefinition {
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinitionDTO.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinitionDTO.java
index 5f71883..31ad030 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinitionDTO.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinitionDTO.java
@@ -1,3 +1,16 @@
+/*********************************************************************************
+ * Copyright (c) 2020 Robert Bosch GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Robert Bosch GmbH - initial API and implementation
+ ********************************************************************************
+ */
 package org.eclipse.app4mc.cloud.manager.administration;
 
 import java.util.ArrayList;
diff --git a/manager/src/main/resources/templates/admin.html b/manager/src/main/resources/templates/admin.html
index 5807fae..e5a077c 100644
--- a/manager/src/main/resources/templates/admin.html
+++ b/manager/src/main/resources/templates/admin.html
@@ -11,8 +11,6 @@
 	<div>
 		<form action="#" th:action="@{/admin/save}" th:object="${dto}" method="POST">
 		    <fieldset>
-		        <input type="submit" id="submitButton" th:value="Save">
-		        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
 		        <table>
 		            <thead>
 		                <tr>
@@ -29,6 +27,37 @@
 		                </tr>
 		            </tbody>
 		        </table>
+		        <input type="submit" id="submitButton" th:value="Save">
+		        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
+		    </fieldset>
+		</form>
+	</div>
+	<div>
+		<h2>Configure Proxy</h2>
+	</div>
+	<div>
+		<form action="/admin/saveProxy" method="POST">
+		    <fieldset>
+		        <table>
+					<tr>
+						<td>Host:</td>
+						<td><input type="text" name="proxy_host" th:value="${proxy_host}"/></td>
+					</tr>
+					<tr>
+						<td>Port:</td>
+						<td><input type="text" name="proxy_port" th:value="${proxy_port}"/></td>
+					</tr>
+					<tr>
+						<td>User:</td>
+						<td><input type="text" name="proxy_user" th:value="${proxy_user}"/></td>
+					</tr>
+					<tr>
+						<td>Password:</td>
+						<td><input type="password" name="proxy_pwd" th:value="${proxy_pwd}"/></td>
+					</tr>
+		        </table>
+		        <input type="submit" id="submitButton" th:value="Save">
+		        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
 		    </fieldset>
 		</form>
 	</div>