diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25a056c532647dc2e8806b4e9ebccfcdfd91dc65..9d2d7883229dd069113850e5512a34b7d8b737ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
-* 2.0.13
+# 2.0.14
+* [LGR-75](https://jira.software.geant.org/browse/LGR-73) - Externalise saml groups to role
+* [LGR-73](https://jira.software.geant.org/browse/LGR-73) - Remove router endpoint
+* [LGR-72](https://jira.software.geant.org/browse/LGR-72) - Check user is authenticated prior to executing commands on internal routers
+
+# 2.0.13
 * [LGR-70](https://jira.software.geant.org/browse/LGR-70) - Pull UI out of jar
 
 # 2.0.12
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..f202d3031e2c69a6aa27c2cda1ad9fe29c690889
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,4 @@
+# tells Lombok that this is the root directory and that it shouldn’t search parent directories for more configuration files
+config.stopBubbling = true
+# tells Lombok to add @lombok.Generated annotation to all generated methods
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 1dd100dcd082d0e1eeb019dca8f96cf5bf68a8e8..9a67ccd7ff5a28557bdf4dfb5918085b1fba9163 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,10 +3,10 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-	<groupId>org.geant</groupId>
-	<artifactId>looking-glass-service</artifactId>
-	<version>2.0.13.RELEASE</version>
-	<packaging>jar</packaging>
+    <groupId>org.geant</groupId>
+    <artifactId>looking-glass-service</artifactId>
+    <version>2.0.14.RELEASE</version>
+    <packaging>jar</packaging>
 
     <name>looking-glass-service</name>
     <description>Looking Glass Demo</description>
@@ -14,7 +14,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.0.5.RELEASE</version>
+        <version>2.2.5.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
 
@@ -25,25 +25,6 @@
         <java.version>1.8</java.version>
     </properties>
 
-    <scm>
-        <developerConnection>scm:git:https://gitlab.geant.net/live-projects/looking-glass-service.git
-        </developerConnection>
-        <tag>HEAD</tag>
-    </scm>
-
-    <distributionManagement>
-        <repository>
-            <id>looking-glass-releases</id>
-            <name>GEANT Artifactory-releases</name>
-            <url>https://artifactory.geant.net:443/artifactory/lg-release-local</url>
-        </repository>
-        <snapshotRepository>
-            <id>looking-glass-snapshots</id>
-            <name>GEANT Artifactory-snapshots</name>
-            <url>https://artifactory.geant.net:443/artifactory/lg-snapshot-local</url>
-        </snapshotRepository>
-    </distributionManagement>
-
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -59,19 +40,6 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-rest</artifactId>
-        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -87,6 +55,11 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>
@@ -101,16 +74,6 @@
             <artifactId>spring-security-saml2-core</artifactId>
             <version>1.0.3.RELEASE</version>
         </dependency>
-        <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger2</artifactId>
-            <version>2.7.0</version>
-        </dependency>
-        <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger-ui</artifactId>
-            <version>2.7.0</version>
-        </dependency>
         <dependency>
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-prometheus</artifactId>
@@ -119,6 +82,50 @@
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-statsd</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>retrofit</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>converter-gson</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>converter-jackson</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tngtech.jgiven</groupId>
+            <artifactId>jgiven-junit</artifactId>
+            <version>0.18.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.tngtech.jgiven</groupId>
+            <artifactId>jgiven-spring</artifactId>
+            <version>0.18.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.14.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alexlovett</groupId>
+            <artifactId>jgiven-extension</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+            <version>2.26.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -138,7 +145,8 @@
                                 <line>After=syslog.target</line>
                                 <line>[Service]</line>
                                 <line>User=${artifactId}</line>
-                                <line>ExecStart=/opt/${artifactId}/latest/bin/${artifactId}.${project.packaging} --spring.config.location=file:/opt/${artifactId}/${version}/config/application.yml</line>
+                                <line>ExecStart=/opt/${artifactId}/latest/bin/${artifactId}.${project.packaging} --spring.config.location=file:/opt/${artifactId}/${version}/config/application.yml
+                                </line>
                                 <line>StandardOutput=syslog</line>
                                 <line>StandardError=syslog</line>
                                 <line>SuccessExitStatus=143</line>
@@ -188,13 +196,15 @@
                 <version>0.8.5</version>
                 <executions>
                     <execution>
+                        <id>pre-integration-test</id>
+                        <phase>test-compile</phase>
                         <goals>
                             <goal>prepare-agent</goal>
                         </goals>
                     </execution>
                     <execution>
                         <id>report</id>
-                        <phase>prepare-package</phase>
+                        <phase>post-integration-test</phase>
                         <goals>
                             <goal>report</goal>
                         </goals>
@@ -277,8 +287,11 @@
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
-                                    <name>/opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}</name>
-                                    <file>${project.build.directory}/${project.build.finalName}.${project.packaging}</file>
+                                    <name>
+                                        /opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}
+                                    </name>
+                                    <file>${project.build.directory}/${project.build.finalName}.${project.packaging}
+                                    </file>
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
@@ -288,7 +301,9 @@
                                 </entry>
                                 <entry>
                                     <name>/opt/${artifactId}/${version}/bin/${artifactId}.${project.packaging}</name>
-                                    <linkTo>/opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}</linkTo>
+                                    <linkTo>
+                                        /opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}
+                                    </linkTo>
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
@@ -301,6 +316,34 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.18.1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.tngtech.jgiven</groupId>
+                <artifactId>jgiven-maven-plugin</artifactId>
+                <version>0.18.2</version>
+                <executions>
+                    <execution>
+                        <phase>post-integration-test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <format>html</format>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/saml/keystore.jks b/saml/keystore.jks
deleted file mode 100644
index 402e7ab6eaa52384af67825a7d9bf9fd26183237..0000000000000000000000000000000000000000
Binary files a/saml/keystore.jks and /dev/null differ
diff --git a/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java b/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
index 2d37ef6559f361750cfab987561033de78b46497..8ed4c25017169ceaa4cb6fbac1c77f6d41c56f67 100644
--- a/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
+++ b/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
@@ -1,32 +1,20 @@
 package org.geant.lgservice;
 
+import org.geant.lgservice.infrastructure.rest.inventoryprovider.InventoryProviderConfig;
+import org.geant.lgservice.infrastructure.ssh.SSHConfig;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
 
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-@EnableSwagger2
 @SpringBootApplication
+@Import({
+		InventoryProviderConfig.class,
+		SSHConfig.class
+})
 public class LookingGlassServiceApplication {
 
 	public static void main(String[] args) {
 		SpringApplication.run(LookingGlassServiceApplication.class, args);
 	}
 
-	@Bean
-	public Docket productApi() {
-		return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
-				.apis(RequestHandlerSelectors.basePackage("org.geant.lgservice")).build();
-	}
-
-	private ApiInfo apiInfo() {
-		return new ApiInfoBuilder().title("Looking Glass Rest APIs")
-				.description("This page lists all the rest apis for Looking Glass Application.").build();
-	}
 }
diff --git a/src/main/java/org/geant/lgservice/config/AppConfig.java b/src/main/java/org/geant/lgservice/config/AppConfig.java
index 147163082c60fe7499bfcd75c8a00d59f887db2f..254b7750442def27aa55563415c398666afe4377 100644
--- a/src/main/java/org/geant/lgservice/config/AppConfig.java
+++ b/src/main/java/org/geant/lgservice/config/AppConfig.java
@@ -30,18 +30,6 @@ public class AppConfig implements Serializable {
 
 	@Value("${configuration.publickey.keyfile}")
 	private String keyFile;
-
-	@Value("${spring.datasource.username}")
-	private String dbUsername;
-
-	@Value("${spring.datasource.password}")
-	private String dbPassword;
-
-	@Value("${spring.datasource.url}")
-	private String dbURL;
-
-	@Value("${spring.datasource.driverClassName}")
-	private String dbDriverClassName;
 	
 	@Value("${saml.metadata-url}")
 	private String metadataURL;
diff --git a/src/main/java/org/geant/lgservice/config/CommandsParser.java b/src/main/java/org/geant/lgservice/config/CommandsParser.java
index 47d92ceab8fe70307bdb00b7bc47e6a60613ed47..1f8ee9526713d86b39af8ccafa8808d2717164b8 100644
--- a/src/main/java/org/geant/lgservice/config/CommandsParser.java
+++ b/src/main/java/org/geant/lgservice/config/CommandsParser.java
@@ -1,62 +1,61 @@
 package org.geant.lgservice.config;
 
-import java.io.InputStream;
-import java.util.List;
-
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
-
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.geant.lgservice.exceptions.TechnicalException;
 import org.geant.lgservice.pojos.Group;
 import org.geant.lgservice.security.LGUser;
-import org.geant.lgservice.utils.LGRole;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
 
 public class CommandsParser {
 
-	private static final Logger logger = LogManager.getLogger(CommandsParser.class);
-
-	public List<Group> getCommandsFromXML(final String fileName, final LGUser user) {
-		Commands commands = new Commands();
-		try {
-			String newFileName = resolveFileName(fileName, user);
-			if (newFileName != null) {
-				ClassLoader classLoader = getClass().getClassLoader();
-				InputStream stream = classLoader.getResourceAsStream(newFileName);
-
-				JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class);
-				Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
-				commands = (Commands) unmarshaller.unmarshal(stream);
-			} else {
-				logger.error("Unable to read config file for commands list");
-			}
-		} catch (JAXBException e) {
-			logger.error("Unable to parse xml for " + fileName, e);
-		}
-
-		return commands.getGroups();
-	}
-
-	public String resolveFileName(final String fileName, final LGUser user) {
-		String newFileName = fileName + "_" + LGRole.PUBLIC.getName();
-		if (user != null) {
-			switch (user.getRole()) {
-			case PUBLIC: {
-				newFileName = fileName + "_" + LGRole.PUBLIC.getName();
-				break;
-			}
-			case INTERNAL: {
-				newFileName = fileName + "_" + LGRole.INTERNAL.getName();
-				break;
-			}
-			case EXTERNAL: {
-				newFileName = fileName + "_" + LGRole.EXTERNAL.getName();
-				break;
-			}
-			}
-		}
-		newFileName = newFileName + ".xml";
-		return newFileName;
-	}
+    private static final Logger logger = LogManager.getLogger(CommandsParser.class);
+
+    public List<Group> getCommandsFromXML(final String fileName, final LGUser user) {
+        try {
+            InputStream stream = getClass().getClassLoader().getResourceAsStream(resolveFileName(fileName, user));
+
+            JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class);
+            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+            return ((Commands) unmarshaller.unmarshal(stream)).getGroups();
+        } catch (JAXBException e) {
+            logger.error("Unable to parse xml for " + fileName, e);
+            throw new TechnicalException(e);
+        }
+
+    }
+
+    @RequiredArgsConstructor
+    private enum GroupRanking {
+        INTERNAL(0),
+        EXTERNAL(1);
+
+        @Getter
+        private final int rank;
+    }
+
+    public String resolveFileName(final String fileName, final LGUser user) {
+        String accessLevel = Optional.ofNullable(user)
+                .map(UserDetails::getAuthorities)
+                .flatMap(authorities -> authorities
+                        .stream()
+                        .map(GrantedAuthority::getAuthority)
+                        .map(GroupRanking::valueOf)
+                        .reduce((groupOne, groupTwo) -> groupOne.getRank() < groupTwo.getRank() ? groupOne : groupTwo))
+                .map(GroupRanking::name)
+                .map(String::toLowerCase)
+                .orElse("public");
+
+        return String.format("%s_%s.xml", fileName, accessLevel);
+    }
 }
diff --git a/src/main/java/org/geant/lgservice/domain/Router.java b/src/main/java/org/geant/lgservice/domain/Router.java
new file mode 100644
index 0000000000000000000000000000000000000000..5421521b49b14cfa705c7821d7a86f2dd6ea4dfc
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/domain/Router.java
@@ -0,0 +1,18 @@
+package org.geant.lgservice.domain;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@Builder
+@RequiredArgsConstructor
+public class Router {
+    private final String hostName;
+    private final Access access;
+
+    public enum Access {
+        INTERNAL,
+        PUBLIC
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/domain/RouterRepository.java b/src/main/java/org/geant/lgservice/domain/RouterRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..8177f0a98076a6d2e4b3d4d0ddccc4064e4ddb3f
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/domain/RouterRepository.java
@@ -0,0 +1,7 @@
+package org.geant.lgservice.domain;
+
+import java.util.Optional;
+
+public interface RouterRepository {
+    Optional<Router> getByHostName(String hostname);
+}
diff --git a/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java b/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java
deleted file mode 100644
index afb29f3678965113917353734b39ee963b3b4929..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.concurrent.Callable;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Credentials;
-import org.geant.lgservice.pojos.Router;
-
-import ch.ethz.ssh2.Connection;
-import ch.ethz.ssh2.Session;
-
-public class CallableCommandExecutor implements Callable<CommandOutput> {
-
-	private static final Logger logger = LogManager.getLogger(CallableCommandExecutor.class);
-
-	private Router router;
-
-	private Command command;
-
-	private String arguments;
-
-	private boolean displayAsXML;
-
-	private Credentials credentials;
-
-	CallableCommandExecutor(final Router router, final Command command, final String arguments,
-			final boolean displayAsXML, final Credentials credentials) {
-		this.router = router;
-		this.command = command;
-		this.arguments = arguments;
-		this.displayAsXML = displayAsXML;
-		this.credentials = credentials;
-	}
-
-	@Override
-	public CommandOutput call() {
-		long start = System.currentTimeMillis();
-		StringBuilder output = new StringBuilder();
-		CommandOutput finalOutput = new CommandOutput();
-
-		Connection conn = new Connection(router.getName());
-		try {
-		  try {
-		  	  conn.connect(null, 30000, 30000); // Should be configurable but this code is due a rewrite / pulled out
-		  } catch (IOException e) {
-			  logger.error("Error while connecting to router " + router.getName(), e);
-			  return new CommandOutput(router.getName(), "Error while connecting to router " + router.getName(), null);
-		  }
-		  boolean isAuthenticated;
-		  try {
-			  isAuthenticated = conn.authenticateWithPublicKey(credentials.getUsername(),
-					  new File(credentials.getKeyFile()), credentials.getPassword());
-		  } catch (IOException e) {
-			  logger.error("Error while authenticating the user for router " + router.getName(), e);
-			  return new CommandOutput(router.getName(),
-					  "Error while authenticating the user for router " + router.getName(), null);
-		  }
-
-		  if (isAuthenticated) {
-			  Session session;
-			  try {
-				  session = conn.openSession();
-			  } catch (IOException e) {
-				  logger.error("Error while opening session to router " + router.getName(), e);
-				  return new CommandOutput(router.getName(), "Error while opening session to router " + router.getName(),
-						  null);
-			  }
-			  InputStream in = session.getStdout();
-			  BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
-			  try {
-				  session.execCommand(getCommandWithArguments());
-			  } catch (IOException e) {
-				  logger.error("Error while executing command " + command.getValue(), e);
-				  return new CommandOutput(router.getName(), "Error while executing command " + command.getValue(), null);
-			  }
-
-			  while (true) {
-				  String line = "";
-				  try {
-					  line = br.readLine();
-					  if (line == null)
-						  break;
-					  output.append(line + "\n");
-				  } catch (IOException e) {
-				    	return new CommandOutput(router.getName(), "Error while reading line", null);
-				  }
-			  }
-			  long finish = System.currentTimeMillis();
-			  finalOutput.setRouterName(router.getName());
-			  finalOutput.setCommandResult(output.toString());
-			  finalOutput.setExecutionTime(finish - start);
-
-			  return finalOutput;
-		  }
-		  return new CommandOutput(router.getName(), "Unable to authenticate user for router " + router.getName(), null);
-		} finally {
-		  conn.close();
-	    }
-	}
-
-	public String getCommandWithArguments() {
-		return Stream.of(
-				this.command.getValue(),
-				this.arguments,
-				this.displayAsXML ? "| display xml" : "")
-				.filter(StringUtils::isNotBlank)
-				.collect(Collectors.joining(" "));
-	}
-
-}
diff --git a/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java b/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java
deleted file mode 100644
index 551969153187b00044026905fbd4b717302e3668..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import org.geant.lgservice.config.AppConfig;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Credentials;
-import org.geant.lgservice.pojos.Router;
-import org.springframework.stereotype.Component;
-
-@Component
-public class CommandExecutor {
-
-    private final Credentials credentials;
-
-    public CommandExecutor(AppConfig config) {
-        credentials = Credentials.builder()
-                .username(config.getUsername())
-                .password(config.getPassword())
-                .keyFile(config.getKeyFile())
-                .build();
-    }
-
-    public CommandOutput execute(Router router, Command command, String arguments, boolean asXml) {
-        return new CallableCommandExecutor(router, command, arguments, asXml, credentials).call();
-    }
-}
diff --git a/src/main/java/org/geant/lgservice/exceptions/BusinessException.java b/src/main/java/org/geant/lgservice/exceptions/BusinessException.java
new file mode 100644
index 0000000000000000000000000000000000000000..68926f80859f45b5670daff8afb1f0e0b4729897
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/exceptions/BusinessException.java
@@ -0,0 +1,33 @@
+package org.geant.lgservice.exceptions;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import static lombok.AccessLevel.PRIVATE;
+
+@RequiredArgsConstructor(access = PRIVATE)
+public class BusinessException  extends RuntimeException {
+
+    @Getter
+    private final Reason reason;
+
+    @AllArgsConstructor
+    public enum Reason {
+        UNAUTHORIZED("User is not logged in"),
+        FORBIDDEN("User does not have access rights"),
+        NOT_FOUND("Could not find router");
+
+        @Getter
+        private final String description;
+    }
+
+    public static BusinessException withReason(Reason reason) {
+        return new BusinessException(reason);
+    }
+
+    @Override
+    public String getMessage() {
+        return reason.getDescription();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java b/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e18b63ae5faa0b9fbbfd2422f72fb3d68042ec93
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java
@@ -0,0 +1,11 @@
+package org.geant.lgservice.exceptions;
+
+public class TechnicalException extends RuntimeException {
+    public TechnicalException(Throwable t) {
+        super(t);
+    }
+
+    public TechnicalException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderConfig.java b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..81601df36f310ae51232bf8e3f27aee2c1bf3d59
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderConfig.java
@@ -0,0 +1,39 @@
+package org.geant.lgservice.infrastructure.rest.inventoryprovider;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.geant.lgservice.domain.RouterRepository;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+
+@Configuration
+public class InventoryProviderConfig {
+
+    @Bean
+    public RouterRepository routerRepository(@Value("${inventoryprovider.baseUrl}")String baseUrl) {
+        return InventoryProviderRouterRepository.builder().baseUrl(baseUrl)
+                .client(new OkHttpClient.Builder().addInterceptor(new OkHttpLogger()).build()).build();
+    }
+
+    @Slf4j
+    private static class OkHttpLogger implements Interceptor {
+
+        @Override
+        public Response intercept(Chain chain) throws IOException {
+
+            Request request = chain.request();
+            log.info("Making request to {} with headers {}", request.url().toString(), request.headers());
+            Response response = chain.proceed(request);
+            long contentLength = response.body().contentLength();
+            log.info("Response recieved with status {} and body {}", response.code(), response.peekBody(contentLength >= 0 ? contentLength : Long.MAX_VALUE).string());
+            return response;
+        }
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..17ac48d40a7878533f62cffb126e8d02c7bebc28
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
@@ -0,0 +1,124 @@
+package org.geant.lgservice.infrastructure.rest.inventoryprovider;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import org.geant.lgservice.domain.Router;
+import org.geant.lgservice.domain.RouterRepository;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.HttpStatusCodeException;
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+import retrofit2.http.GET;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static org.geant.lgservice.domain.Router.Access.INTERNAL;
+import static org.geant.lgservice.domain.Router.Access.PUBLIC;
+
+@Slf4j
+public class InventoryProviderRouterRepository implements RouterRepository {
+
+    private final InventoryProvider inventoryProvider;
+
+    @Override
+    public Optional<Router> getByHostName(String hostname) {
+        try {
+            Response<Collection<InventoryProvider.InventoryProviderRouter>> response = inventoryProvider.getRouters()
+                    .execute();
+            if (!response.isSuccessful()) {
+                HttpStatus status = HttpStatus.valueOf(response.code());
+                HttpStatusCodeException rootException = status.is4xxClientError() ?
+                        new HttpClientErrorException(status, response.message(), response.errorBody()
+                                .bytes(), defaultCharset()) :
+                        new HttpServerErrorException(status, response.message(), response.errorBody()
+                                .bytes(), defaultCharset());
+                log.error(response.message(), rootException);
+                throw new TechnicalException(rootException);
+            }
+
+            return response.body()
+                    .stream()
+                    .filter(router -> router.getEquipmentName().equals(hostname))
+                    .map(InventoryProvider.InventoryProviderRouter::toDomain)
+                    .findFirst();
+        } catch (IOException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    @Builder
+    public InventoryProviderRouterRepository(String baseUrl, OkHttpClient client) {
+        OkHttpClient.Builder builder =
+                (client != null ? client.newBuilder() : new OkHttpClient.Builder());
+
+        List<Interceptor> existingInterceptors = builder.interceptors();
+        List<Interceptor> interceptors = Stream.concat(
+                Stream.of((Interceptor.Chain chain) -> chain.proceed(chain.request().newBuilder()
+                    .addHeader("Accept", "application/json")
+                    .build())),
+                existingInterceptors.stream()).collect(Collectors.toList());
+
+        existingInterceptors.removeIf(interceptors::contains);
+        existingInterceptors.addAll(interceptors);
+
+        this.inventoryProvider = new Retrofit.Builder()
+                .baseUrl(baseUrl)
+                .client(builder.build())
+                .addConverterFactory(JacksonConverterFactory.create())
+                .build()
+                .create(InventoryProvider.class);
+    }
+
+    interface InventoryProvider {
+        @GET("routers/all")
+        Call<Collection<InventoryProviderRouter>> getRouters();
+
+        @Getter
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        class InventoryProviderRouter {
+            private final String equipmentName;
+            private final String type;
+
+            @JsonCreator
+            public InventoryProviderRouter(
+                    @JsonProperty("equipment name") String equipmentName,
+                    @JsonProperty("type") String type
+            ) {
+                this.equipmentName = equipmentName;
+                this.type = type;
+            }
+
+            private static final Map<String, Router.Access> ACCESS_MAPPINGS =
+                    ImmutableMap.<String, Router.Access>builder()
+                            .put("INTERNAL", INTERNAL)
+                            .build();
+
+            public Router toDomain() {
+                return Router.builder()
+                        .hostName(equipmentName)
+                        .access(ACCESS_MAPPINGS.getOrDefault(type, PUBLIC))
+                        .build();
+            }
+        }
+
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..838cadc104ae218bcc523e36640f832a49e4b35a
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java
@@ -0,0 +1,36 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+
+import java.io.File;
+import java.io.IOException;
+
+import static java.lang.String.format;
+
+@RequiredArgsConstructor
+@Slf4j
+public class ConnectionFactory {
+
+    private final String username;
+    private final String password;
+    private final File keyFile;
+    private final int timeout;
+
+    public Connection createConnection(String hostname) {
+        log.info("Creating connection for {}", hostname);
+        Connection connection = new Connection(hostname);
+        try {
+            connection.connect(null, timeout, timeout);
+            if (!connection.authenticateWithPublicKey(username, keyFile, password)){
+                throw new TechnicalException(format("Could not authenticate ssh connection with %s", hostname));
+            }
+        } catch (IOException e) {
+            throw new TechnicalException(e);
+        }
+        log.info("Connection for {} created", hostname);
+        return connection;
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..17f3249b21630c9213320fd606a08bf5db365fe7
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java
@@ -0,0 +1,46 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.ERROR;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class ConnectionManager implements SSHHost.ConnectionStateListener {
+
+    private final ConnectionFactory connectionFactory;
+
+    private final Map<String, SSHHost> hosts = new ConcurrentHashMap<>();
+
+    public SSHHost getHost(String hostname) {
+        log.info("retrieving host {}", hostname);
+        synchronized (this) {
+            return hosts
+                    .computeIfAbsent(
+                            hostname,
+                            host -> {
+                                log.info("Host [{}] connection does not exist", hostname);
+                                return SSHHost.fromConnection(connectionFactory.createConnection(host))
+                                        .withConnectionStateListener(this);
+                            });
+        }
+    }
+
+    @Override
+    public void stateChanged(SSHHost host, State state) {
+        synchronized (this) {
+            log.info("host [{}] state changed to [{}]", host, state);
+            if (state.equals(DISCONNECTED) || state.equals(ERROR)) {
+                hosts.remove(host);
+            }
+            log.info("currently active connections: [{}]", hosts.keySet());
+        }
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..35f532db31c73edc1ef157da219d7616114f8095
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java
@@ -0,0 +1,31 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.pojos.CommandOutput;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StopWatch;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SSHCommandExecutor {
+
+    private final ConnectionManager connectionManager;
+
+    public CommandOutput execute(String hostname, String command) {
+        log.info("Executing command [{}] on host [{}]", command, hostname);
+        StopWatch timer = new StopWatch();
+        timer.start();
+        String response = connectionManager.getHost(hostname)
+                .execute(command);
+        timer.stop();
+        log.info("Command completed on host [{}] in [{}]ms with result [{}]", hostname, timer.getTotalTimeMillis(), response);
+        return CommandOutput.builder()
+                .routerName(hostname)
+                .commandResult(response)
+                .executionTime(timer.getTotalTimeMillis())
+                .build();
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHConfig.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb195bfd2a62db05ccab15b87e67ab81456dee63
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHConfig.java
@@ -0,0 +1,22 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+
+@Configuration
+public class SSHConfig {
+
+    @Bean
+    public ConnectionFactory connectionFactory(
+            @Value("${configuration.publickey.username}") String username,
+            @Value("${configuration.publickey.keyfile}") File keyFile,
+            @Value("${configuration.publickey.password}") String password,
+            @Value("${configuration.ssh.connection.timeout:30000}") int timeout
+    ) {
+        return new ConnectionFactory(username, password, keyFile, timeout);
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java
new file mode 100644
index 0000000000000000000000000000000000000000..98d7d29f0622cc5567c6671e31329d50f04f5099
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java
@@ -0,0 +1,94 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.ConnectionMonitor;
+import ch.ethz.ssh2.Session;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.ERROR;
+
+@Slf4j
+public class SSHHost implements ConnectionMonitor {
+
+    private final Connection connection;
+    private final Collection<ConnectionStateListener> connectionStateListeners = new HashSet<>();
+
+    private SSHHost(Connection connection) {
+        this.connection = connection;
+        connection.addConnectionMonitor(this);
+    }
+
+    public static SSHHost fromConnection(Connection connection) {
+        if (!connection.isAuthenticationComplete()) {
+            throw new TechnicalException("Connection is not authenticated");
+        }
+        return new SSHHost(connection);
+    }
+
+    public String execute(String command) {
+        Session session = null;
+        try {
+            log.debug("opening session on connection [{}] with host [{}]", connection, hostname());
+            session = connection.openSession();
+            log.debug("Session [{}] opened", session);
+
+            log.debug("sending command [{}] to host [{}]", command, hostname());
+            session.execCommand(command);
+            log.debug("command [{}] sent to host [{}]", command, hostname());
+
+            int exitStatus = Optional.of(session).map(Session::getExitStatus).orElse(0);
+            log.debug("Exit status [{}] recieved", exitStatus);
+
+            InputStream response = exitStatus != 0 ? session.getStderr() : session.getStdout();
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(response))) {
+                return reader.lines().collect(Collectors.joining("\n"));
+            }
+        } catch (IOException e) {
+            triggerStateChangeListeners(ERROR);
+            connection.close();
+            throw new TechnicalException(e);
+        }
+    }
+
+    public String hostname() {
+        return connection.getHostname();
+    }
+
+    public SSHHost withConnectionStateListener(ConnectionStateListener listener) {
+        connectionStateListeners.add(listener);
+        return this;
+    }
+
+    private void triggerStateChangeListeners(ConnectionStateListener.State state) {
+        connectionStateListeners
+                .forEach(connectionStateListener -> connectionStateListener.stateChanged(this, state));
+    }
+
+    @Override
+    public void connectionLost(Throwable reason) {
+        log.warn("Host [{}] lost connection: ", connection.getHostname(), reason);
+        triggerStateChangeListeners(DISCONNECTED);
+    }
+
+    @FunctionalInterface
+    public interface ConnectionStateListener {
+
+        enum State {
+            DISCONNECTED,
+            ERROR
+        }
+
+        void stateChanged(SSHHost host, State state);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java b/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..07fb9acb6696f814d69616b33ffbe63ca2f36836
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java
@@ -0,0 +1,20 @@
+package org.geant.lgservice.interfaces.rest;
+
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.BusinessException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@Slf4j
+@ControllerAdvice
+public class BusinessExceptionHandler extends ResponseEntityExceptionHandler {
+
+    @ExceptionHandler(BusinessException.class)
+    protected ResponseEntity<Object> handleBusinessException (BusinessException ex) {
+        log.info("Business error: {}", ex.getMessage());
+        return ResponseEntity.status(HttpStatus.valueOf(ex.getReason().name())).build();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java b/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf184cc96a76572005d456cd6f4c84dd2fed0925
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java
@@ -0,0 +1,21 @@
+package org.geant.lgservice.interfaces.rest;
+
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+
+@ControllerAdvice
+@Slf4j
+public class TechnicalExceptionHandler extends ResponseEntityExceptionHandler {
+
+    @ExceptionHandler(TechnicalException.class)
+    protected ResponseEntity<Object> handleBusinessException(TechnicalException ex) {
+        log.error("Caught Technical Exception: ", ex);
+        return ResponseEntity.status(INTERNAL_SERVER_ERROR).build();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/model/Equipment.java b/src/main/java/org/geant/lgservice/model/Equipment.java
deleted file mode 100755
index c08a8156497a528270cac3747c6a7b3154c0baaf..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/model/Equipment.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.geant.lgservice.model;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Table;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.geant.lgservice.pojos.Coordinates;
-import org.geant.lgservice.pojos.Router;
-
-@Data
-@Entity()
-@NoArgsConstructor
-@AllArgsConstructor
-@Table(name = "vlg_routers")
-public class Equipment {
-
-	@Id
-	@Column(name = "router_absid")
-	private int routerAbsid;
-
-	@Column(name = "router_name")
-	private String routerName;
-	
-	@Column(name = "pop_absid")
-	private int popAbsid;
-	
-	@Column(name = "pop_name")
-	private String popName;
-	
-	@Column(name = "pop_lat")
-	private String popLat;
-	
-	@Column(name = "pop_long")
-	private String popLong;
-	
-	@Column(name = "pop_country_code")
-	private String popCountryCode;
-	
-	@Column(name = "pop_country")
-	private String popCountry;
-	
-	@Column(name = "pop_city")
-	private String popCity;
-
-	@Column(name = "internal")
-	private String internal;
-
-	@Column(name = "pop_abbrev")
-	private String popAbbrev;
-
-	public Router toDomain() {
-		return Router.builder()
-				.abbreviatedName(popAbbrev)
-				.city(popCity)
-				.coordinates(new Coordinates(popLat, popLong))
-				.name(routerName)
-				.popCountryCode(popCountryCode)
-				.country(popCountry)
-				.popName(popName)
-				.build();
-	}
-
-}
diff --git a/src/main/java/org/geant/lgservice/pojos/Command.java b/src/main/java/org/geant/lgservice/pojos/Command.java
index 674fdc65cd166998a69fa5b080e03bd89c1702aa..d6f806f2a0d077cc887b5c16070d8406d05ab566 100644
--- a/src/main/java/org/geant/lgservice/pojos/Command.java
+++ b/src/main/java/org/geant/lgservice/pojos/Command.java
@@ -1,12 +1,12 @@
 package org.geant.lgservice.pojos;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
-@Data
 @NoArgsConstructor
-@AllArgsConstructor
+@Setter
+@Getter
 public class Command {
 
 	private String name;
diff --git a/src/main/java/org/geant/lgservice/pojos/CommandOutput.java b/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
index 89b252988870a6a51b3384ac0649e1c60fe29352..c4073ba2c1dc43f891e4968721e9cbfd2d215273 100644
--- a/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
+++ b/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
@@ -1,16 +1,14 @@
 package org.geant.lgservice.pojos;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
 
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
+@Getter
+@Builder
 public class CommandOutput {
 
-	private String routerName;
-	private String commandResult;
-	private Long executionTime;
+	private final String routerName;
+	private final String commandResult;
+	private final Long executionTime;
 
 }
diff --git a/src/main/java/org/geant/lgservice/pojos/Coordinates.java b/src/main/java/org/geant/lgservice/pojos/Coordinates.java
deleted file mode 100644
index 622c71d4ad288808db47f9f768b50c39291c4bf4..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/pojos/Coordinates.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.geant.lgservice.pojos;
-
-import javax.xml.bind.annotation.XmlAttribute;
-
-public class Coordinates {
-
-	private String latitude;
-
-	private String longitude;
-
-	public Coordinates() {
-
-	}
-
-	public Coordinates(String latitude, String longitude) {
-		super();
-		this.latitude = latitude;
-		this.longitude = longitude;
-	}
-
-	public String getLatitude() {
-		return latitude;
-	}
-
-	@XmlAttribute(name = "lat")
-	public void setLatitude(String latitude) {
-		this.latitude = latitude;
-	}
-
-	public String getLongitude() {
-		return longitude;
-	}
-
-	@XmlAttribute(name = "long")
-	public void setLongitude(String longitude) {
-		this.longitude = longitude;
-	}
-
-}
diff --git a/src/main/java/org/geant/lgservice/pojos/Credentials.java b/src/main/java/org/geant/lgservice/pojos/Credentials.java
deleted file mode 100644
index 05f6defe925b12f3bff7d4e19a171609e14f2615..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/pojos/Credentials.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.geant.lgservice.pojos;
-
-import lombok.Builder;
-import lombok.Getter;
-
-@Getter
-@Builder
-public class Credentials {
-
-	private String username;
-
-	private String password;
-
-	private String keyFile;
-
-}
diff --git a/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java b/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
index 6ff035763a9c62acfc716e5198fd7a7218ccbc50..3f9b2abf1c66052a151a5867a61662568d0c7197 100644
--- a/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
+++ b/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
@@ -1,13 +1,11 @@
 package org.geant.lgservice.pojos;
 
-import java.util.List;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
 
-@Data
-@AllArgsConstructor
+import java.util.List;
+
+@Getter
 @NoArgsConstructor
 public class QueryInputRequest {
 
diff --git a/src/main/java/org/geant/lgservice/pojos/QueryResponse.java b/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
index 9ded0504bae9655840e85312b76ff20c5027090b..4d6584d66232ec7b864d18d7305c8b3fddb5feea 100644
--- a/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
+++ b/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
@@ -1,14 +1,12 @@
 package org.geant.lgservice.pojos;
 
-import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import java.util.Map;
 
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
+@Getter
+@Builder
 public class QueryResponse {
 
 	Map<String, CommandOutput> output;
diff --git a/src/main/java/org/geant/lgservice/pojos/Router.java b/src/main/java/org/geant/lgservice/pojos/Router.java
index 19ee044a55f41bd3d4fae90c1987661792252322..6975c43f3c1cb2593c5989053f93bbe020410324 100644
--- a/src/main/java/org/geant/lgservice/pojos/Router.java
+++ b/src/main/java/org/geant/lgservice/pojos/Router.java
@@ -1,32 +1,10 @@
 package org.geant.lgservice.pojos;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Getter;
 
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
+@Getter
 public class Router {
 
-	private Coordinates coordinates;
-
 	private String name;
 
-	private String authType;
-
-	private String city;
-
-	private String country;
-
-	private String popName;
-	
-	private String popCountryCode;
-	
-	private boolean selected;
-
-	private String abbreviatedName;
-
 }
diff --git a/src/main/java/org/geant/lgservice/repository/EquipmentRepository.java b/src/main/java/org/geant/lgservice/repository/EquipmentRepository.java
deleted file mode 100755
index 058a2c68b85668fd86eb4e58e271dc59ab7546eb..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/repository/EquipmentRepository.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.geant.lgservice.repository;
-
-import org.geant.lgservice.model.Equipment;
-import org.springframework.data.repository.CrudRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface EquipmentRepository extends CrudRepository<Equipment, Integer> {
-
-}
diff --git a/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java b/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
index 8e86c0a643330ecacca6bfd1e12885e1a4351281..39b98d64aeec92340a0d03a9cc779b5dc6a00dbb 100644
--- a/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
+++ b/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
@@ -1,22 +1,18 @@
 package org.geant.lgservice.rest;
 
-import java.util.List;
-import java.util.Map;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.geant.lgservice.exceptions.ConnectionException;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
 import org.geant.lgservice.pojos.QueryInputRequest;
 import org.geant.lgservice.pojos.QueryResponse;
-import org.geant.lgservice.pojos.Router;
 import org.geant.lgservice.security.LGUser;
 import org.geant.lgservice.services.BuildService;
 import org.geant.lgservice.services.LookingGlassService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.info.BuildProperties;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -25,7 +21,10 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.stream.Collectors.toMap;
 
 @RestController
 @RequestMapping("/rest")
@@ -51,17 +50,6 @@ public class LookingGlassRestController {
 		return ResponseEntity.ok(commands);
 	}
 
-	@GetMapping(value = "/routers/all")
-	public ResponseEntity<?> getAllRouters() {
-		LGUser currentUser = null;
-		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-		if (principal instanceof LGUser) {
-			currentUser = (LGUser) principal;
-		}
-		List<Router> routers = service.getAllRouters(currentUser);
-		return ResponseEntity.ok(routers);
-	}
-
 	@GetMapping(value = "/user/getLoggedInUser")
 	public ResponseEntity<?> getLoggedInUser() {
 		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
@@ -75,19 +63,16 @@ public class LookingGlassRestController {
 
 	@PostMapping(value = "/submit")
 	@CrossOrigin
-	public ResponseEntity<QueryResponse> submit(@RequestBody QueryInputRequest input) throws ConnectionException {
-		QueryResponse response = new QueryResponse();
-		Map<String, CommandOutput> result = service.submitQuery(input.getSelectedRouters(), input.getSelectedCommand(),
-				input.getArguments(), input.isDisplayAsXML());
-		if (result != null && !result.isEmpty())
-			response.setOutput(result);
-		else
-			throw new ConnectionException();
-		return ResponseEntity.ok(response);
+	public QueryResponse submit(@RequestBody QueryInputRequest input, @AuthenticationPrincipal LGUser user) {
+
+		Map<String, CommandOutput> result = input.getSelectedRouters().stream()
+				.map(router -> service.submitQuery(router.getName(), input.getSelectedCommand(), input.getArguments(), input.isDisplayAsXML(), user))
+				.collect(toMap(CommandOutput::getRouterName, output -> output));
+
+		return QueryResponse.builder().output(result).build();
 	}
 
 	@GetMapping(value = "/build")
-	@ApiOperation(value = "Get build information", response = String.class)
 	public ResponseEntity<?> getBuildInfo() {
 		BuildProperties buildProperties = buildService.getBuildInfo();
 		return ResponseEntity.ok(buildProperties);
diff --git a/src/main/java/org/geant/lgservice/security/LGUser.java b/src/main/java/org/geant/lgservice/security/LGUser.java
index 54859a3faf55f4e499e8c96e380d26575a5940cd..4aa362ed5a388b9a2060e2203bb54ad35c542b31 100644
--- a/src/main/java/org/geant/lgservice/security/LGUser.java
+++ b/src/main/java/org/geant/lgservice/security/LGUser.java
@@ -1,103 +1,69 @@
 package org.geant.lgservice.security;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.geant.lgservice.utils.LGRole;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Singular;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.User;
-
-public class LGUser extends User {
-
-	private static final long serialVersionUID = 1L;
-
-	private String federatedUser;
-	private String givenName;
-	private String surname;
-	private String mail;
-	private LGRole role;
-	private Collection<? extends GrantedAuthority> groups;
-
-	public static final String INTERNAL_GROUP = "GEANT Staff:All";
-	public static final String EXTERNAL_GROUP = "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs";
-
-	public LGUser(String federatedUser, String givenName, String surname, String mail,
-			Collection<? extends GrantedAuthority> groups) {
-
-		super(federatedUser, "DUMMY PASSWORD", true, true, true, true, groups);
-
-		this.federatedUser = federatedUser;
-		this.givenName = givenName;
-		this.surname = surname;
-		this.mail = mail;
-		this.groups = groups;
-
-		List<String> groupValues = new ArrayList<>();
-		for (GrantedAuthority grantedAuthority : groups) {
-			groupValues.add(grantedAuthority.getAuthority());
-		}
-
-		if (!groupValues.contains(INTERNAL_GROUP) && !groupValues.contains(EXTERNAL_GROUP)) {
-			this.role = LGRole.PUBLIC;
-		}
-
-		if (groupValues.contains(INTERNAL_GROUP)) {
-			this.role = LGRole.INTERNAL;
-		}
-
-		if (groupValues.contains(EXTERNAL_GROUP)) {
-			this.role = LGRole.EXTERNAL;
-		}
+import org.springframework.security.core.userdetails.UserDetails;
 
-	}
-
-	public String getSurname() {
-		return surname;
-	}
-
-	public void setSurname(String surname) {
-		this.surname = surname;
-	}
-
-	public String getGivenName() {
-		return givenName;
-	}
-
-	public void setGivenName(String givenName) {
-		this.givenName = givenName;
-	}
-
-	public String getMail() {
-		return mail;
-	}
-
-	public void setMail(String mail) {
-		this.mail = mail;
-	}
-
-	public String getFederatedUser() {
-		return federatedUser;
-	}
-
-	public void setFederatedUser(String federatedUser) {
-		this.federatedUser = federatedUser;
-	}
-
-	public Collection<? extends GrantedAuthority> getGroups() {
-		return groups;
-	}
-
-	public void setGroups(Collection<? extends GrantedAuthority> groups) {
-		this.groups = groups;
-	}
-
-	public LGRole getRole() {
-		return role;
-	}
+import java.util.Collection;
 
-	public void setRole(LGRole role) {
-		this.role = role;
-	}
+@Builder
+@Getter
+public class LGUser implements UserDetails {
+
+    private final String federatedUser;
+    private final String givenName;
+    private final String surname;
+    private final String mail;
+    @Singular
+    private final Collection<Group> groups;
+
+    @RequiredArgsConstructor
+    public enum Group implements GrantedAuthority{
+        INTERNAL,
+        EXTERNAL;
+
+        @Override
+        public String getAuthority() {
+            return name();
+        }
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return getGroups();
+    }
+
+    @Override
+    public String getPassword() {
+        return null;
+    }
+
+    @Override
+    public String getUsername() {
+        return federatedUser;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
 
 }
diff --git a/src/main/java/org/geant/lgservice/security/LGUserService.java b/src/main/java/org/geant/lgservice/security/LGUserService.java
index 506512f993831a945769920196dac7303b1cfe35..6949949ab3c245652e87b76e2136cff59bc10c38 100644
--- a/src/main/java/org/geant/lgservice/security/LGUserService.java
+++ b/src/main/java/org/geant/lgservice/security/LGUserService.java
@@ -1,39 +1,55 @@
 package org.geant.lgservice.security;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.saml.SAMLCredential;
 import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
+
 @Service
+@RequiredArgsConstructor
+@Slf4j
 public class LGUserService implements SAMLUserDetailsService {
 
-	private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
-
-	@Override
-	public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
-		String federatedUser = credential.getAttributeAsString("uid");
-		String givenName = credential.getAttributeAsString("givenName");
-		String surname = credential.getAttributeAsString("sn");
-		String mail = credential.getAttributeAsString("mail");
-
-		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
-		String[] groupNames = credential.getAttributeAsStringArray("isMemberOf");
-		if (groupNames != null) {
-			for (String role : groupNames) {
-				GrantedAuthority authority = new SimpleGrantedAuthority(role);
-				authorities.add(authority);
-			}
-		}
-		LOGGER.info("Current user : " + givenName + ", " + surname);
-		LGUser currentUser = new LGUser(federatedUser, givenName, surname, mail, authorities);
-		return currentUser;
-	}
+    private final Map<SamlKeyMappings.Key, String> keyMappings;
+    private final Map<String, LGUser.Group> groupMappings;
+
+    @Override
+    public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
+        log.debug("Extracting attributes using keys: {}", keyMappings);
+
+        String federatedUser = credential.getAttributeAsString(keyMappings.get(ID));
+        String givenName = credential.getAttributeAsString(keyMappings.get(GIVEN_NAME));
+        String surname = credential.getAttributeAsString(keyMappings.get(SURNAME));
+        String mail = credential.getAttributeAsString(keyMappings.get(EMAIL));
+
+        Collection<LGUser.Group> groups = Arrays.stream(credential.getAttributeAsStringArray(keyMappings.get(GROUPS)))
+                .map(groupMappings::get)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        log.info("Current user : " + givenName + ", " + surname);
+        return LGUser.builder()
+                .federatedUser(federatedUser)
+                .givenName(givenName)
+                .surname(surname)
+                .mail(mail)
+                .groups(groups)
+                .build();
+    }
 }
diff --git a/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java b/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bdbd6ded72d096807f2f740d6ec74fe51e5e960
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java
@@ -0,0 +1,24 @@
+package org.geant.lgservice.security;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConstructorBinding;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.unmodifiableMap;
+
+@ConstructorBinding
+@ConfigurationProperties("saml")
+public class SamlGroupMapping {
+    private final Map<LGUser.Group, String> groupMappings;
+
+    public SamlGroupMapping(Map<LGUser.Group, String> groupMappings) {
+        this.groupMappings = groupMappings;
+    }
+
+    public Map<String, LGUser.Group> groupMappings() {
+        return unmodifiableMap(groupMappings.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java b/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c2551775e4800c7cd89ad6d90ff4bd1f953b64b
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java
@@ -0,0 +1,38 @@
+package org.geant.lgservice.security;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConstructorBinding;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableMap;
+
+@ConstructorBinding
+@ConfigurationProperties("saml")
+public class SamlKeyMappings {
+
+    public enum Key {
+        ID,
+        GIVEN_NAME,
+        SURNAME,
+        EMAIL,
+        GROUPS
+    }
+
+    private final Map<Key, String> keyMappings;
+
+    public SamlKeyMappings(Map<Key, String> keyMappings) {
+        checkNotNull(keyMappings, "Missing saml key mappings, under 'saml.key-mappings'");
+        checkArgument(keyMappings.size() == Key
+                .values().length, format("Missing saml key mappings, required: %s", Arrays.toString(Key.values())));
+        this.keyMappings = keyMappings;
+    }
+
+    public Map<Key, String> mappings() {
+        return unmodifiableMap(keyMappings);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java b/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
index 2f27ad859d0c82e997cdf4d6212a5f343706c5f9..349490dd5ea24c1088569d13f1d4bb2aacb4f953 100644
--- a/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
+++ b/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
@@ -11,6 +11,7 @@ import org.opensaml.xml.parse.StaticBasicParserPool;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.io.DefaultResourceLoader;
@@ -71,65 +72,70 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.util.*;
+import java.util.Timer;
 
 import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
 
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(securedEnabled = true)
+@EnableConfigurationProperties({
+        SamlGroupMapping.class,
+        SamlKeyMappings.class
+})
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-	
-	@Autowired
-	AppConfig config;
-	
-	private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
-	
-	private String metadataURL;
- 
-	private Timer backgroundTaskTimer;
-	private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
-	
-	@PostConstruct
-	public void init() {
-		this.backgroundTaskTimer = new Timer(true);
-		this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
-		this.metadataURL = config.getMetadataURL();
-	}
-	
-	@PreDestroy
-	public void destroy() {
-		this.backgroundTaskTimer.purge();
-		this.backgroundTaskTimer.cancel();
-		this.multiThreadedHttpConnectionManager.shutdown();
-	}
-	
+
+    @Autowired
+    AppConfig config;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
+
+    private String metadataURL;
+
+    private Timer backgroundTaskTimer;
+    private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
+
+    @PostConstruct
+    public void init() {
+        this.backgroundTaskTimer = new Timer(true);
+        this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
+        this.metadataURL = config.getMetadataURL();
+    }
+
+    @PreDestroy
+    public void destroy() {
+        this.backgroundTaskTimer.purge();
+        this.backgroundTaskTimer.cancel();
+        this.multiThreadedHttpConnectionManager.shutdown();
+    }
+
     @Autowired
     private LGUserService userService;
-     
+
     @Bean
     public VelocityEngine velocityEngine() {
         return VelocityFactory.getEngine();
     }
- 
+
     @Bean(initMethod = "initialize")
     public StaticBasicParserPool parserPool() {
         return new StaticBasicParserPool();
     }
- 
+
     @Bean(name = "parserPoolHolder")
     public ParserPoolHolder parserPoolHolder() {
         return new ParserPoolHolder();
     }
- 
+
     // Bindings, encoders and decoders used for creating and parsing messages
     @Bean
-	public HttpClient httpClient() {
-		HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
-		httpClient.getHostConfiguration().setHost(
-		        fromHttpUrl(metadataURL).build().toUri().getHost());
-		return httpClient;
-	}
- 
+    public HttpClient httpClient() {
+        HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
+        httpClient.getHostConfiguration().setHost(
+                fromHttpUrl(metadataURL).build().toUri().getHost());
+        return httpClient;
+    }
+
     // SAML Authentication Provider responsible for validating of received SAML
     // messages
     @Bean
@@ -139,77 +145,77 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlAuthenticationProvider.setForcePrincipalAsString(false);
         return samlAuthenticationProvider;
     }
- 
+
     // Provider of default SAML Context
     @Bean
     public SAMLContextProviderImpl contextProvider() {
         return new SAMLContextProviderImpl();
     }
- 
+
     // Initialization of OpenSAML library
     @Bean
     public static SAMLBootstrap sAMLBootstrap() {
         return new SAMLBootstrap();
     }
- 
+
     // Logger for SAML messages and events
     @Bean
     public SAMLDefaultLogger samlLogger() {
         return new SAMLDefaultLogger();
     }
- 
+
     // SAML 2.0 WebSSO Assertion Consumer
     @Bean
     public WebSSOProfileConsumer webSSOprofileConsumer() {
         return new WebSSOProfileConsumerImpl();
     }
- 
+
     // SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
     @Bean
     public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
         return new WebSSOProfileConsumerHoKImpl();
     }
- 
+
     // SAML 2.0 Web SSO profile
     @Bean
     public WebSSOProfile webSSOprofile() {
         return new WebSSOProfileImpl();
     }
- 
+
     // SAML 2.0 Holder-of-Key Web SSO profile
     @Bean
     public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
         return new WebSSOProfileConsumerHoKImpl();
     }
- 
+
     // SAML 2.0 ECP profile
     @Bean
     public WebSSOProfileECPImpl ecpprofile() {
         return new WebSSOProfileECPImpl();
     }
- 
+
     @Bean
     public SingleLogoutProfile logoutprofile() {
         return new SingleLogoutProfileImpl();
     }
-    
+
     @Bean
-	public KeyManager keyManager() {
-    	DefaultResourceLoader loader = new DefaultResourceLoader();
-		Resource storeFile = loader.getResource(config.getKeyStore());
-		String storePass = config.getKeystorePassword();
-		Map<String, String> passwords = new HashMap<String, String>();
-		passwords.put(config.getKeystoreAlias(), config.getKeystorePassword());
-		return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias());
-	}
-    
+    public KeyManager keyManager() {
+        DefaultResourceLoader loader = new DefaultResourceLoader();
+        Resource storeFile = loader.getResource(config.getKeyStore());
+        String storePass = config.getKeystorePassword();
+        Map<String, String> passwords = new HashMap<String, String>();
+        passwords.put(config.getKeystoreAlias(), config.getKeystorePassword());
+        return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias());
+    }
+
     @Bean
     public WebSSOProfileOptions defaultWebSSOProfileOptions() {
         WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
         webSSOProfileOptions.setIncludeScoping(false);
         return webSSOProfileOptions;
     }
- 
+
     // Entry point to initialize authentication, default values taken from
     // properties file
     @Bean
@@ -218,19 +224,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
         return samlEntryPoint;
     }
-    
- // Setup advanced info about metadata
- 	@Bean
- 	public ExtendedMetadata extendedMetadata() {
- 		ExtendedMetadata extendedMetadata = new ExtendedMetadata();
- 		extendedMetadata.setIdpDiscoveryEnabled(false);
- 		extendedMetadata.setSignMetadata(false);
- 		extendedMetadata.setEcpEnabled(false);
- 		extendedMetadata.setSslHostnameVerification("allowAll");
- 		extendedMetadata.setSigningKey("spring");
- 		return extendedMetadata;
- 	}
-    
+
+    // Setup advanced info about metadata
+    @Bean
+    public ExtendedMetadata extendedMetadata() {
+        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
+        extendedMetadata.setIdpDiscoveryEnabled(false);
+        extendedMetadata.setSignMetadata(false);
+        extendedMetadata.setEcpEnabled(false);
+        extendedMetadata.setSslHostnameVerification("allowAll");
+        extendedMetadata.setSigningKey("spring");
+        return extendedMetadata;
+    }
+
     // IDP Discovery Service
     @Bean
     public SAMLDiscovery samlIDPDiscovery() {
@@ -238,46 +244,46 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         idpDiscovery.setIdpSelectionPath("/saml/idpSelection");
         return idpDiscovery;
     }
-    
-	@Bean(name = "metadata")
-	public CachingMetadataManager metadata() throws MetadataProviderException {
-
-		HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(),
-				this.metadataURL);
-
-		//provider.setMinRefreshDelay(3600000);
-		//provider.setMinRefreshDelay(3600000);
-		provider.setParserPool(parserPool());
-
-		ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata());
-		extendedMetadataDelegate.setMetadataTrustCheck(false);
-		extendedMetadataDelegate.setMetadataRequireSignature(false);
-		backgroundTaskTimer.purge();
-
-		List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
-		providers.add(extendedMetadataDelegate);
-		return new CachingMetadataManager(providers);
-
-	}
- 
-	@Bean
-	public MetadataGenerator metadataGenerator() {
-		MetadataGenerator metadataGenerator = new MetadataGenerator();
-		metadataGenerator.setEntityId(config.getEntityName());
-		metadataGenerator.setEntityBaseURL(config.getEntityBaseURL());
-		metadataGenerator.setExtendedMetadata(extendedMetadata());
-		metadataGenerator.setIncludeDiscoveryExtension(false);
-		metadataGenerator.setKeyManager(keyManager());
-		return metadataGenerator;
-	}
- 
+
+    @Bean(name = "metadata")
+    public CachingMetadataManager metadata() throws MetadataProviderException {
+
+        HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(),
+                this.metadataURL);
+
+        //provider.setMinRefreshDelay(3600000);
+        //provider.setMinRefreshDelay(3600000);
+        provider.setParserPool(parserPool());
+
+        ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata());
+        extendedMetadataDelegate.setMetadataTrustCheck(false);
+        extendedMetadataDelegate.setMetadataRequireSignature(false);
+        backgroundTaskTimer.purge();
+
+        List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
+        providers.add(extendedMetadataDelegate);
+        return new CachingMetadataManager(providers);
+
+    }
+
+    @Bean
+    public MetadataGenerator metadataGenerator() {
+        MetadataGenerator metadataGenerator = new MetadataGenerator();
+        metadataGenerator.setEntityId(config.getEntityName());
+        metadataGenerator.setEntityBaseURL(config.getEntityBaseURL());
+        metadataGenerator.setExtendedMetadata(extendedMetadata());
+        metadataGenerator.setIncludeDiscoveryExtension(false);
+        metadataGenerator.setKeyManager(keyManager());
+        return metadataGenerator;
+    }
+
     // The filter is waiting for connections on URL suffixed with filterSuffix
     // and presents SP metadata there
     @Bean
     public MetadataDisplayFilter metadataDisplayFilter() {
         return new MetadataDisplayFilter();
     }
-     
+
     // Handler deciding where to redirect user after successful login
     @Bean
     public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
@@ -287,17 +293,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         LOGGER.info("executing the authentication success handler");
         return successRedirectHandler;
     }
-    
-	// Handler deciding where to redirect user after failed login
+
+    // Handler deciding where to redirect user after failed login
     @Bean
     public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
-    	SimpleUrlAuthenticationFailureHandler failureHandler =
-    			new SimpleUrlAuthenticationFailureHandler();
-    	failureHandler.setUseForward(true);
-    	failureHandler.setDefaultFailureUrl("/error");
-    	return failureHandler;
+        SimpleUrlAuthenticationFailureHandler failureHandler =
+                new SimpleUrlAuthenticationFailureHandler();
+        failureHandler.setUseForward(true);
+        failureHandler.setDefaultFailureUrl("/error");
+        return failureHandler;
     }
-     
+
     @Bean
     public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
         SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
@@ -306,7 +312,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
         return samlWebSSOHoKProcessingFilter;
     }
-    
+
     // Processing filter for WebSSO profile messages
     @Bean
     public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
@@ -316,12 +322,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
         return samlWebSSOProcessingFilter;
     }
-     
+
     @Bean
     public MetadataGeneratorFilter metadataGeneratorFilter() {
         return new MetadataGeneratorFilter(metadataGenerator());
     }
-     
+
     // Handler for successful logout
     @Bean
     public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
@@ -329,17 +335,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         successLogoutHandler.setDefaultTargetUrl("/");
         return successLogoutHandler;
     }
-     
+
     // Logout handler terminating local session
     @Bean
     public SecurityContextLogoutHandler logoutHandler() {
-        SecurityContextLogoutHandler logoutHandler = 
-        		new SecurityContextLogoutHandler();
+        SecurityContextLogoutHandler logoutHandler =
+                new SecurityContextLogoutHandler();
         logoutHandler.setInvalidateHttpSession(true);
         logoutHandler.setClearAuthentication(true);
         return logoutHandler;
     }
- 
+
     // Filter processing incoming logout messages
     // First argument determines URL user will be redirected to after successful
     // global logout
@@ -348,59 +354,59 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         return new SAMLLogoutProcessingFilter(successLogoutHandler(),
                 logoutHandler());
     }
-     
+
     // Overrides default logout processing filter with the one processing SAML
     // messages
     @Bean
     public SAMLLogoutFilter samlLogoutFilter() {
         return new SAMLLogoutFilter(successLogoutHandler(),
-                new LogoutHandler[] { logoutHandler() },
-                new LogoutHandler[] { logoutHandler() });
+                new LogoutHandler[]{logoutHandler()},
+                new LogoutHandler[]{logoutHandler()});
     }
-	
+
     @Bean
     public HTTPSOAP11Binding soapBinding() {
         return new HTTPSOAP11Binding(parserPool());
     }
-    
+
     @Bean
     public HTTPPostBinding httpPostBinding() {
-    	return new HTTPPostBinding(parserPool(), velocityEngine());
+        return new HTTPPostBinding(parserPool(), velocityEngine());
     }
-    
+
     @Bean
     public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
-    	return new HTTPRedirectDeflateBinding(parserPool());
+        return new HTTPRedirectDeflateBinding(parserPool());
     }
-    
+
     @Bean
     public HTTPSOAP11Binding httpSOAP11Binding() {
-    	return new HTTPSOAP11Binding(parserPool());
+        return new HTTPSOAP11Binding(parserPool());
     }
-    
+
     @Bean
     public HTTPPAOS11Binding httpPAOS11Binding() {
-    	return new HTTPPAOS11Binding(parserPool());
+        return new HTTPPAOS11Binding(parserPool());
     }
-    
+
     // Processor
-	@Bean
-	public SAMLProcessorImpl processor() {
-		Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
-		bindings.add(httpRedirectDeflateBinding());
-		bindings.add(httpPostBinding());
-		//bindings.add(artifactBinding(parserPool(), velocityEngine()));
-		bindings.add(httpSOAP11Binding());
-		bindings.add(httpPAOS11Binding());
-		return new SAMLProcessorImpl(bindings);
-	}
-    
-	/**
-	 * Define the security filter chain in order to support SSO Auth by using SAML 2.0
-	 * 
-	 * @return Filter chain proxy
-	 * @throws Exception
-	 */
+    @Bean
+    public SAMLProcessorImpl processor() {
+        Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
+        bindings.add(httpRedirectDeflateBinding());
+        bindings.add(httpPostBinding());
+        //bindings.add(artifactBinding(parserPool(), velocityEngine()));
+        bindings.add(httpSOAP11Binding());
+        bindings.add(httpPAOS11Binding());
+        return new SAMLProcessorImpl(bindings);
+    }
+
+    /**
+     * Define the security filter chain in order to support SSO Auth by using SAML 2.0
+     *
+     * @return Filter chain proxy
+     * @throws Exception
+     */
     @Bean
     public FilterChainProxy samlFilter() throws Exception {
         List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
@@ -420,57 +426,67 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 samlIDPDiscovery()));
         return new FilterChainProxy(chains);
     }
-     
+
     /**
      * Returns the authentication manager currently used by Spring.
      * It represents a bean definition with the aim allow wiring from
      * other classes performing the Inversion of Control (IoC).
-     * 
-     * @throws  Exception 
+     *
+     * @throws Exception
      */
     @Bean
     @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }
-     
+
     /**
      * Defines the web based security configuration.
-     * 
-     * @param   http It allows configuring web based security for specific http requests.
-     * @throws  Exception 
+     *
+     * @param http It allows configuring web based security for specific http requests.
+     * @throws Exception
      */
-    @Override  
+    @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
-            .httpBasic()
+                .httpBasic()
                 .authenticationEntryPoint(samlEntryPoint());
         http
-        	.csrf()
-        		.disable();
+                .csrf()
+                .disable();
         http
-            .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
-            .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
-        
+                .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
+                .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
+
         http.authorizeRequests()
-        .antMatchers("/authenticate*/**").authenticated()
-        .anyRequest().permitAll();
-        
+                .antMatchers("/authenticate*/**").authenticated()
+                .anyRequest().permitAll();
+
         http
-            .logout()
+                .logout()
                 .logoutSuccessUrl("/");
     }
- 
+
     /**
      * Sets a custom authentication provider.
-     * 
-     * @param   auth SecurityBuilder used to create an AuthenticationManager.
-     * @throws  Exception 
+     *
+     * @param auth SecurityBuilder used to create an AuthenticationManager.
+     * @throws Exception
      */
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth
-            .authenticationProvider(samlAuthenticationProvider());
-    }   
+                .authenticationProvider(samlAuthenticationProvider());
+    }
+
+    @Bean
+    public Map<String, LGUser.Group> groupMappings(SamlGroupMapping mappings) {
+        return mappings.groupMappings();
+    }
+
+    @Bean
+    public Map<SamlKeyMappings.Key, String> keyMappings(SamlKeyMappings mappings) {
+        return mappings.mappings();
+    }
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/geant/lgservice/services/LookingGlassService.java b/src/main/java/org/geant/lgservice/services/LookingGlassService.java
index fb565f797475cdd6a87e05788d505fae78f95665..aa8e955f9aa0a7f5b8a79cd9cbd0bd4c705ceddf 100644
--- a/src/main/java/org/geant/lgservice/services/LookingGlassService.java
+++ b/src/main/java/org/geant/lgservice/services/LookingGlassService.java
@@ -1,20 +1,17 @@
 package org.geant.lgservice.services;
 
-import java.util.List;
-import java.util.Map;
-
 import org.geant.lgservice.pojos.Command;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
-import org.geant.lgservice.pojos.Router;
 import org.geant.lgservice.security.LGUser;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.List;
 
 public interface LookingGlassService {
 
-	List<Router> getAllRouters(final LGUser user);
-	
 	List<Group> getAllCommands(final LGUser user);
 
-	Map<String, CommandOutput> submitQuery(final List<Router> selectedRouters, final Command selectedCommand,
-			final String arguments, final boolean displayAsXML);
+	CommandOutput submitQuery(final String hostname, final Command selectedCommand,
+							  final String arguments, final boolean displayAsXML, UserDetails user);
 }
diff --git a/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java b/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
index 2965cddf54e3f5ebc2b65b5ad3ed8e9cea678ccf..be982e9769f993464cd323216760a391eead0540 100644
--- a/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
+++ b/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
@@ -1,21 +1,27 @@
 package org.geant.lgservice.services;
 
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
 import lombok.RequiredArgsConstructor;
-import org.geant.lgservice.ecmr.CommandExecutor;
+import org.apache.commons.lang.StringUtils;
+import org.geant.lgservice.domain.Router;
+import org.geant.lgservice.domain.RouterRepository;
+import org.geant.lgservice.exceptions.BusinessException;
+import org.geant.lgservice.infrastructure.ssh.SSHCommandExecutor;
 import org.geant.lgservice.pojos.Command;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
-import org.geant.lgservice.pojos.Router;
 import org.geant.lgservice.security.LGUser;
 import org.geant.lgservice.utils.LookingGlassHelper;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Service;
 
-import static java.util.stream.Collectors.toMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.geant.lgservice.domain.Router.Access.INTERNAL;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.FORBIDDEN;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.NOT_FOUND;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.UNAUTHORIZED;
 
 @Service
 @RequiredArgsConstructor
@@ -23,7 +29,9 @@ public class LookingGlassServiceImpl implements LookingGlassService {
 
 	private final LookingGlassHelper lookingGlassHelper;
 
-	private final CommandExecutor commandExecutor;
+	private final RouterRepository routerRepository;
+
+	private final SSHCommandExecutor sshCommandExecutor;
 
 	@Override
 	public List<Group> getAllCommands(final LGUser user) {
@@ -31,15 +39,29 @@ public class LookingGlassServiceImpl implements LookingGlassService {
 	}
 
 	@Override
-	public List<Router> getAllRouters(final LGUser user) {
-		return lookingGlassHelper.loadAllRouters(user);
+	public CommandOutput submitQuery(final String hostname, final Command selectedCommand,
+									 final String arguments, final boolean displayAsXML, UserDetails user) {
+
+		Router router = routerRepository.getByHostName(hostname)
+				.orElseThrow(() -> BusinessException.withReason(NOT_FOUND));
+
+		if (router.getAccess().equals(INTERNAL)) {
+			if (user == null){
+				throw BusinessException.withReason(UNAUTHORIZED);
+			}else if (!user.getAuthorities().contains(LGUser.Group.INTERNAL)){
+				throw BusinessException.withReason(FORBIDDEN);
+			}
+		}
+
+		return sshCommandExecutor.execute(hostname, getCommandWithArguments(selectedCommand, arguments, displayAsXML));
 	}
 
-	@Override
-	public Map<String, CommandOutput> submitQuery(final List<Router> selectedRouters, final Command selectedCommand,
-			final String arguments, final boolean displayAsXML) {
-		return selectedRouters.stream()
-				.map(router -> commandExecutor.execute(router, selectedCommand, arguments, displayAsXML))
-				.collect(toMap(CommandOutput::getRouterName, output -> output));
+	private String getCommandWithArguments(Command command, String arguments, boolean displayAsXML) {
+		return Stream.of(
+				command.getValue(),
+				arguments,
+				displayAsXML ? "| display xml" : "")
+				.filter(StringUtils::isNotBlank)
+				.collect(Collectors.joining(" "));
 	}
 }
diff --git a/src/main/java/org/geant/lgservice/utils/LookingGlassHelper.java b/src/main/java/org/geant/lgservice/utils/LookingGlassHelper.java
index 8078d98428ae086efd8f2635dc788597f314b99b..11a5f495c14a775d7fee12fe1fe803abc5a03fc9 100755
--- a/src/main/java/org/geant/lgservice/utils/LookingGlassHelper.java
+++ b/src/main/java/org/geant/lgservice/utils/LookingGlassHelper.java
@@ -1,57 +1,20 @@
 package org.geant.lgservice.utils;
 
 import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
 
 import org.geant.lgservice.config.CommandsParser;
-import org.geant.lgservice.model.Equipment;
-import org.geant.lgservice.pojos.Coordinates;
 import org.geant.lgservice.pojos.Group;
-import org.geant.lgservice.pojos.Router;
-import org.geant.lgservice.repository.EquipmentRepository;
 import org.geant.lgservice.security.LGUser;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import static java.util.Spliterator.ORDERED;
 import static java.util.Spliterators.spliteratorUnknownSize;
 import static java.util.stream.StreamSupport.stream;
-import static org.geant.lgservice.utils.LGRole.INTERNAL;
 
 @Component
 public class LookingGlassHelper {
 
 	public static final String COMMANDS_FILE_NAME = "commands";
 
-	@Autowired
-	private EquipmentRepository equipmentRepo;
-
-	public List<Router> loadAllRouters(final LGUser user) {
-		final boolean internalRoutersAvailable =
-				Optional.ofNullable(user)
-					.map(LGUser::getRole)
-					.filter(INTERNAL::equals)
-					.isPresent();
-
-
-		return stream(equipmentRepo.findAll().spliterator(), false)
-				.filter(equipment -> isValidRouter(internalRoutersAvailable, equipment))
-				.map(Equipment::toDomain)
-				.collect(Collectors.toList());
-	}
-
-	private boolean isValidRouter(final Boolean showInternalRouters, final Equipment equipment) {
-		if (showInternalRouters) {
-			return true;
-		} else {
-			if (equipment.getInternal().equals("no")) {
-				return true;
-			}
-		}
-		return false;
-	}
-
 	public List<Group> loadAllCommands(final LGUser user) {
 		return new CommandsParser().getCommandsFromXML(COMMANDS_FILE_NAME, user);
 	}
diff --git a/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java b/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java
deleted file mode 100644
index 610bec5c79ef4b5f25d254a0e2489018675f05b7..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import static org.junit.Assert.assertEquals;
-
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.Router;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class CallableCommandExecutorTest {
-
-	public static final String TEST_ROUTER = "test-router.geant.net";
-	public static final String TEST_COMMAND = "ping 25";
-
-	@Test
-	public void testGetCommandWithArgumentsXML() {
-		Router router = new Router();
-		router.setName(TEST_ROUTER);
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(true);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, "param1", true, null);
-		assertEquals("ping 25 param1 | display xml", callable.getCommandWithArguments());
-	}
-
-	@Test
-	public void testGetCommandWithoutArgumentsXML() {
-		Router router = new Router();
-		router.setName(TEST_ROUTER);
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(false);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, null, true, null);
-		assertEquals("ping 25 | display xml", callable.getCommandWithArguments());
-	}
-
-	@Test
-	public void testGetCommandWithoutArguments() {
-		Router router = new Router();
-		router.setName(TEST_ROUTER);
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(false);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, null, false, null);
-		assertEquals("ping 25", callable.getCommandWithArguments());
-	}
-
-}
diff --git a/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java b/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9adef25bb14684f4c67041e91925ec77ff647c8e
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java
@@ -0,0 +1,110 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+public class SSHHostTest {
+
+    private static final String HOSTNAME = "localhost";
+    private static final String RESPONSE_STRING = "A Response String";
+
+    private Connection connection = mock(Connection.class);
+    private Session session = mock(Session.class);
+    private SSHHost subject;
+
+    private PipedInputStream response;
+    private PipedOutputStream responseSource;
+
+    @Before
+    @SneakyThrows
+    public void setup() {
+        reset(connection);
+        when(connection.isAuthenticationComplete()).thenReturn(true);
+        when(connection.openSession()).thenReturn(session);
+        when(connection.getHostname()).thenReturn(HOSTNAME);
+
+        responseSource = new PipedOutputStream();
+        response = new PipedInputStream(responseSource);
+        when(session.getStdout()).thenReturn(response);
+        when(session.getStderr()).thenReturn(response);
+
+        responseSource.write(RESPONSE_STRING.getBytes());
+        responseSource.close();
+
+        subject = SSHHost.fromConnection(connection);
+    }
+
+    @Test
+    public void commandExecutedSuccessfully() {
+        when(session.getExitStatus()).thenReturn(0);
+        assertThat(subject.execute("some command")).isEqualTo(RESPONSE_STRING);
+    }
+
+    @Test
+    public void commandExitedWithNonZeroStatus() {
+        when(session.getExitStatus()).thenReturn(1);
+        assertThat(subject.execute("some command")).isEqualTo(RESPONSE_STRING);
+    }
+
+    @Test(expected = TechnicalException.class)
+    public void createWithUnAuthenticatedConnection() {
+        reset(connection);
+        when(connection.isAuthenticationComplete()).thenReturn(false);
+        SSHHost.fromConnection(connection);
+        fail("subject should have thrown an exception");
+    }
+
+    @Test(expected = TechnicalException.class)
+    public void sessionThrowsIOException() {
+        try {
+            Mockito.doThrow(new IOException()).when(session).execCommand(any(String.class));
+        } catch (IOException e) {
+            fail("Mock should not throw IOException", e);
+        }
+        subject.execute("some command");
+        fail("subject should have thrown an exception");
+    }
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    private static class StateListenerOutput {
+        private String host;
+        private SSHHost.ConnectionStateListener.State state;
+    }
+
+    @Test
+    public void connectionStateListenersTriggered() {
+        final StateListenerOutput listenerOutput = new StateListenerOutput();
+
+        subject.withConnectionStateListener((host, state) -> {
+            listenerOutput.setHost(host.hostname());
+            listenerOutput.setState(state);
+        });
+
+        subject.connectionLost(new IOException());
+
+        assertThat(listenerOutput.getHost()).isEqualTo(HOSTNAME);
+        assertThat(listenerOutput.getState()).isEqualTo(DISCONNECTED);
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/BaseIT.java b/src/test/java/org/geant/lgservice/integration/BaseIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0ab1a60a811290c37d943d90002562dfc2566b8
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/BaseIT.java
@@ -0,0 +1,97 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedScenarioTest;
+import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
+import com.tngtech.jgiven.integration.spring.SpringStageCreator;
+import lombok.SneakyThrows;
+import org.geant.lgservice.LookingGlassServiceApplication;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.geant.lgservice.infrastructure.ssh.ConnectionManager;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        webEnvironment = RANDOM_PORT,
+        classes = {LookingGlassServiceApplication.class, Config.class}
+)
+@TestPropertySource(properties = {
+        "server.ssl.enabled=false"
+})
+@DirtiesContext(classMode = BEFORE_CLASS)
+public abstract class BaseIT <GIVEN, WHEN, THEN> extends ExtendedScenarioTest<GIVEN, WHEN, THEN> implements BeanFactoryAware {
+    @LocalServerPort
+    protected int port;
+
+    @Autowired
+    private PortSink portSink;
+
+    @Autowired
+    private ConnectionFactory connectionFactory;
+
+    @Autowired
+    private ConnectionManager connectionManager;
+
+    @Autowired
+    private Connection connection;
+
+    @Autowired
+    private Session session;
+
+    interface PortSink {
+        void sink(int port);
+    }
+
+    @ClassRule
+    public static final WireMockClassRule INVENTORY_PROVIDER = new WireMockClassRule(wireMockConfig().dynamicPort());
+
+    @Before
+    @SneakyThrows
+    public void setup() {
+        portSink.sink(port);
+        INVENTORY_PROVIDER.resetAll();
+        reset(connectionFactory);
+        reset(connection);
+        reset(session);
+        Mockito.when(connectionFactory.createConnection(any(String.class))).thenReturn(connection);
+        Mockito.when(connection.openSession()).thenReturn(session);
+        Mockito.when(connection.isAuthenticationComplete()).thenReturn(true);
+
+        Field hostField = ConnectionManager.class.getDeclaredField("hosts");
+        hostField.setAccessible(true);
+        Map hosts = (Map)hostField.get(connectionManager);
+        hosts.clear();
+    }
+
+    @Override
+    public void setBeanFactory( BeanFactory beanFactory ) {
+        getScenario().setStageCreator( beanFactory.getBean( SpringStageCreator.class ) );
+    }
+
+    @BeforeClass
+    public static void setupClass() {
+        System.setProperty("inventoryprovider.baseUrl", String.format("http://localhost:%s/lg/", INVENTORY_PROVIDER.port()));
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/Config.java b/src/test/java/org/geant/lgservice/integration/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..137c9338bedb4d01bc112ffe364a5bc9bc4b3721
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/Config.java
@@ -0,0 +1,99 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.tngtech.jgiven.integration.spring.EnableJGiven;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.support.HttpRequestWrapper;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.io.IOException;
+import java.net.URI;
+
+import static java.util.Optional.of;
+import static org.geant.lgservice.integration.Context.withDefaults;
+
+@Configuration
+@EnableJGiven
+@ComponentScan(basePackages = { "org.geant.lgservice.integration"})
+@MockBean({
+        ConnectionFactory.class,
+        Connection.class,
+        Session.class
+})
+public class Config {
+
+    @Bean
+    public RestTemplate restTemplate(PortInjectingInterceptor interceptor) {
+        return new RestTemplateBuilder()
+                .rootUri("http://localhost")
+                .interceptors(interceptor)
+                .messageConverters(new MappingJackson2HttpMessageConverter())
+                .build();
+    }
+
+    @Bean
+    PortInjectingInterceptor portInjector() {
+        return new PortInjectingInterceptor();
+    }
+
+    static class PortInjectingInterceptor implements ClientHttpRequestInterceptor, BaseIT.PortSink {
+
+        private int port;
+
+        @Override
+        public ClientHttpResponse intercept(
+                HttpRequest httpRequest,
+                byte[] bytes,
+                ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
+
+            HttpRequest req = new HttpRequestWrapper(httpRequest) {
+                @Override
+                public URI getURI() {
+                    return of(port)
+                            .map(port -> UriComponentsBuilder.fromUri(super.getURI())
+                                    .host("localhost")
+                                    .port(port)
+                                    .build()
+                                    .toUri())
+                            .get();
+                }
+            };
+
+            return clientHttpRequestExecution.execute(req, bytes);
+        }
+
+        @Override
+        public void sink(int port) {
+            this.port = port;
+        }
+    }
+
+    @Bean
+    public Context context(){
+        return withDefaults();
+    }
+
+    @Bean
+    public MockMvc mockMvc(WebApplicationContext context) {
+        return MockMvcBuilders.webAppContextSetup(context)
+                .apply(SecurityMockMvcConfigurers.springSecurity())
+                .build();
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/Context.java b/src/test/java/org/geant/lgservice/integration/Context.java
new file mode 100644
index 0000000000000000000000000000000000000000..f97e2afd50709426ae60d282815d67645bff758c
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/Context.java
@@ -0,0 +1,151 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.geant.lgservice.security.LGUser;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static org.geant.lgservice.integration.Context.Router.INTERNAL;
+import static org.geant.lgservice.integration.Context.Router.PUBLIC;
+import static org.mockito.Mockito.mock;
+
+@Getter
+@Builder
+public class Context {
+
+    @Getter
+    @RequiredArgsConstructor
+    enum Router {
+        PUBLIC("mx2.tal.ee.geant.net"),
+        INTERNAL("mx1.cbg.uk.geant.net");
+
+        private final String host;
+    }
+
+    private Router router;
+    private String command;
+    private LGUser user;
+
+    private StubMapping inventoryProviderMapping;
+
+    private int responseStatus;
+    private SubmitResponseBody submitResponseBody;
+    private Collection<CommandGroup> commandResponseBody;
+
+    public Context withPublicRouter() {
+        this.router = PUBLIC;
+        return this;
+    }
+
+    public Context withInternalRouter() {
+        this.router = INTERNAL;
+        return this;
+    }
+
+    public Context withCommand(String command) {
+        this.command = command;
+        return this;
+    }
+
+    public Context withUser(LGUser user) {
+        this.user = user;
+        return this;
+    }
+
+    public Context withInventoryProviderStub(StubMapping mapping) {
+        this.inventoryProviderMapping = mapping;
+        return this;
+    }
+
+    public Context withResponseStatus(int responseStatus) {
+        this.responseStatus = responseStatus;
+        return this;
+    }
+
+    public Context withSubmitResponseBody(SubmitResponseBody submitResponseBody) {
+        this.submitResponseBody = submitResponseBody;
+        return this;
+    }
+
+    public Context withCommandResponseBody(Collection<CommandGroup> commandResponseBody) {
+        this.commandResponseBody = commandResponseBody;
+        return this;
+    }
+
+    public String getRouterHost() {
+        return router.getHost();
+    }
+
+    @SneakyThrows
+    public static Context withDefaults() {
+        return Context.builder()
+                .inventoryProviderMapping(stubFor(get("/lg/routers/all")
+                        .willReturn(WireMock.aResponse()
+                                .withStatus(200)
+                                .withHeader("Content-Type", "application/json")
+                                .withBodyFile("routers.json"))))
+                .build()
+                .withPublicRouter()
+                .withCommand("show config");
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class SubmitResponseBody {
+
+        private final Map<String, CommandResult> output;
+
+        @JsonCreator
+        public SubmitResponseBody(@JsonProperty("output") Map<String, CommandResult> output) {
+            this.output = output;
+        }
+
+        public CommandResult result() {
+            return output.values()
+                    .stream()
+                    .findFirst()
+                    .orElseThrow(() -> new AssertionError("There should be command result in the response"));
+        }
+
+        @Getter
+        public static class CommandResult {
+
+            private String routerName;
+            @JsonProperty("commandResult")
+            private String response;
+            private Long executionTime;
+        }
+    }
+
+    @Getter
+    public static class CommandGroup {
+
+        private List<Command> commands;
+        private String name;
+
+        @Getter
+        public static class Command {
+            private String name;
+            private String value;
+            private String description;
+            private boolean requiresParams;
+        }
+    }
+
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java b/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..da1b79cd83491c2f084654001b4a04a78c33cac5
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java
@@ -0,0 +1,66 @@
+package org.geant.lgservice.integration.scenario;
+
+import org.geant.lgservice.integration.BaseIT;
+import org.junit.Test;
+
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.EXTERNAL;
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.INTERNAL;
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.PUBLIC;
+
+public class GetCommandsIT extends BaseIT<Given, When, Then> {
+
+    @Test
+    public void anonymous_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().anonymous();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_not_for_$_users(EXTERNAL)
+                        .and().are_not_for_$_users(INTERNAL);
+    }
+
+    @Test
+    public void external_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .but().does_not_have_internal_access();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_for_$_users(EXTERNAL)
+                        .and().are_not_for_$_users(INTERNAL);
+    }
+
+    @Test
+    public void internal_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().logged_in();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_for_$_users(EXTERNAL)
+                        .and().are_for_$_users(INTERNAL);
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/Given.java b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
new file mode 100644
index 0000000000000000000000000000000000000000..6dc87eeb717e2c1707f1a50056f0ad706c6e0aff
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
@@ -0,0 +1,169 @@
+package org.geant.lgservice.integration.scenario;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.http.ResponseDefinition;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import com.tngtech.jgiven.annotation.AfterStage;
+import com.tngtech.jgiven.annotation.BeforeScenario;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.annotation.ProvidedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.SneakyThrows;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.geant.lgservice.integration.BaseIT;
+import org.geant.lgservice.integration.Context;
+import org.geant.lgservice.security.LGUser;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Collections;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static org.geant.lgservice.integration.Context.withDefaults;
+import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
+import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
+import static org.mockito.ArgumentMatchers.any;
+
+@JGivenStage
+public class Given extends ExtendedStage<Given> {
+
+    @ProvidedScenarioState
+    private Context context;
+
+    @Autowired
+    @ProvidedScenarioState
+    private ConnectionFactory connectionFactory;
+
+    @Autowired
+    @ProvidedScenarioState
+    private Session session;
+
+    @BeforeScenario
+    public void setup() {
+        context = withDefaults();
+    }
+
+    public GivenUser user() {
+        return nest(GivenUser.class);
+    }
+
+    public static class GivenUser extends NestedStage<GivenUser, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public GivenUser anonymous() {
+            return self();
+        }
+
+        public GivenUser logged_in() {
+            context.withUser(
+                    LGUser.builder()
+                            .federatedUser("1")
+                            .givenName("barry")
+                            .surname("scott")
+                            .mail("barry.scott@geant.org")
+                            .group(INTERNAL)
+                            .group(EXTERNAL)
+                            .build());
+            return self();
+        }
+
+        public GivenUser does_not_have_internal_access() {
+            LGUser user = context.getUser();
+            context.withUser(
+                    LGUser.builder()
+                            .federatedUser(user.getFederatedUser())
+                            .givenName(user.getGivenName())
+                            .surname(user.getSurname())
+                            .mail(user.getMail())
+                            .group(EXTERNAL)
+                            .build());
+            return self();
+        }
+    }
+
+    public GivenRouter router() {
+        return nest(GivenRouter.class);
+    }
+
+    public static class GivenRouter extends NestedStage<GivenRouter, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        @Autowired
+        @ExpectedScenarioState
+        private ConnectionFactory connectionFactory;
+
+        @ExpectedScenarioState
+        private Session session;
+
+        public GivenRouter publicly_accessible() {
+            context.withPublicRouter();
+            return self();
+        }
+
+        public GivenRouter internal() {
+            context.withInternalRouter();
+            return self();
+        }
+
+        @SneakyThrows
+        public GivenRouter ok() {
+            PipedOutputStream responseSource = new PipedOutputStream();
+            PipedInputStream response = new PipedInputStream(responseSource);
+
+            Mockito.when(session.getStdout()).thenReturn(response);
+
+            Mockito.when(session.getExitStatus())
+                    .thenReturn(0);
+
+            Mockito.doAnswer(invocation -> {
+                responseSource.write(invocation.getArgument(0, String.class).getBytes());
+                responseSource.flush();
+                responseSource.close();
+                return null;
+            }).when(session).execCommand(any(String.class));
+
+            return self();
+        }
+    }
+
+    public GivenInventoryProvider inventory_provider() {
+        return nest(GivenInventoryProvider.class);
+    }
+
+    public static class GivenInventoryProvider extends NestedStage<GivenInventoryProvider, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public GivenInventoryProvider returns_(int status) {
+            context.withInventoryProviderStub(get("/lg/routers/all")
+                    .willReturn(WireMock.aResponse()
+                            .withStatus(status))
+                    .build());
+            return self();
+        }
+
+        public GivenInventoryProvider bad_body() {
+            StubMapping mapping = context.getInventoryProviderMapping();
+            ResponseDefinition response = mapping.getResponse();
+            mapping.setResponse(WireMock.aResponse().withStatus(response.getStatus())
+                    .withBody("{\"This is not valid response body\":true").build());
+            return self();
+        }
+    }
+
+    @AfterStage
+    public void setupMocks() {
+        BaseIT.INVENTORY_PROVIDER.resetAll();
+        BaseIT.INVENTORY_PROVIDER.addStubMapping(context.getInventoryProviderMapping());
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..503caa603154bffa8984e78ad745a427b4a40e7d
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
@@ -0,0 +1,161 @@
+package org.geant.lgservice.integration.scenario;
+
+import org.geant.lgservice.integration.BaseIT;
+import org.junit.Test;
+
+public class SubmitCommandRequestIT extends BaseIT<Given, When, Then> {
+
+    @Test
+    public void anonymous_user_sends_command_to_public_router(){
+        given()
+                .a().user()
+                    .that().is().anonymous().backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void anonymous_user_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().anonymous().backup()
+                .and().a().router()
+                    .that().is().internal();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+            .the().response()
+                .is().an().error()
+                    .with_a().status(401);
+    }
+
+    @Test
+    public void logged_in_user_sends_command_to_public_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void logged_in_user_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .backup()
+                .and().a().router()
+                    .that().is().internal()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void logged_in_user_without_internal_access_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .but().does_not_have_internal_access()
+                    .backup()
+                .and().a().router()
+                    .that().is().internal();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with_a().status(403);
+    }
+
+    @Test
+    public void inventory_provider_returns_client_error() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(400);
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+    @Test
+    public void inventory_provider_returns_server_error() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(500);
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+    @Test
+    public void inventory_provider_returns_invalid_body() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(200)
+                    .with().a().bad_body();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/Then.java b/src/test/java/org/geant/lgservice/integration/scenario/Then.java
new file mode 100644
index 0000000000000000000000000000000000000000..6af304fde81cdec81ed831c30f6fdfc6aab6cc02
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/Then.java
@@ -0,0 +1,112 @@
+package org.geant.lgservice.integration.scenario;
+
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.SneakyThrows;
+import org.geant.lgservice.integration.Context;
+import org.mockito.ArgumentCaptor;
+
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@JGivenStage
+public class Then extends ExtendedStage<Then> {
+
+    @ExpectedScenarioState
+    private Context context;
+
+    @ExpectedScenarioState
+    private Session session;
+
+    @SneakyThrows
+    public Then command_is_executed() {
+        ArgumentCaptor<String> command = ArgumentCaptor.forClass(String.class);
+
+        verify(session, times(1))
+                .execCommand(command.capture());
+        assertThat(command.getValue()).isEqualTo(context.getCommand());
+        return self();
+    }
+
+    public Then result_is_returned() {
+        Context.SubmitResponseBody submitResponseBody = context.getSubmitResponseBody();
+        assertThat(submitResponseBody).isNotNull();
+
+        Context.SubmitResponseBody.CommandResult result = submitResponseBody.result();
+        assertThat(result).isNotNull();
+        assertThat(result.getRouterName()).isEqualTo(context.getRouterHost());
+        assertThat(result.getResponse()).isEqualTo(context.getCommand());
+
+        return self();
+    }
+
+    public ThenResponse response() {
+        return nest(ThenResponse.class);
+    }
+
+    public static class ThenResponse extends NestedStage<ThenResponse, Then> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public ThenResponseError error() {
+            assertThat(context.getResponseStatus()).isNotEqualTo(200);
+            return nest(ThenResponseError.class);
+        }
+
+        public ThenResponse ok() {
+            assertThat(context.getResponseStatus()).isEqualTo(200);
+            return self();
+        }
+
+        public static class ThenResponseError extends NestedStage<ThenResponseError, ThenResponse> {
+
+            @ExpectedScenarioState
+            private Context context;
+
+            public ThenResponseError status(int status) {
+                assertThat(context.getResponseStatus()).isEqualTo(status);
+                return self();
+            }
+
+            public ThenResponseError with_a() {
+                return self();
+            }
+        }
+
+        public ThenCommandResponse has_commands() {
+            assertThat(context.getCommandResponseBody()).isNotNull();
+            return nest(ThenCommandResponse.class);
+        }
+
+        public static class ThenCommandResponse extends NestedStage<ThenCommandResponse, ThenResponse> {
+            @ExpectedScenarioState
+            private Context context;
+
+            public enum UserType {
+                PUBLIC,
+                EXTERNAL,
+                INTERNAL
+            }
+
+            public ThenCommandResponse are_for_$_users(UserType userType) {
+                assertThat(context.getCommandResponseBody().stream().map(Context.CommandGroup::getName).collect(Collectors.toList()))
+                        .contains(String.format("%s-group", userType.name().toLowerCase()));
+                return self();
+            }
+
+            public ThenCommandResponse are_not_for_$_users(UserType userType) {
+                assertThat(context.getCommandResponseBody().stream().map(Context.CommandGroup::getName).collect(Collectors.toList()))
+                        .doesNotContain(String.format("%s-group", userType.name().toLowerCase()));
+                return self();
+            }
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/When.java b/src/test/java/org/geant/lgservice/integration/scenario/When.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fa83d8f0de4284e5bad4beddfacb70d9c355647
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/When.java
@@ -0,0 +1,132 @@
+package org.geant.lgservice.integration.scenario;
+
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.geant.lgservice.integration.Context;
+import org.geant.lgservice.integration.Context.SubmitResponseBody;
+import org.geant.lgservice.security.LGUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import static java.util.Collections.singletonList;
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.http.HttpMethod.POST;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
+
+@JGivenStage
+@Slf4j
+public class When extends ExtendedStage<When> {
+
+    @ExpectedScenarioState
+    private Context context;
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private MockMvc mvc;
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @SneakyThrows
+    public When request_to_the_router_is_sent() {
+        MockHttpServletResponse response = send(POST, "rest/submit", MAPPER.writeValueAsString(RequestBody.builder()
+                .command(context.getCommand())
+                .routerHost(context.getRouterHost())
+                .build()));
+
+        context.withResponseStatus(response.getStatus());
+        if (response.getStatus() == 200) {
+            String responseBody = response.getContentAsString();
+            log.info("response body = {}", responseBody);
+            context.withSubmitResponseBody(MAPPER.readValue(responseBody, SubmitResponseBody.class));
+        }
+
+        return self();
+    }
+
+    @SneakyThrows
+    public When request_for_available_commands_is_sent() {
+        MockHttpServletResponse response = send(GET,"rest/commands/all", null);
+        if (response.getStatus() == 200) {
+            String responseBody = response.getContentAsString();
+            log.info("response body = {}", responseBody);
+            context.withCommandResponseBody(MAPPER.readValue(responseBody, new TypeReference<Collection<Context.CommandGroup>>() {}));
+        }
+        return self();
+    }
+
+    @SneakyThrows
+    private MockHttpServletResponse send(HttpMethod method, String path, String body) {
+        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders
+                .request(method, UriComponentsBuilder.newInstance().scheme("http").path(path).build().toUri())
+                .contentType(APPLICATION_JSON);
+
+        if (StringUtils.isNotBlank(body)) {
+            requestBuilder.content(body);
+        }
+
+        LGUser user = context.getUser();
+        if (user != null) {
+            requestBuilder.with(user(user));
+        }
+
+        MockHttpServletResponse response = mvc.perform(requestBuilder).andReturn().getResponse();
+        context.withResponseStatus(response.getStatus());
+        return response;
+    }
+
+    @Getter
+    public static class RequestBody {
+
+        private final Collection<Router> selectedRouters;
+        private final Command selectedCommand;
+
+        @Builder
+        private RequestBody(String routerHost, String command) {
+            this.selectedRouters = singletonList(Router.withHost(routerHost));
+            this.selectedCommand = Command.withCommand(command);
+        }
+
+        @RequiredArgsConstructor
+        @Getter
+        public static class Router {
+
+            private final String name;
+
+            public static Router withHost(String hostName) {
+                return new Router(hostName);
+            }
+        }
+
+        @RequiredArgsConstructor
+        @Getter
+        public static class Command {
+
+            private final String value;
+
+            public static Command withCommand(String command) {
+                return new Command(command);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java b/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..50f5332788d2478f5a67520d0ef15bee9a0735e6
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java
@@ -0,0 +1,71 @@
+package org.geant.lgservice.security;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.security.saml.SAMLCredential;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
+import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+public class LGUserServiceTest {
+
+    SAMLCredential credential = mock(SAMLCredential.class);
+
+    LGUserService subject = new LGUserService(
+            ImmutableMap.<SamlKeyMappings.Key, String>builder()
+                    .put(ID, "uid")
+                    .put(GIVEN_NAME, "givenName")
+                    .put(SURNAME, "sn")
+                    .put(EMAIL, "mail")
+                    .put(GROUPS, "isMemberOf")
+                    .build(),
+            ImmutableMap.<String, LGUser.Group>builder()
+                    .put("internal", INTERNAL)
+                    .put("external", EXTERNAL)
+                    .build());
+
+    @Before
+    public void setup() {
+        reset(credential);
+        when(credential.getAttributeAsString("uid")).thenReturn("1");
+        when(credential.getAttributeAsString("givenName")).thenReturn("barry");
+        when(credential.getAttributeAsString("sn")).thenReturn("scott");
+        when(credential.getAttributeAsString("mail")).thenReturn("barry.scott@geant.org");
+    }
+
+    @Test
+    public void loadUserWithMultipleGroups() {
+        when(credential.getAttributeAsStringArray("isMemberOf"))
+                .thenReturn(new String[]{"internal", "external", "ignore"});
+
+        LGUser result = (LGUser) subject.loadUserBySAML(credential);
+        assertThat(result.getFederatedUser()).isEqualTo("1");
+        assertThat(result.getGivenName()).isEqualTo("barry");
+        assertThat(result.getSurname()).isEqualTo("scott");
+        assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
+        assertThat(result.getAuthorities()).containsExactlyInAnyOrder(INTERNAL, EXTERNAL);
+    }
+
+    @Test
+    public void loadUser() {
+        when(credential.getAttributeAsStringArray("isMemberOf"))
+                .thenReturn(new String[]{"external", "ignore"});
+
+        LGUser result = (LGUser) subject.loadUserBySAML(credential);
+        assertThat(result.getFederatedUser()).isEqualTo("1");
+        assertThat(result.getGivenName()).isEqualTo("barry");
+        assertThat(result.getSurname()).isEqualTo("scott");
+        assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
+        assertThat(result.getAuthorities()).containsExactlyInAnyOrder(EXTERNAL);
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java b/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java
deleted file mode 100644
index ba313f7892e7071784d0847afc444adc73aab999..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.geant.lgservice.services;
-
-import org.assertj.core.api.Assertions;
-import org.geant.lgservice.ecmr.CommandExecutor;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Coordinates;
-import org.geant.lgservice.pojos.Router;
-import org.hibernate.result.Output;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import java.util.Collections;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.*;
-
-public class LookingGlassServiceImplTest {
-
-    private CommandExecutor commandExecutor = mock(CommandExecutor.class);
-
-    private LookingGlassServiceImpl subject = new LookingGlassServiceImpl(null, commandExecutor);
-
-    @Before
-    public void setup() {
-        reset(commandExecutor);
-    }
-
-    @Test
-    public void testQuery() {
-        Router router = Router.builder()
-                .name("router.geant.org")
-                .build();
-
-        Command command = new Command();
-        String arguments = "some args";
-
-        CommandOutput output = new CommandOutput();
-        output.setRouterName(router.getName());
-
-        when(commandExecutor.execute(any(Router.class), any(Command.class), any(String.class), anyBoolean())).thenReturn(output);
-
-        Map<String, CommandOutput> result = subject.submitQuery(Collections.singletonList(router), command, arguments, false);
-
-        assertThat(result).hasSize(1);
-        assertThat(result).containsKey(router.getName());
-        assertThat(result.get(router.getName())).isEqualTo(output);
-    }
-}
diff --git a/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java b/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java
deleted file mode 100644
index 4669aad648d8c1b60982c7caa1a9544737bc45f5..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.geant.lgservice.services;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-
-import org.geant.lgservice.pojos.Group;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class LookingGlassServiceTest {
-
-	@Autowired
-	LookingGlassService fixture;
-
-	@Test
-	public void test() {
-		List<Group> commandGroups = fixture.getAllCommands(null);
-		assertEquals("number of commands not correct", 1, commandGroups.size());
-		assertEquals("group name not correct", "test-group", commandGroups.get(0).getName());
-		assertEquals("command name not correct", "test-name", commandGroups.get(0).getCommands().get(0).getName());
-	}
-
-}
diff --git a/src/test/resources/__files/routers.json b/src/test/resources/__files/routers.json
new file mode 100644
index 0000000000000000000000000000000000000000..c3e1d17320216e3e591da8824fdf8ecf79a5ffd7
--- /dev/null
+++ b/src/test/resources/__files/routers.json
@@ -0,0 +1,470 @@
+[
+  {
+    "equipment name": "mx1.cbg.uk.geant.net",
+    "pop": {
+      "abbreviation": "lab",
+      "city": "Cambridge",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "DANTE Lab"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx2.tal.ee.geant.net",
+    "pop": {
+      "abbreviation": "tal",
+      "city": "Tallinn",
+      "country": "Estonia",
+      "country code": "EE",
+      "latitude": 59.434825,
+      "longitude": 24.71385,
+      "name": "Tallinn"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lon.uk.geant.net",
+    "pop": {
+      "abbreviation": "lon",
+      "city": "London",
+      "country": "England",
+      "country code": "UK",
+      "latitude": 51.498652777778,
+      "longitude": -0.015805555555556,
+      "name": "London"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.pra.cz.geant.net",
+    "pop": {
+      "abbreviation": "pra",
+      "city": "Prague",
+      "country": "Czech Republic",
+      "country code": "CZ",
+      "latitude": 50.101847222222,
+      "longitude": 14.391738888889,
+      "name": "Prague"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.sof.bg.geant.net",
+    "pop": {
+      "abbreviation": "sof",
+      "city": "Sofia",
+      "country": "Bulgaria",
+      "country code": "BG",
+      "latitude": 42.675758333333,
+      "longitude": 23.370986111111,
+      "name": "Sofia"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.rig.lv.geant.net",
+    "pop": {
+      "abbreviation": "rig",
+      "city": "Riga",
+      "country": "Latvia",
+      "country code": "LV",
+      "latitude": 56.948344444444,
+      "longitude": 24.118,
+      "name": "Riga"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lon2.uk.geant.net",
+    "pop": {
+      "abbreviation": "lon2",
+      "city": "Slough",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 51.521986111111,
+      "longitude": 0.62331666666667,
+      "name": "London 2 (Slough)"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ham.de.geant.net",
+    "pop": {
+      "abbreviation": "ham",
+      "city": "Hamburg",
+      "country": "Germany",
+      "country code": "DE",
+      "latitude": 53.550902777778,
+      "longitude": 10.046486111111,
+      "name": "Hamburg"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.poz.pl.geant.net",
+    "pop": {
+      "abbreviation": "poz",
+      "city": "Poznan",
+      "country": "Poland",
+      "country code": "PL",
+      "latitude": 52.411775,
+      "longitude": 16.917561111111,
+      "name": "Poznan"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ath2.gr.geant.net",
+    "pop": {
+      "abbreviation": "ath2",
+      "city": "Attiki",
+      "country": "Greece",
+      "country code": "GR",
+      "latitude": 37.98,
+      "longitude": 23.73,
+      "name": "Athens 2"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.kau.lt.geant.net",
+    "pop": {
+      "abbreviation": "kau",
+      "city": "Kaunas",
+      "country": "Lithuania",
+      "country code": "LT",
+      "latitude": 54.940672222222,
+      "longitude": 24.018013888889,
+      "name": "Kaunas"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.bud.hu.geant.net",
+    "pop": {
+      "abbreviation": "bud",
+      "city": "Budapest",
+      "country": "Hungary",
+      "country code": "HU",
+      "latitude": 47.517902777778,
+      "longitude": 19.055436111111,
+      "name": "Budapest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.bru.be.geant.net",
+    "pop": {
+      "abbreviation": "bru",
+      "city": "Brussels",
+      "country": "Belgium",
+      "country code": "BE",
+      "latitude": 50.857002777778,
+      "longitude": 4.4115694444444,
+      "name": "Brussels"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.bra.sk.geant.net",
+    "pop": {
+      "abbreviation": "bra",
+      "city": "Bratislava",
+      "country": "Slovakia",
+      "country code": "SK",
+      "latitude": 48.119027777778,
+      "longitude": 17.0957,
+      "name": "Bratislava"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.kau.lt.geant.net",
+    "pop": {
+      "abbreviation": "kau",
+      "city": "Kaunas",
+      "country": "Lithuania",
+      "country code": "LT",
+      "latitude": 54.940672222222,
+      "longitude": 24.018013888889,
+      "name": "Kaunas"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.cbg.uk",
+    "pop": {
+      "abbreviation": "oc",
+      "city": "CAMBRIDGE",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "Cambridge OC"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.buc.ro.geant.net",
+    "pop": {
+      "abbreviation": "buc",
+      "city": "Bucharest",
+      "country": "Romania",
+      "country code": "RO",
+      "latitude": 44.444741666667,
+      "longitude": 26.096422222222,
+      "name": "Bucharest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.vie.at.geant.net",
+    "pop": {
+      "abbreviation": "vie",
+      "city": "Vienna",
+      "country": "Austria",
+      "country code": "AT",
+      "latitude": 48.268925,
+      "longitude": 16.410194444444,
+      "name": "Vienna"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mil2.it.geant.net",
+    "pop": {
+      "abbreviation": "mil2",
+      "city": "Milan",
+      "country": "Italy",
+      "country code": "IT",
+      "latitude": 45.475522222222,
+      "longitude": 9.1033194444444,
+      "name": "Milan 2 Caldera"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ams.nl.geant.net",
+    "pop": {
+      "abbreviation": "ams",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.356363888889,
+      "longitude": 4.9529555555556,
+      "name": "Amsterdam"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "sw2.am.office.geant.net",
+    "pop": {
+      "abbreviation": "am",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.313305555556,
+      "longitude": 4.9491111111111,
+      "name": "Amsterdam GEANT Office"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.fra.de.geant.net",
+    "pop": {
+      "abbreviation": "fra",
+      "city": "Frankfurt",
+      "country": "Germany",
+      "country code": "DE",
+      "latitude": 50.120294444444,
+      "longitude": 8.7358444444444,
+      "name": "Frankfurt"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lis.pt.geant.net",
+    "pop": {
+      "abbreviation": "lis2",
+      "city": "Lisbon",
+      "country": "Portugal",
+      "country code": "PT",
+      "latitude": 38.759097222222,
+      "longitude": -9.1421944444444,
+      "name": "Lisbon 2"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.lju.si.geant.net",
+    "pop": {
+      "abbreviation": "lju",
+      "city": "Ljubljana",
+      "country": "Slovenia",
+      "country code": "SI",
+      "latitude": 46.042366666667,
+      "longitude": 14.488719444444,
+      "name": "Ljubljana"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.dub.ie.geant.net",
+    "pop": {
+      "abbreviation": "dub",
+      "city": "Dublin",
+      "country": "Ireland",
+      "country code": "IE",
+      "latitude": 53.291980555556,
+      "longitude": -6.4147333333333,
+      "name": "Dublin (City West)"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.lis.pt.geant.net",
+    "pop": {
+      "abbreviation": "lis",
+      "city": "Lisbon",
+      "country": "Portugal",
+      "country code": "PT",
+      "latitude": 38.759097222222,
+      "longitude": -9.1421944444444,
+      "name": "Lisbon"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mad.es.geant.net",
+    "pop": {
+      "abbreviation": "mad",
+      "city": "Alcobendas  - Madrid",
+      "country": "Spain",
+      "country code": "ES",
+      "latitude": 40.536477777778,
+      "longitude": -3.6488027777778,
+      "name": "Madrid"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.tal.ee.geant.net",
+    "pop": {
+      "abbreviation": "tal",
+      "city": "Tallinn",
+      "country": "Estonia",
+      "country code": "EE",
+      "latitude": 59.434825,
+      "longitude": 24.71385,
+      "name": "Tallinn"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.ath.gr.geant.net",
+    "pop": {
+      "abbreviation": "ath",
+      "city": "Athens",
+      "country": "Greece",
+      "country code": "GR",
+      "latitude": 37.973086111111,
+      "longitude": 23.745555555556,
+      "name": "Athens"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx4 - Camb Lab",
+    "pop": {
+      "abbreviation": "lab",
+      "city": "Cambridge",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "DANTE Lab"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.par.fr.geant.net",
+    "pop": {
+      "abbreviation": "par",
+      "city": "Paris",
+      "country": "France",
+      "country code": "FR",
+      "latitude": 48.904630555556,
+      "longitude": 2.3713555555556,
+      "name": "Paris"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.dub2.ie.geant.net",
+    "pop": {
+      "abbreviation": "dub2",
+      "city": "Dublin 12",
+      "country": "Ireland",
+      "country code": "IE",
+      "latitude": 53.33425,
+      "longitude": -6.3655444444444,
+      "name": "Dublin 2 - Parkwest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "sw3.am.office.geant.net",
+    "pop": {
+      "abbreviation": "am",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.313305555556,
+      "longitude": 4.9491111111111,
+      "name": "Amsterdam GEANT Office"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx2.zag.hr.geant.net",
+    "pop": {
+      "abbreviation": "zag",
+      "city": "Zagreb",
+      "country": "Crotia",
+      "country code": "HR",
+      "latitude": 45.792030555556,
+      "longitude": 15.969494444444,
+      "name": "Zagreb"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mar.fr.geant.net",
+    "pop": {
+      "abbreviation": "mar",
+      "city": "Marseille",
+      "country": "France",
+      "country code": "FR",
+      "latitude": 43.311027777778,
+      "longitude": 5.3738888888889,
+      "name": "Marseille"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.gen.ch.geant.net",
+    "pop": {
+      "abbreviation": "gen",
+      "city": "Geneva",
+      "country": "Switzerland",
+      "country code": "CH",
+      "latitude": 46.232194444444,
+      "longitude": 6.0457638888889,
+      "name": "Geneva"
+    },
+    "type": "CORE"
+  }
+]
\ No newline at end of file
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index 5bddf85b14c909a998a8a58bd624eedd5ffafd07..4ff7a719f4e6ea3c6b147748b34a8c8113c8263a 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -10,24 +10,6 @@ configuration:
 security: 
   saml2: 
     metadataURL: "https://login.terena.org/wayf/saml2/idp/metadata"
-server: 
-  port: 8009
-  ssl: 
-    enabled: true
-spring: 
-  datasource: 
-    driverClassName: com.mysql.jdbc.Driver
-    password: opsro
-    url: "jdbc:mysql://test-opsdb01.geant.net:3306/opsdb"
-    username: opsro
-  jpa: 
-    generate-ddl: true
-    hibernate.ddl-auto: update
-    properties: 
-      hibernate: 
-        dialect: org.hibernate.dialect.MySQL5Dialect
-        show-sql: false
-    show-sql: false
 saml:
     metadata-url: https://login.terena.org/wayf/saml2/idp/metadata
     entity-name: https://test-lg.geant.net/saml
@@ -35,3 +17,12 @@ saml:
     key-store: classpath:saml/keystore.jks
     key-alias: spring
     key-store-password: secret
+    key-mappings:
+      id: "id"
+      given-name: "givenName"
+      surname: "surname"
+      email: "mail"
+      groups: "isMemberOf"
+    group-mappings:
+      INTERNAL: "GEANT Staff:All"
+      EXTERNAL: "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"
diff --git a/src/test/resources/commands_external.xml b/src/test/resources/commands_external.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c955898fc7b6c9c0251af4d73723c329f836c7b1
--- /dev/null
+++ b/src/test/resources/commands_external.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="external-group">
+		<command>
+			<name>external-command</name>
+			<value>external-command</value>
+			<description>external-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+</commands>
\ No newline at end of file
diff --git a/src/test/resources/commands_internal.xml b/src/test/resources/commands_internal.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f1ef017e54a71a6ffa5e8766bc4abf51650fc32b
--- /dev/null
+++ b/src/test/resources/commands_internal.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="external-group">
+		<command>
+			<name>external-command</name>
+			<value>external-command</value>
+			<description>external-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="internal-group">
+		<command>
+			<name>internal-command</name>
+			<value>internal-command</value>
+			<description>internal-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+</commands>
\ No newline at end of file
diff --git a/src/test/resources/commands_public.xml b/src/test/resources/commands_public.xml
index 8b785de5ab5590d7731b24c76472c263060f9888..70b1e69507f4ca895604c968a921ccb81ea9d1d9 100644
--- a/src/test/resources/commands_public.xml
+++ b/src/test/resources/commands_public.xml
@@ -1,13 +1,13 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- List of Commands, here name element contains the command name that is 
-	visible on UI and value contains actual command. -->
-<commands>
-	<group name="test-group">
-		<command>
-			<name>test-name</name>
-			<value>test-value</value>
-			<description>test-description</description>
-			<requiresParams>true</requiresParams>
-		</command>
-	</group>
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
 </commands>
\ No newline at end of file