diff --git a/gms-client/gms-cli/src/main/resources/application.properties b/gms-client/gms-cli/src/main/resources/application.properties
deleted file mode 100644
index 2a867fa5f89b8104fe8d51524d699a0d5c9b064f..0000000000000000000000000000000000000000
--- a/gms-client/gms-cli/src/main/resources/application.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-spring.main.banner-mode=off
-logging.level.root=OFF
\ No newline at end of file
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java
index 1a414cd9ffe6bdbac43510168d7babe81e6b3134..70bb477eb621127bacf3f24140c33a757da671c5 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java
@@ -14,7 +14,7 @@ public class AddMemberCall extends BaseCall<GmsClient> {
 
     public boolean addMember(String completeGroupName, String userId) {
 
-        String endpoint = "membership";
+        String endpoint = "ws/jwt/membership";
         if (completeGroupName != null && !completeGroupName.isBlank()) {
             endpoint += "/" + completeGroupName;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java
index 01562e6ae9ec574db0ada3bf4257231c7bca3a96..ace30eccf540a97fb56e84fdee2dca5eea1d21a6 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java
@@ -16,7 +16,7 @@ public class AddPermissionCall extends BaseCall<GmsClient> {
 
     public String addPermission(String completeGroupName, String userId, Permission permission) {
 
-        String endpoint = "permission";
+        String endpoint = "ws/jwt/permission";
         if (completeGroupName != null && !completeGroupName.isBlank()) {
             endpoint += "/" + completeGroupName;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java
index 1d7a6db5d16a6598f9373cba630ea21e399efdab..edb5158ce23c9027e23f0dea538a61f4a39d8026 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java
@@ -14,7 +14,7 @@ public class CreateGroupCall extends BaseCall<GmsClient> {
 
     public boolean createGroup(String completeGroupName, boolean leaf) {
 
-        HttpRequest groupsRequest = client.newRequest(completeGroupName)
+        HttpRequest groupsRequest = client.newRequest("ws/jwt/" + completeGroupName)
                 .header("Accept", "text/plain")
                 .header("Content-Type", "application/x-www-form-urlencoded")
                 .POST(BodyPublishers.ofString("leaf=" + leaf))
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java
index 4b9f0d423d3ffec07886500740c8c8692461b49e..bbde41dc7dfe37f830a3228a13a641b278f5dc23 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java
@@ -13,7 +13,7 @@ public class DeleteGroupCall extends BaseCall<GmsClient> {
 
     public boolean deleteGroup(String completeGroupName) {
 
-        HttpRequest groupsRequest = client.newRequest(completeGroupName)
+        HttpRequest groupsRequest = client.newRequest("ws/jwt/" + completeGroupName)
                 .header("Accept", "text/plain")
                 .DELETE()
                 .build();
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetGroupPermissionsCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetGroupPermissionsCall.java
index d4c6838ad8afd04bb4cfd8f8a3ec2feb213b1bd1..5e782d12c0d473c475e0522626fab8cc312da2da 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetGroupPermissionsCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetGroupPermissionsCall.java
@@ -20,7 +20,7 @@ public class GetGroupPermissionsCall extends BaseCall<GmsClient> {
 
         List<GroupPermission> groupPermissions = new ArrayList<>();
 
-        String endpoint = "permission";
+        String endpoint = "ws/jwt/permission";
         endpoint += "/" + groupId;
 
         HttpRequest groupsRequest = client.newRequest(endpoint)
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java
index a2c49348353bb6ec5962fe462c670b8009f80425..a6a4b66438be803b9c0f4b49f5208f427e21bce4 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java
@@ -22,7 +22,7 @@ public class GetUserGroupsCall extends BaseCall<GmsClient> {
 
         List<String> groups = new ArrayList<>();
 
-        HttpRequest groupsRequest = client.newRequest("search")
+        HttpRequest groupsRequest = client.newRequest("vo/search")
                 .header("Accept", "text/plain")
                 .GET()
                 .build();
@@ -52,7 +52,7 @@ public class GetUserGroupsCall extends BaseCall<GmsClient> {
 
         List<String> groups = new ArrayList<>();
 
-        String endpoint = "membership";
+        String endpoint = "ws/jwt/membership";
         if (prefix != null && !prefix.isBlank()) {
             endpoint += "/" + prefix;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserPermissionsCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserPermissionsCall.java
index 67aa39adc43c7674082512ada555d91246a3cfbc..8f4e9ed7916d1af0a752321a9fb4797a70181b85 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserPermissionsCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/GetUserPermissionsCall.java
@@ -20,7 +20,7 @@ public class GetUserPermissionsCall extends BaseCall<GmsClient> {
 
         List<UserPermission> userPermissions = new ArrayList<>();
 
-        String endpoint = "permission";
+        String endpoint = "ws/jwt/permission";
         endpoint += "?user_id=" + userId;
 
         HttpRequest groupsRequest = client.newRequest(endpoint)
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java
index 4b3411c76a6329df632e4bda00656f87c1d38568..2c3123ef896e211fe76c41fec92224d5030620c6 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java
@@ -23,7 +23,7 @@ public class ListGroupsCall extends BaseCall<GmsClient> {
 
         List<String> groups = new ArrayList<>();
 
-        String uri = "list";
+        String uri = "ws/jwt/list";
         if (prefix != null && !prefix.isBlank()) {
             uri += "/" + prefix;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java
index 546ccfeca73b8cce2ae71990501f920c3ca0a434..7daf15ecd214c59a820b8ad03ed071216b01b385 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java
@@ -13,7 +13,7 @@ public class RemoveMemberCall extends BaseCall<GmsClient> {
 
     public boolean removeMember(String completeGroupName, String userId) {
 
-        String endpoint = "membership";
+        String endpoint = "ws/jwt/membership";
         if (completeGroupName != null && !completeGroupName.isBlank()) {
             endpoint += "/" + completeGroupName;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java
index f950614e7b7a0c0c0f2b8726b18cb4a82b92dab2..3602a551cc25a27f31a971a3876b0e108064eec0 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java
@@ -13,7 +13,7 @@ public class RemovePermissionCall extends BaseCall<GmsClient> {
 
     public boolean removePermission(String completeGroupName, String userId) {
 
-        String endpoint = "permission";
+        String endpoint = "ws/jwt/permission";
         if (completeGroupName != null && !completeGroupName.isBlank()) {
             endpoint += "/" + completeGroupName;
         }
diff --git a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/SetPermissionCall.java b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/SetPermissionCall.java
index 9f62ebe552efbeb8ecf8f1a39e62cbef2d3619da..97ceb21c0b40fd67ab698ae1bff321a4a060f790 100644
--- a/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/SetPermissionCall.java
+++ b/gms-client/gms-client/src/main/java/it/inaf/ia2/gms/client/call/SetPermissionCall.java
@@ -14,7 +14,7 @@ public class SetPermissionCall extends BaseCall<GmsClient> {
 
     public String setPermission(String completeGroupName, String userId, Permission permission) {
 
-        String endpoint = "permission";
+        String endpoint = "ws/jwt/permission";
         if (completeGroupName != null && !completeGroupName.isBlank()) {
             endpoint += "/" + completeGroupName;
         }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddMemberTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddMemberTest.java
index 250eec2e5a5f8e4c39d6dc1f5aebc40c5d7ef9bf..268b2f0f957e5908584de13e9b437984b377a8ee 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddMemberTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddMemberTest.java
@@ -28,6 +28,6 @@ public class AddMemberTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.addMember("LBT.INAF", "user");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "membership/LBT.INAF"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "ws/jwt/membership/LBT.INAF"), any());
     }
 }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddPermissionTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddPermissionTest.java
index 04131a84c207cf30cc39245f639bf40c74d2dd41..4c68372d3e8ebdb3e4c62c2ba791145f3556c450 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddPermissionTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/AddPermissionTest.java
@@ -29,6 +29,6 @@ public class AddPermissionTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.addPermission("LBT.INAF", "user", Permission.ADMIN);
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "permission/LBT.INAF"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "ws/jwt/permission/LBT.INAF"), any());
     }
 }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/CreateGroupTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/CreateGroupTest.java
index dfd3fd0add2552aec3b1a03c4d83898ab3998a35..8c62a3fc3e52fe29fb60a77ddb16f243161e0548 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/CreateGroupTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/CreateGroupTest.java
@@ -28,6 +28,6 @@ public class CreateGroupTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.createGroup("LBT.INAF", false);
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "LBT.INAF"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("POST", "ws/jwt/LBT.INAF"), any());
     }
 }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/DeleteGroupTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/DeleteGroupTest.java
index dd87b4ea61aa5551ea65d9df0b2d02c3622db748..e5b1161ba230b8c11170ec0b9eb16a4e9cecdb0b 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/DeleteGroupTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/DeleteGroupTest.java
@@ -28,6 +28,6 @@ public class DeleteGroupTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.deleteGroup("LBT.INAF");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "LBT.INAF"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/LBT.INAF"), any());
     }
 }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/GetUserGroupsTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/GetUserGroupsTest.java
index 7741eb4a97dd7811954aec414e60418f3ee849e5..3b2dee689ffc556752cb0af3027803482f97d130 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/GetUserGroupsTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/GetUserGroupsTest.java
@@ -34,7 +34,7 @@ public class GetUserGroupsTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         List<String> groups = gmsClient.getMyGroups("LBT.");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("GET", "search"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("GET", "vo/search"), any());
 
         assertEquals(2, groups.size());
         assertEquals("INAF", groups.get(0));
@@ -52,7 +52,7 @@ public class GetUserGroupsTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         List<String> groups = gmsClient.listGroups("LBT.");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("GET", "list/LBT."), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("GET", "ws/jwt/list/LBT."), any());
 
         assertEquals(2, groups.size());
         assertEquals("INAF", groups.get(0));
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemoveMemberTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemoveMemberTest.java
index 298909eb5d1f485160fd8cf163876511b25fd774..845dbb21087a48ddc68d35a36dfafa95fd18873b 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemoveMemberTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemoveMemberTest.java
@@ -28,6 +28,6 @@ public class RemoveMemberTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.removeMember("LBT.INAF", "user");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "membership/LBT.INAF?user_id=user"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/membership/LBT.INAF?user_id=user"), any());
     }
 }
diff --git a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemovePermissionTest.java b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemovePermissionTest.java
index d48577c9e1af8b6b9265b07e50e4bd4ebfb82b88..c1a86b0c51085af59139c3f492ad6611c1a5868d 100644
--- a/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemovePermissionTest.java
+++ b/gms-client/gms-client/src/test/java/it/inaf/ia2/gms/client/call/RemovePermissionTest.java
@@ -28,6 +28,6 @@ public class RemovePermissionTest extends BaseGmsClientTest {
         when(httpClient.sendAsync(any(), any())).thenReturn(response);
         gmsClient.removePermission("LBT.INAF", "user");
 
-        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "permission/LBT.INAF?user_id=user"), any());
+        verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/permission/LBT.INAF?user_id=user"), any());
     }
 }
diff --git a/gms/pom.xml b/gms/pom.xml
index 2f599fbbdf941399174822a076d7d14129206bcf..011a3eb4433a5152635ee5cf1be5f21a2d357e1c 100644
--- a/gms/pom.xml
+++ b/gms/pom.xml
@@ -68,36 +68,46 @@
         </dependency>
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>build-gui</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.github.eirslett</groupId>
+                        <artifactId>frontend-maven-plugin</artifactId>
+                        <version>1.7.6</version>
+                        <configuration>
+                            <nodeVersion>v12.6.0</nodeVersion>
+                            <environmentVariables>
+                                <VUE_APP_SHOW_USER_ID_IN_SEARCH>${show.user_id_in_search}</VUE_APP_SHOW_USER_ID_IN_SEARCH>
+                            </environmentVariables>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>install-node-and-npm</goal>
+                                </goals>
+                            </execution>
+                            <execution>
+                                <id>npm install</id>
+                                <goals>
+                                    <goal>npm</goal>
+                                </goals>
+                                <configuration>
+                                    <arguments>run build --prefix ../gms-ui</arguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
     <build>
         <finalName>gms</finalName>
         <plugins>
-            <plugin>
-                <groupId>com.github.eirslett</groupId>
-                <artifactId>frontend-maven-plugin</artifactId>
-                <version>1.7.6</version>
-                <configuration>
-                    <nodeVersion>v12.6.0</nodeVersion>
-                    <environmentVariables>
-                        <VUE_APP_SHOW_USER_ID_IN_SEARCH>${show.user_id_in_search}</VUE_APP_SHOW_USER_ID_IN_SEARCH>
-                    </environmentVariables>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>install-node-and-npm</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>npm install</id>
-                        <goals>
-                            <goal>npm</goal>
-                        </goals>
-                        <configuration>
-                            <arguments>run build --prefix ../gms-ui</arguments>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
             <plugin>
                 <artifactId>maven-resources-plugin</artifactId>
                 <version>3.1.0</version>
diff --git a/gms/src/main/java/it/inaf/ia2/gms/GmsApplication.java b/gms/src/main/java/it/inaf/ia2/gms/GmsApplication.java
index f3f4a5bcab393988d1e053c5e80897e8a73632be..69bcf3d5e839759bc0a4dc65976cec26fb04db62 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/GmsApplication.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/GmsApplication.java
@@ -16,7 +16,7 @@ public class GmsApplication {
     public static void main(String[] args) {
         SpringApplication.run(GmsApplication.class, args);
     }
-    
+
     @Bean
     public RapClient rapClient() {
         return ServiceLocator.getInstance().getRapClient();
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
index a2a081a693173367e466b070a88378e43943602b..af5000bcdba79cff1d09dfcf334378e0d3277956 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
@@ -26,6 +26,11 @@ public class GmsLoginFilter extends LoginFilter {
 
     private boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
 
+        if (request.getUserPrincipal() != null) {
+            // Principal set using JWT
+            return true;
+        }
+
         // Allow CORS check
         if ("OPTIONS".equals(request.getMethod())) {
             return true;
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/JWTFilter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/JWTFilter.java
index 6273aed731e85b1c44a8ed7cc4e9deb0c0fb4cb0..f2c047c6cfdd7ae965443cc2c1768f21b9d55752 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/JWTFilter.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/JWTFilter.java
@@ -1,5 +1,6 @@
 package it.inaf.ia2.gms.authn;
 
+import it.inaf.ia2.aa.data.User;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.rap.client.RapClient;
 import java.io.IOException;
@@ -13,6 +14,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 public class JWTFilter implements Filter {
 
@@ -31,9 +33,21 @@ public class JWTFilter implements Filter {
         HttpServletResponse response = (HttpServletResponse) res;
 
         String authHeader = request.getHeader("Authorization");
+
         if (authHeader == null) {
-            loggingDAO.logAction("Attempt to access WS without token", request);
-            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing Authorization token");
+
+            if (request.isRequestedSessionIdValid()) {
+                HttpSession session = request.getSession(false);
+                User user = (User) session.getAttribute("user_data");
+                if (user != null) {
+                    rapClient.setAccessToken(user.getAccessToken());
+                    ServletRequestWithSessionPrincipal wrappedRequest = new ServletRequestWithSessionPrincipal(request, user);
+                    fc.doFilter(wrappedRequest, res);
+                    return;
+                }
+            }
+
+            fc.doFilter(req, res);
             return;
         }
 
@@ -54,9 +68,24 @@ public class JWTFilter implements Filter {
         fc.doFilter(wrappedRequest, res);
     }
 
+    private static class ServletRequestWithSessionPrincipal extends HttpServletRequestWrapper {
+
+        private final User principal;
+
+        public ServletRequestWithSessionPrincipal(HttpServletRequest request, User user) {
+            super(request);
+            this.principal = user;
+        }
+
+        @Override
+        public Principal getUserPrincipal() {
+            return principal;
+        }
+    }
+
     private static class ServletRequestWithJWTPrincipal extends HttpServletRequestWrapper {
 
-        private final Principal principal;
+        private final RapPrincipal principal;
 
         public ServletRequestWithJWTPrincipal(HttpServletRequest request, Map<String, Object> jwtClaims) {
             super(request);
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
index 57df36edd76cbcaf38fb4711bf4cc4b3f2f08c33..b2aa5b75b3a1735abe6c78e7587a91d3eae57658 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
@@ -51,7 +51,7 @@ public class SecurityConfig {
     public FilterRegistrationBean serviceJWTFilter(LoggingDAO loggingDAO, RapClient rapClient) {
         FilterRegistrationBean bean = new FilterRegistrationBean();
         bean.setFilter(new JWTFilter(loggingDAO, rapClient));
-        bean.addUrlPatterns("/ws/jwt/*");
+        bean.addUrlPatterns("/*");
         bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
         return bean;
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
index fe9137ed62cca9a1d3337718d95fdb967923a464..4bef932ad8648333180f7c8bf2b4a6ada9078879 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
@@ -1,7 +1,6 @@
 package it.inaf.ia2.gms.controller;
 
 import com.opencsv.CSVWriter;
-import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.manager.GroupStatusManager;
 import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.model.request.AddGroupRequest;
@@ -16,6 +15,7 @@ import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,7 +34,7 @@ import org.springframework.web.bind.annotation.RestController;
 public class GroupsController {
 
     @Autowired
-    private SessionData session;
+    private HttpServletRequest servletRequest;
 
     @Autowired
     private GroupsManager groupsManager;
@@ -96,8 +96,7 @@ public class GroupsController {
     @GetMapping(value = "/group/status/{groupId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
     public void downloadStatus(@PathVariable("groupId") String groupId, HttpServletResponse response) throws Exception {
 
-        try (OutputStream out = response.getOutputStream();
-                CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
+        try ( OutputStream out = response.getOutputStream();  CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
 
             writer.writeNext(new String[]{"program", "email"});
 
@@ -108,6 +107,6 @@ public class GroupsController {
     }
 
     private <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> getGroupsPanel(GroupEntity parentGroup, T request) {
-        return groupsTreeBuilder.listSubGroups(parentGroup, request, session.getUserId());
+        return groupsTreeBuilder.listSubGroups(parentGroup, request, servletRequest.getUserPrincipal().getName());
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
index 2718b19cd8d586c6fd8ca882b31466a4538f29bb..2da6216a9c827234cdcf4f7e068cda6c49fbd5c7 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
@@ -1,6 +1,5 @@
 package it.inaf.ia2.gms.controller;
 
-import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
@@ -10,6 +9,7 @@ import it.inaf.ia2.gms.model.response.GroupsTabResponse;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
+import javax.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
 public class GroupsTabResponseBuilder {
 
     @Autowired
-    private SessionData session;
+    HttpServletRequest servletRequest;
 
     @Autowired
     private PermissionsManager permissionsManager;
@@ -46,7 +46,7 @@ public class GroupsTabResponseBuilder {
         Permission permission = permissionsManager.getCurrentUserPermission(group);
         response.setPermission(permission);
 
-        response.setGroupsPanel(groupsListBuilder.listSubGroups(group, request, session.getUserId()));
+        response.setGroupsPanel(groupsListBuilder.listSubGroups(group, request, servletRequest.getUserPrincipal().getName()));
 
         response.setLeaf(group.isLeaf());
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
index 22ce42638c8767c29c72f1ee8e4147e1652ca164..554d4437ef63752fda45efd5468b6cf01dc0b355 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
@@ -39,15 +39,16 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 /**
- * Web service called by other web applications using JWT (delegation).
+ * This class needs some refactoring: it contains all endpoints that used JWT.
+ * Now all endpoints accept both a JWT token or a session, so some of them could
+ * be removed and others should be moved on dedicated classes. Some endpoints
+ * match 2 patters to achieve a smooth transition.
  */
 @RestController
-@RequestMapping("/ws/jwt")
 public class JWTWebServiceController {
 
     @Autowired
@@ -63,7 +64,7 @@ public class JWTWebServiceController {
     private GroupsService groupsService;
 
     @Autowired
-    private GroupNameService groupNameService;
+    protected GroupNameService groupNameService;
 
     @Autowired
     private MembershipManager membershipManager;
@@ -83,7 +84,7 @@ public class JWTWebServiceController {
     /**
      * This endpoint is compliant with the IVOA GMS standard.
      */
-    @GetMapping(value = "/search", produces = MediaType.TEXT_PLAIN_VALUE)
+    @GetMapping(value = {"/ws/jwt/search", "/vo/search"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void getGroups(HttpServletResponse response) throws IOException {
 
         List<GroupEntity> memberships = membershipManager.getCurrentUserMemberships();
@@ -104,10 +105,10 @@ public class JWTWebServiceController {
      * be defined adding ".+", otherwise Spring will think it is a file
      * extension (thanks https://stackoverflow.com/a/16333149/771431)
      */
-    @GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
+    @GetMapping(value = {"/ws/jwt/search/{group:.+}", "/vo/search/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void isMemberOf(@PathVariable("group") String group, HttpServletResponse response) throws IOException {
 
-        List<String> groupNames = extractGroupNames(group);
+        List<String> groupNames = groupNameService.extractGroupNames(group);
 
         boolean isMember = membershipManager.isCurrentUserMemberOf("ROOT");
         if (!isMember) {
@@ -135,13 +136,12 @@ public class JWTWebServiceController {
         // else: empty response (as defined by GMS standard)
     }
 
-    @GetMapping(value = {"/list/{group:.+}", "/list"}, produces = MediaType.TEXT_PLAIN_VALUE)
-    public void listGroups(@PathVariable("group") Optional<String> group, Principal principal, HttpServletResponse response) throws IOException {
+    @GetMapping(value = {"/ws/jwt/list/{group:.+}", "/ws/jwt/list"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    public void listGroups(@PathVariable("group") Optional<String> groupNames, Principal principal, HttpServletResponse response) throws IOException {
 
         String userId = principal.getName();
 
-        List<String> groupNames = extractGroupNames(group);
-        GroupEntity parentGroup = getGroupFromNames(groupNames);
+        GroupEntity parentGroup = groupNameService.getGroupFromNames(groupNames);
 
         List<GroupEntity> allSubGroups = groupsDAO.getDirectSubGroups(parentGroup.getPath());
 
@@ -157,7 +157,7 @@ public class JWTWebServiceController {
 
         try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
             for (String groupName : groupNameService.getGroupsNames(visibleSubgroups)) {
-                pw.println(getShortGroupName(groupName, group));
+                pw.println(groupNameService.getShortGroupName(groupName, groupNames));
             }
         }
     }
@@ -166,10 +166,10 @@ public class JWTWebServiceController {
      * Creates a group and its ancestors if they are missing. It doesn't fail if
      * the last group already exists.
      */
-    @PostMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
+    @PostMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
     public void createGroup(@PathVariable("group") String groupParam, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        List<String> groupNames = extractGroupNames(groupParam);
+        List<String> groupNames = groupNameService.extractGroupNames(groupParam);
 
         String leafParam = request.getParameter("leaf");
         boolean leaf = leafParam == null ? false : Boolean.valueOf(leafParam);
@@ -191,29 +191,29 @@ public class JWTWebServiceController {
         }
     }
 
-    @DeleteMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
+    @DeleteMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
     public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) {
-        GroupEntity group = getGroupFromNames(extractGroupNames(groupParam));
+        GroupEntity group = groupNameService.getGroupFromNames(Optional.of(groupParam));
         groupsDAO.deleteGroup(group);
         response.setStatus(HttpServletResponse.SC_NO_CONTENT);
     }
 
-    @GetMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
-    public void getMembership(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
+    @GetMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    public void getMembership(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
 
-        GroupEntity parent = getGroupFromNames(extractGroupNames(group));
+        GroupEntity parent = groupNameService.getGroupFromNames(groupNames);
 
         List<GroupEntity> groups = membershipManager.getUserGroups(parent, userId);
 
         try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
             for (String groupName : groupNameService.getGroupsNames(groups)) {
-                pw.println(getShortGroupName(groupName, group));
+                pw.println(groupNameService.getShortGroupName(groupName, groupNames));
             }
         }
     }
 
-    @PostMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
-    public void addMember(@PathVariable("group") Optional<String> group, HttpServletRequest request, HttpServletResponse response) throws IOException {
+    @PostMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    public void addMember(@PathVariable("group") Optional<String> groupNames, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
         String targetUserId = request.getParameter("user_id");
         if (targetUserId == null) {
@@ -221,25 +221,25 @@ public class JWTWebServiceController {
             return;
         }
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
 
         membershipManager.addMember(groupEntity, targetUserId);
     }
 
-    @DeleteMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
-    public void removeMember(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId,
+    @DeleteMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    public void removeMember(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
             HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
         membershipManager.removeMember(groupEntity, userId);
 
         response.setStatus(HttpServletResponse.SC_NO_CONTENT);
     }
 
-    @GetMapping(value = {"/permission/{group:.+}", "/permission"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    @GetMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void getUserPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") Optional<String> userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
         if (userId.isPresent()) {
             try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
                 for (UserPermission userPermission : searchService.getUserPermission(groupEntity, userId.get(), permissionsManager.getCurrentUserPermissions(groupEntity))) {
@@ -249,36 +249,36 @@ public class JWTWebServiceController {
             }
         } else {
             try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
-                for (it.inaf.ia2.gms.model.UserPermission up : permissionsManager.getAllPermissions(groupEntity)) {
+                for (it.inaf.ia2.gms.model.RapUserPermission up : permissionsManager.getAllPermissions(groupEntity)) {
                     pw.println(up.getUser().getId() + " " + up.getPermission());
                 }
             }
         }
     }
 
-    @PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+    @PostMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
     public void addPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
         permissionsManager.addPermission(groupEntity, targetUserId, permission);
     }
 
-    @PutMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+    @PutMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
     public void setPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
         permissionsManager.createOrUpdatePermission(groupEntity, targetUserId, permission);
     }
 
-    @DeleteMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    @DeleteMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void removePermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
             HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
         permissionsManager.removePermission(groupEntity, userId);
 
         response.setStatus(HttpServletResponse.SC_NO_CONTENT);
     }
 
-    @GetMapping(value = "/check-invited-registration", produces = MediaType.TEXT_PLAIN_VALUE)
+    @GetMapping(value = {"/ws/jwt/check-invited-registration", "/check-invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void completeInvitedRegistrationIfNecessary(Principal principal, HttpServletResponse response) throws IOException {
 
         String userId = principal.getName();
@@ -300,7 +300,7 @@ public class JWTWebServiceController {
         }
     }
 
-    @PostMapping(value = "/invited-registration", produces = MediaType.TEXT_PLAIN_VALUE)
+    @PostMapping(value = {"/ws/jwt/invited-registration", "/invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void addInvitedRegistration(@RequestParam("token_hash") String tokenHash, @RequestParam("email") String email,
             @RequestParam("groups") String groupNamesAndPermissionsParam, HttpServletResponse response) {
 
@@ -311,7 +311,7 @@ public class JWTWebServiceController {
                 int lastSpaceIndex = param.lastIndexOf(" ");
                 String groupName = param.substring(0, lastSpaceIndex);
                 Permission permission = Permission.valueOf(param.substring(lastSpaceIndex + 1));
-                GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupName));
+                GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupName));
                 groupsPermissions.put(groupEntity, permission);
             }
         }
@@ -321,10 +321,10 @@ public class JWTWebServiceController {
         response.setStatus(HttpServletResponse.SC_CREATED);
     }
 
-    @GetMapping(value = "/email/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
+    @GetMapping(value = {"/ws/jwt/email/{group:.+}", "/email/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void getEmailOfMembers(@PathVariable("group") String groupNames, @RequestParam("permission") Optional<Permission> permission, HttpServletResponse response) throws IOException {
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupNames));
 
         Set<String> selectedUserIds = null;
         if (permission.isPresent()) {
@@ -346,68 +346,7 @@ public class JWTWebServiceController {
         }
     }
 
-    private GroupEntity getGroupFromNames(List<String> groupNames) {
-        if (groupNames.isEmpty()) {
-            return getRoot();
-        }
-        return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1);
-    }
-
-    private GroupEntity getGroupFromNamesAndIndex(List<String> groupNames, int index) {
-        String parentPath = ""; // starting from ROOT
-        GroupEntity group = null;
-        for (int i = 0; i < index + 1; i++) {
-            String groupName = groupNames.get(i);
-            group = groupsDAO.findGroupByParentAndName(parentPath, groupName)
-                    .orElseThrow(() -> new BadRequestException("Unable to find group " + groupName));
-            parentPath = group.getPath();
-        }
-        if (group == null) {
-            throw new IllegalStateException();
-        }
-        return group;
-    }
-
-    private GroupEntity getRoot() {
-        return groupsDAO.findGroupById("ROOT")
-                .orElseThrow(() -> new IllegalStateException("Missing root group"));
-    }
-
-    private List<String> extractGroupNames(Optional<String> group) {
-        return extractGroupNames(group.orElse(null));
-    }
-
-    private List<String> extractGroupNames(String groupStr) {
-
-        if (groupStr == null || groupStr.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        List<String> names = new ArrayList<>();
-        String currentName = "";
-        for (int i = 0; i < groupStr.length(); i++) {
-            char c = groupStr.charAt(i);
-            // dot is the group separator and it must be escaped if used inside
-            // group names
-            if (c == '.' && groupStr.charAt(i - 1) != '\\') {
-                names.add(currentName.replace("\\.", "."));
-                currentName = "";
-            } else {
-                currentName += c;
-            }
-        }
-        names.add(currentName);
-        return names;
-    }
-
-    private String getShortGroupName(String completeGroupName, Optional<String> groupPrefix) {
-        if (groupPrefix.isPresent()) {
-            return completeGroupName.substring(groupPrefix.get().length() + 1);
-        }
-        return completeGroupName;
-    }
-
-    @PostMapping(value = "/join", produces = MediaType.APPLICATION_JSON_VALUE)
+    @PostMapping(value = {"/ws/jwt/join", "/join"}, produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<?> join(RapPrincipal principal) {
 
         String fromUser = principal.getName();
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
index 9742396d930bb9bc279952e7c10f2661a78edbc0..84e3faf3a5063af96e787b1f33ecdacf13854066 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
@@ -1,5 +1,6 @@
 package it.inaf.ia2.gms.controller;
 
+import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.request.AddPermissionRequest;
@@ -7,15 +8,20 @@ import it.inaf.ia2.gms.model.request.MemberRequest;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.request.PaginatedModelRequest;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.model.RapUserPermission;
 import it.inaf.ia2.gms.model.request.TabRequest;
 import it.inaf.ia2.gms.model.request.UpdatePermissionRequest;
+import it.inaf.ia2.gms.model.response.UserPermission;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.service.GroupNameService;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -39,10 +45,10 @@ public class PermissionsController {
     private PermissionsManager permissionsManager;
 
     @GetMapping(value = "/permissions", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<PaginatedData<UserPermission>> getPermissionsTab(TabRequest request) {
+    public ResponseEntity<PaginatedData<RapUserPermission>> getPermissionsTab(TabRequest request) {
 
         GroupEntity group = groupsManager.getGroupById(request.getGroupId());
-        PaginatedData<UserPermission> permissionsPanel = getPermissionsPanel(group, request);
+        PaginatedData<RapUserPermission> permissionsPanel = getPermissionsPanel(group, request);
 
         return ResponseEntity.ok(permissionsPanel);
     }
@@ -63,7 +69,7 @@ public class PermissionsController {
     }
 
     @PostMapping(value = "/permission", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<PaginatedData<UserPermission>> addPermission(@Valid @RequestBody AddPermissionRequest request) {
+    public ResponseEntity<PaginatedData<RapUserPermission>> addPermission(@Valid @RequestBody AddPermissionRequest request) {
 
         GroupEntity group = groupsManager.getGroupById(request.getGroupId());
         if (request.isOverride()) {
@@ -88,7 +94,7 @@ public class PermissionsController {
     }
 
     @DeleteMapping(value = "/permission", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<PaginatedData<UserPermission>> deletePermission(@Valid MemberRequest request) {
+    public ResponseEntity<PaginatedData<RapUserPermission>> deletePermission(@Valid MemberRequest request) {
 
         GroupEntity group = groupsManager.getGroupById(request.getGroupId());
         permissionsManager.removePermission(group, request.getUserId());
@@ -96,8 +102,8 @@ public class PermissionsController {
         return ResponseEntity.ok(getPermissionsPanel(group, request));
     }
 
-    private PaginatedData<UserPermission> getPermissionsPanel(GroupEntity group, PaginatedModelRequest request) {
-        List<UserPermission> permissions = permissionsManager.getAllPermissions(group);
+    private PaginatedData<RapUserPermission> getPermissionsPanel(GroupEntity group, PaginatedModelRequest request) {
+        List<RapUserPermission> permissions = permissionsManager.getAllPermissions(group);
         Collections.sort(permissions, (p1, p2) -> {
             return p1.getUser().getDisplayName().compareTo(p2.getUser().getDisplayName());
         });
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/SearchController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/SearchController.java
index 46ec4842a75fd7d3035016dfe9ba3cfd22b57096..a7fcc04bd49d13e89763ad95920004261b1d32b6 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/SearchController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/SearchController.java
@@ -1,10 +1,10 @@
 package it.inaf.ia2.gms.controller;
 
-import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.response.SearchResponseItem;
 import it.inaf.ia2.gms.model.response.UserSearchResponse;
 import it.inaf.ia2.gms.service.SearchService;
+import javax.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
 public class SearchController {
 
     @Autowired
-    private SessionData sessionData;
+    private HttpServletRequest servletRequest;
 
     @Autowired
     private SearchService searchService;
@@ -26,14 +26,14 @@ public class SearchController {
     public ResponseEntity<PaginatedData<SearchResponseItem>> getSearchResults(@RequestParam("query") String query,
             @RequestParam("page") int page, @RequestParam("pageSize") int pageSize) {
 
-        PaginatedData<SearchResponseItem> response = searchService.search(query, sessionData.getUserId(), page, pageSize);
+        PaginatedData<SearchResponseItem> response = searchService.search(query, servletRequest.getUserPrincipal().getName(), page, pageSize);
         return ResponseEntity.ok(response);
     }
 
     @GetMapping(value = "/search/user/{userId}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<UserSearchResponse> getSearchResultUser(@PathVariable("userId") String userId) {
 
-        UserSearchResponse response = searchService.getUserSearchResult(sessionData.getUserId(), userId);
+        UserSearchResponse response = searchService.getUserSearchResult(servletRequest.getUserPrincipal().getName(), userId);
         return ResponseEntity.ok(response);
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
index 82dace994e99bd7ca7a5a0bf76e717ff7575a00d..7c9525d7e3ed319fb3c996df5e1c23ddc466369a 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -54,7 +55,7 @@ public class InvitedRegistrationManager extends UserAwareComponent {
     private RapClient rapClient;
 
     @Autowired
-    private SessionData sessionData;
+    private HttpServletRequest servletRequest;
 
     @Autowired
     private LoggingDAO loggingDAO;
@@ -104,7 +105,7 @@ public class InvitedRegistrationManager extends UserAwareComponent {
 
     public Optional<List<InvitedRegistration>> completeInvitedRegistrationIfNecessary() {
 
-        List<InvitedRegistration> invitedRegistrations = completeInvitedRegistrationIfNecessary(sessionData.getUserId());
+        List<InvitedRegistration> invitedRegistrations = completeInvitedRegistrationIfNecessary(servletRequest.getUserPrincipal().getName());
 
         InvitedRegistration invitedRegistrationFromToken = (InvitedRegistration) httpSession.getAttribute(INVITED_REGISTRATION);
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java
index 4fd1bdfef3ba6afafcbc3a97b69dd7ba4db18154..00a254533c3198b68fa9773ed5dc48218b9aba91 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java
@@ -2,7 +2,7 @@ package it.inaf.ia2.gms.manager;
 
 import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.model.RapUserPermission;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
@@ -34,7 +34,7 @@ public class PermissionsManager extends UserAwareComponent {
         this.loggingDAO = loggingDAO;
     }
 
-    public List<UserPermission> getAllPermissions(GroupEntity group) {
+    public List<RapUserPermission> getAllPermissions(GroupEntity group) {
 
         verifyUserCanManagePermissions(group);
 
@@ -47,12 +47,12 @@ public class PermissionsManager extends UserAwareComponent {
         Map<String, RapUser> users = rapClient.getUsers(userIdentifiers).stream()
                 .collect(Collectors.toMap(RapUser::getId, Function.identity()));
 
-        List<UserPermission> result = new ArrayList<>();
+        List<RapUserPermission> result = new ArrayList<>();
 
         for (PermissionEntity p : permissions) {
             RapUser rapUser = users.get(p.getUserId());
             if (rapUser != null) {
-                UserPermission permission = new UserPermission();
+                RapUserPermission permission = new RapUserPermission();
                 permission.setPermission(p.getPermission());
                 permission.setUser(rapUser);
                 result.add(permission);
@@ -117,6 +117,11 @@ public class PermissionsManager extends UserAwareComponent {
         }
     }
 
+    public List<PermissionEntity> findUserPermissions(GroupEntity group, String userId) {
+        verifyUserCanManagePermissions(group);
+        return permissionsService.findUserPermissions(group, userId);
+    }
+
     public Permission getUserPermission(GroupEntity group, String userId) {
         return getUserPermission(group, userId, true);
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/UserAwareComponent.java b/gms/src/main/java/it/inaf/ia2/gms/manager/UserAwareComponent.java
index f6af17ca33b29f359814e53043a3aa3eceb938d0..ad7dece5ef1060520d47968c7158f908b1afab76 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/manager/UserAwareComponent.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/UserAwareComponent.java
@@ -1,23 +1,14 @@
 package it.inaf.ia2.gms.manager;
 
-import it.inaf.ia2.gms.authn.RapPrincipal;
-import it.inaf.ia2.gms.authn.SessionData;
 import javax.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 
 public abstract class UserAwareComponent {
 
-    @Autowired(required = false)
-    private SessionData session;
-
     @Autowired
     private HttpServletRequest request;
 
     protected String getCurrentUserId() {
-        if (request.getUserPrincipal() != null && request.getUserPrincipal() instanceof RapPrincipal) {
-            return request.getUserPrincipal().getName();
-        } else {
-            return session.getUserId();
-        }
+        return request.getUserPrincipal().getName();
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupPermission.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupPermission.java
new file mode 100644
index 0000000000000000000000000000000000000000..0846da98be18559f67eb2a676a8b327e846dc8a5
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupPermission.java
@@ -0,0 +1,16 @@
+package it.inaf.ia2.gms.model;
+
+import it.inaf.ia2.gms.model.response.UserGroup;
+
+public class GroupPermission extends UserGroup {
+
+    private Permission permission;
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public void setPermission(Permission permission) {
+        this.permission = permission;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/RapUserPermission.java b/gms/src/main/java/it/inaf/ia2/gms/model/RapUserPermission.java
new file mode 100644
index 0000000000000000000000000000000000000000..8417970e0bb3cb650b68d408e0132764e92d5c01
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/RapUserPermission.java
@@ -0,0 +1,25 @@
+package it.inaf.ia2.gms.model;
+
+import it.inaf.ia2.rap.data.RapUser;
+
+public class RapUserPermission {
+
+    private RapUser user;
+    private Permission permission;
+
+    public RapUser getUser() {
+        return user;
+    }
+
+    public void setUser(RapUser user) {
+        this.user = user;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public void setPermission(Permission permission) {
+        this.permission = permission;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java
index 43a9e0abe60bf617736b904a8337b4c9b07f0f68..38b95068b9189546822c6e474af0407c6babec84 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java
@@ -1,18 +1,16 @@
 package it.inaf.ia2.gms.model;
 
-import it.inaf.ia2.rap.data.RapUser;
-
 public class UserPermission {
 
-    private RapUser user;
+    private String userId;
     private Permission permission;
 
-    public RapUser getUser() {
-        return user;
+    public String getUserId() {
+        return userId;
     }
 
-    public void setUser(RapUser user) {
-        this.user = user;
+    public void setUserId(String userId) {
+        this.userId = userId;
     }
 
     public Permission getPermission() {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
index 3cbffa59790a17845926da0690266a4bf1e93048..abe3041c36bcd623a5c579ea29d16699d3d9838d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -8,6 +8,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -159,6 +160,36 @@ public class GroupsDAO {
         });
     }
 
+    /**
+     * @param groupIds
+     * @return map having group id as keys and group complete name as values
+     */
+    public Map<String, String> getGroupCompleteNamesFromId(Set<String> groupIds) {
+
+        Map<String, String> result = new HashMap<>();
+
+        if (groupIds.isEmpty()) {
+            return result;
+        }
+
+        String sql = "SELECT id, complete_name FROM group_complete_name WHERE id IN ("
+                + String.join(",", Collections.nCopies(groupIds.size(), "?")) + ")";
+
+        jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            int i = 0;
+            for (String groupId : groupIds) {
+                ps.setString(++i, groupId);
+            }
+            return ps;
+        }, (rs, index) -> {
+            result.put(rs.getString("id"), rs.getString("complete_name"));
+            return null;
+        });
+
+        return result;
+    }
+
     public Optional<GroupEntity> findGroupByParentAndName(String parentPath, String childName) {
 
         String sql = "SELECT id, path, is_leaf, locked from gms_group WHERE name = ? AND path ~ ?";
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
index cf7cf8d53fb68ad3d662d3cd51f1331528a53676..dc26fd488dede187917961d2a3ebad10d73551b0 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
@@ -1,7 +1,5 @@
 package it.inaf.ia2.gms.persistence;
 
-import it.inaf.ia2.gms.authn.RapPrincipal;
-import it.inaf.ia2.gms.authn.SessionData;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.sql.PreparedStatement;
@@ -9,7 +7,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.sql.DataSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.BeanCreationException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Component;
@@ -24,9 +21,6 @@ public class LoggingDAO {
     @Autowired(required = false)
     private HttpServletRequest request;
 
-    @Autowired(required = false)
-    private SessionData sessionData;
-
     @Autowired
     public LoggingDAO(DataSource dataSource) {
         jdbcTemplate = new JdbcTemplate(dataSource);
@@ -89,13 +83,8 @@ public class LoggingDAO {
     }
 
     private String getUser(HttpServletRequest request) {
-        if (request.getUserPrincipal() != null && request.getUserPrincipal() instanceof RapPrincipal) {
+        if (request != null && request.getUserPrincipal() != null) {
             return request.getUserPrincipal().getName();
-        } else if (request.getSession(false) != null) {
-            try {
-                return sessionData.getUserId();
-            } catch (BeanCreationException ex) {
-            }
         }
         return null;
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
index 48bc56e4ce55d914c0d7d76eb0874384c3bf5770..7191496a4b8903e0731e598fd3f0cf92a27f965f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
@@ -81,6 +81,10 @@ public class PermissionsDAO {
         });
     }
 
+    /**
+     * Finds all direct user permissions for a given parent path (returns also
+     * all sub-groups permissions).
+     */
     public List<PermissionEntity> findUserPermissions(String userId, String path) {
 
         String sql = "SELECT group_id, permission, group_path FROM gms_permission WHERE user_id = ?\n"
diff --git a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java
deleted file mode 100644
index 189472a49d8ea269b640e20894f3f7850d199f2e..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package it.inaf.ia2.gms.rap;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import it.inaf.ia2.gms.authn.SessionData;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import javax.servlet.http.HttpServletRequest;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.HttpClientErrorException;
-import org.springframework.web.client.HttpServerErrorException;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-@Component
-public class RapClient {
-
-    @Value("${rap.ws-url}")
-    private String rapBaseUrl;
-
-    @Value("${security.oauth2.client.access-token-uri}")
-    private String accessTokenUri;
-
-    @Value("${security.oauth2.client.client-id}")
-    private String clientId;
-
-    @Value("${security.oauth2.client.client-secret}")
-    private String clientSecret;
-
-    @Value("${security.oauth2.client.scope}")
-    private String scope;
-
-    @Autowired
-    private HttpServletRequest request;
-
-    @Autowired(required = false)
-    private SessionData sessionData;
-
-//    private final RestTemplate rapRestTemplate;
-//
-//    private final RestTemplate refreshTokenRestTemplate;
-
-    private final ObjectMapper objectMapper = new ObjectMapper();
-//
-//    @Autowired
-//    public RapClient(RestTemplate rapRestTemplate) {
-//        this.rapRestTemplate = rapRestTemplate;
-//        this.refreshTokenRestTemplate = new RestTemplate();
-//    }
-//
-//    public RapUser getUser(String userId) {
-//
-//        String url = rapBaseUrl + "/user/" + userId;
-//
-//        return httpCall(entity -> {
-//            return rapRestTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<RapUser>() {
-//            }).getBody();
-//        });
-//    }
-//
-//    public List<RapUser> getUsers(Set<String> identifiers) {
-//
-//        if (identifiers.isEmpty()) {
-//            return new ArrayList<>();
-//        }
-//
-//        String url = rapBaseUrl + "/user?identifiers=" + String.join(",", identifiers);
-//
-//        return httpCall(entity -> {
-//            return rapRestTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<List<RapUser>>() {
-//            }).getBody();
-//        });
-//    }
-//
-//    public List<RapUser> searchUsers(String searchText) {
-//
-//        if (searchText == null || searchText.trim().isEmpty()) {
-//            return new ArrayList<>();
-//        }
-//
-//        String url = rapBaseUrl + "/user?search=" + searchText;
-//
-//        return httpCall(entity -> {
-//            return rapRestTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<List<RapUser>>() {
-//            }).getBody();
-//        });
-//    }
-//
-//    private <R> R httpCall(Function<HttpEntity<?>, R> function) {
-//        return httpCall(function, null);
-//    }
-//
-//    private <R, T> R httpCall(Function<HttpEntity<?>, R> function, T body) {
-//        try {
-//            try {
-//                return function.apply(getEntity(body));
-//            } catch (HttpClientErrorException.Unauthorized ex) {
-//                if (request.getSession(false) == null || sessionData.getExpiresIn() > 0) {
-//                    // we can't refresh the token without a session
-//                    throw ex;
-//                }
-//                refreshToken();
-//                return function.apply(getEntity(body));
-//            }
-//        } catch (HttpStatusCodeException ex) {
-//            try {
-//                Map<String, String> map = objectMapper.readValue(ex.getResponseBodyAsString(), Map.class);
-//                if (map.containsKey("error")) {
-//                    String error = map.get("error");
-//                    if (ex instanceof HttpClientErrorException) {
-//                        throw new HttpClientErrorException(ex.getStatusCode(), error);
-//                    } else if (ex instanceof HttpServerErrorException) {
-//                        throw new HttpServerErrorException(ex.getStatusCode(), error);
-//                    }
-//                }
-//            } catch (JsonProcessingException ignore) {
-//            }
-//            throw ex;
-//        }
-//    }
-//
-//    private <T> HttpEntity<T> getEntity(T body) {
-//
-//        HttpHeaders headers = new HttpHeaders();
-//        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
-//        if (request.getSession(false) != null) {
-//            headers.add("Authorization", "Bearer " + sessionData.getAccessToken());
-//        } else {
-//            // from JWT web service
-//            headers.add("Authorization", request.getHeader("Authorization"));
-//        }
-//
-//        return new HttpEntity<>(body, headers);
-//    }
-//
-//    public void refreshToken() {
-//
-//        HttpHeaders headers = new HttpHeaders();
-//        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
-//        headers.setBasicAuth(clientId, clientSecret);
-//
-//        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-//
-//        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
-//        map.add("grant_type", "refresh_token");
-//        map.add("refresh_token", sessionData.getRefreshToken());
-//        map.add("scope", scope.replace(",", " "));
-//
-//        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
-//
-//        ResponseEntity<Map> response = refreshTokenRestTemplate.postForEntity(accessTokenUri, request, Map.class);
-//
-//        Map<String, Object> values = response.getBody();
-//
-//        sessionData.setAccessToken((String) values.get("access_token"));
-//        sessionData.setRefreshToken((String) values.get("refresh_token"));
-//        sessionData.setExpiresIn((int) values.get("expires_in"));
-//    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
index 5668fb21a63083f43acd0718666cb8f7d7a18242..be619b1700e66d1291ecfc174e50d7f5bd383ecb 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java
@@ -1,15 +1,17 @@
 package it.inaf.ia2.gms.service;
 
+import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
-import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -20,8 +22,12 @@ import org.springframework.stereotype.Service;
 @Service
 public class GroupNameService {
 
+    private final GroupsDAO groupsDAO;
+
     @Autowired
-    private GroupsDAO groupsDAO;
+    public GroupNameService(GroupsDAO groupsDAO) {
+        this.groupsDAO = groupsDAO;
+    }
 
     public List<String> getGroupsNamesFromIdentifiers(Set<String> groupIdentifiers) {
         return getGroupsNames(groupsDAO.findGroupsByIds(groupIdentifiers));
@@ -37,113 +43,116 @@ public class GroupNameService {
      */
     public List<String> getGroupsNames(List<GroupEntity> groups) {
 
-        // We need to return the complete group name, so it is necessary to load
-        // all the parents too.
-        Map<String, String> idNameMap = new HashMap<>();
-        Set<String> allIdentifiers = getAllIdentifiers(groups);
-        for (GroupEntity group : groupsDAO.findGroupsByIds(allIdentifiers)) {
-            idNameMap.put(group.getId(), group.getName());
-        }
+        Set<String> groupIds = groups.stream().map(g -> g.getId()).collect(Collectors.toSet());
 
-        List<String> names = new ArrayList<>();
-        for (GroupEntity group : groups) {
-            names.add(getGroupCompleteName(group, idNameMap));
+        List<String> names = new ArrayList<>(groupsDAO.getGroupCompleteNamesFromId(groupIds).values());
+        if (groupIds.contains("ROOT")) {
+            names.add("Root");
         }
-        return names;
-    }
 
-    private Set<String> getAllIdentifiers(List<GroupEntity> groups) {
-
-        Set<String> allIdentifiers = new HashSet<>();
-        for (GroupEntity group : groups) {
-            if (!"".equals(group.getPath())) {
-                String[] ids = group.getPath().split("\\.");
-                for (String id : ids) {
-                    allIdentifiers.add(id);
-                }
-            }
-        }
+        Collections.sort(names);
 
-        return allIdentifiers;
+        return names;
     }
 
-    private String getGroupCompleteName(GroupEntity group, Map<String, String> idNameMap) {
+    /**
+     * @param groups
+     * @return map having group id as keys and group names as values
+     */
+    public Map<String, List<String>> getNames(Set<GroupEntity> groups) {
 
-        if ("ROOT".equals(group.getId())) {
-            return group.getName();
-        }
+        Set<String> groupIds = groups.stream()
+                .map(g -> g.getId()).collect(Collectors.toSet());
 
-        List<String> names = new ArrayList<>();
+        return getNamesFromIds(groupIds);
+    }
 
-        for (String groupId : group.getPath().split("\\.")) {
+    public Map<String, List<String>> getNamesFromIds(Set<String> groupIds) {
 
-            String groupName = idNameMap.get(groupId);
+        Map<String, List<String>> result = new HashMap<>();
 
-            // Dot inside names is considered a special character (because it is
-            // used to separate the group from its parents), so we use a
-            // backslash to escape it (client apps need to be aware of this).
-            groupName = groupName.replace("\\.", "\\\\.");
+        if (groupIds.contains("ROOT")) {
+            result.put("ROOT", Collections.singletonList("Root"));
+        }
 
-            names.add(groupName);
+        for (Map.Entry<String, String> entry : groupsDAO.getGroupCompleteNamesFromId(groupIds).entrySet()) {
+            List<String> names = splitNames(entry.getValue());
+            result.put(entry.getKey(), names);
         }
 
-        return String.join(".", names);
+        return result;
     }
 
-    /**
-     * @param groupsIdPath map having group id as keys and group paths as values
-     * @return map having group id as keys and group names as values
-     */
-    public Map<String, List<String>> getNames(List<Map.Entry<String, String>> groupsIdPath) {
+    private List<String> splitNames(String completeGroupName) {
+        return Arrays.asList(completeGroupName.split("(?<!\\\\)\\."));
+    }
 
-        Set<String> allIdentifiers = new HashSet<>();
-        for (Map.Entry<String, String> entry : groupsIdPath) {
-            allIdentifiers.addAll(getIdentifiers(entry.getValue()));
+    public String getShortGroupName(String completeGroupName, Optional<String> groupPrefix) {
+        if (groupPrefix.isPresent()) {
+            return completeGroupName.substring(groupPrefix.get().length() + 1);
         }
+        return completeGroupName;
+    }
 
-        Map<String, String> groupSingleNamesMap = getGroupSingleNamesMap(allIdentifiers);
+    public GroupEntity getGroupFromNames(Optional<String> group) {
 
-        Map<String, List<String>> groupCompleteNamesMap = new HashMap<>();
-        for (Map.Entry<String, String> entry : groupsIdPath) {
-            List<String> groupCompleteName = getGroupCompleteName(groupSingleNamesMap, entry.getValue());
-            groupCompleteNamesMap.put(entry.getKey(), groupCompleteName);
+        List<String> groupNames = extractGroupNames(group);
+
+        if (groupNames.isEmpty()) {
+            return getRoot();
         }
+        return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1);
+    }
 
-        return groupCompleteNamesMap;
+    public GroupEntity getGroupFromNamesAndIndex(Optional<String> group, int index) {
+        List<String> groupNames = extractGroupNames(group);
+        return getGroupFromNamesAndIndex(groupNames, index);
     }
 
-    private Map<String, String> getGroupSingleNamesMap(Set<String> allIdentifiers) {
-        Map<String, String> groupNamesMap = new HashMap<>();
-        for (GroupEntity group : groupsDAO.findGroupsByIds(allIdentifiers)) {
-            groupNamesMap.put(group.getId(), group.getName());
+    private GroupEntity getGroupFromNamesAndIndex(List<String> groupNames, int index) {
+        String parentPath = ""; // starting from ROOT
+        GroupEntity group = null;
+        for (int i = 0; i < index + 1; i++) {
+            String groupName = groupNames.get(i);
+            group = groupsDAO.findGroupByParentAndName(parentPath, groupName)
+                    .orElseThrow(() -> new BadRequestException("Unable to find group " + groupName));
+            parentPath = group.getPath();
         }
+        if (group == null) {
+            throw new IllegalStateException();
+        }
+        return group;
+    }
 
-        return groupNamesMap;
+    private List<String> extractGroupNames(Optional<String> group) {
+        return extractGroupNames(group.orElse(null));
     }
 
-    private List<String> getGroupCompleteName(Map<String, String> groupNamesMap, String groupPath) {
+    public List<String> extractGroupNames(String groupStr) {
+
+        if (groupStr == null || groupStr.isEmpty()) {
+            return new ArrayList<>();
+        }
+
         List<String> names = new ArrayList<>();
-        if (groupPath.isEmpty()) {
-            names.add("Root");
-        } else {
-            List<String> identifiers = getIdentifiers(groupPath);
-            for (String groupId : identifiers) {
-                names.add(groupNamesMap.get(groupId));
+        String currentName = "";
+        for (int i = 0; i < groupStr.length(); i++) {
+            char c = groupStr.charAt(i);
+            // dot is the group separator and it must be escaped if used inside
+            // group names
+            if (c == '.' && groupStr.charAt(i - 1) != '\\') {
+                names.add(currentName.replace("\\.", "."));
+                currentName = "";
+            } else {
+                currentName += c;
             }
         }
+        names.add(currentName);
         return names;
     }
 
-    /**
-     * Returns the list of all identifiers including parent ones.
-     */
-    private List<String> getIdentifiers(String groupPath) {
-        List<String> identifiers = new ArrayList<>();
-        if (!groupPath.isEmpty()) {
-            for (String id : groupPath.split(Pattern.quote("."))) {
-                identifiers.add(id);
-            }
-        }
-        return identifiers;
+    private GroupEntity getRoot() {
+        return groupsDAO.findGroupById("ROOT")
+                .orElseThrow(() -> new IllegalStateException("Missing root group"));
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/SearchService.java b/gms/src/main/java/it/inaf/ia2/gms/service/SearchService.java
index bd7f6355a0cb649d03d4b5aa50dfb2622d5f7aff..8ba7f3c95737f20403746e13fe26a0a68413d88d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/SearchService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/SearchService.java
@@ -14,10 +14,10 @@ import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
 import it.inaf.ia2.rap.client.RapClient;
-import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -75,22 +75,16 @@ public class SearchService {
 
         // Select only the groups visible to the user
         List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId);
-
-        List<Map.Entry<String, String>> groupsIdPath = new ArrayList<>();
-        for (GroupEntity group : allGroups) {
-            PermissionUtils.getGroupPermission(group, permissions).ifPresent(permission -> {
-                groupsIdPath.add(new SimpleEntry<>(group.getId(), group.getPath()));
-            });
-        }
+        Set<GroupEntity> visibleGroups = getVisibleGroups(allGroups, permissions);
 
         List<SearchResponseItem> items = new ArrayList<>();
-        Map<String, List<String>> groupNames = groupNameService.getNames(groupsIdPath);
-        for (Map.Entry<String, String> entry : groupsIdPath) {
-            String groupId = entry.getKey();
+        Map<String, List<String>> groupNames = groupNameService.getNames(visibleGroups);
+
+        for (GroupEntity group : visibleGroups) {
             SearchResponseItem item = new SearchResponseItem();
             item.setType(SearchResponseType.GROUP);
-            item.setId(groupId);
-            List<String> names = groupNames.get(groupId);
+            item.setId(group.getId());
+            List<String> names = groupNames.get(group.getId());
             item.setLabel(String.join(" / ", names));
             items.add(item);
         }
@@ -129,15 +123,9 @@ public class SearchService {
         List<GroupEntity> allGroups = membershipsDAO.getUserMemberships(targetUserId);
 
         // Select only groups visible to the actor user
-        List<Map.Entry<String, String>> visibleGroupsIdPath = new ArrayList<>();
-        for (GroupEntity group : allGroups) {
-
-            PermissionUtils.getGroupPermission(group, actorPermissions).ifPresent(permission -> {
-                visibleGroupsIdPath.add(new SimpleEntry<>(group.getId(), group.getPath()));
-            });
-        }
+        Set<GroupEntity> visibleGroups = getVisibleGroups(allGroups, actorPermissions);
 
-        return groupNameService.getNames(visibleGroupsIdPath).entrySet().stream()
+        return groupNameService.getNames(visibleGroups).entrySet().stream()
                 .map(entry -> {
                     UserGroup ug = new UserGroup();
                     ug.setGroupId(entry.getKey());
@@ -147,6 +135,12 @@ public class SearchService {
                 .collect(Collectors.toList());
     }
 
+    private Set<GroupEntity> getVisibleGroups(List<GroupEntity> allGroups, List<PermissionEntity> permissions) {
+        return allGroups.stream()
+                .filter(g -> PermissionUtils.getGroupPermission(g, permissions).isPresent())
+                .collect(Collectors.toSet());
+    }
+
     public List<UserPermission> getUserPermission(GroupEntity group, String targetUserId, List<PermissionEntity> actorPermissions) {
 
         List<UserPermission> permissions = new ArrayList<>();
@@ -159,12 +153,10 @@ public class SearchService {
                         = permissionsDAO.findUserPermissions(targetUserId).stream()
                                 .collect(Collectors.toMap(PermissionEntity::getGroupId, p -> p));
 
-                List<Map.Entry<String, String>> groupsIdPath = new ArrayList<>();
-                for (PermissionEntity p : targetUserPermissions.values()) {
-                    groupsIdPath.add(new SimpleEntry<>(p.getGroupId(), p.getGroupPath()));
-                }
+                Set<String> groupIds = targetUserPermissions.values().stream()
+                        .map(p -> p.getGroupId()).collect(Collectors.toSet());
 
-                for (Map.Entry<String, List<String>> entry : groupNameService.getNames(groupsIdPath).entrySet()) {
+                for (Map.Entry<String, List<String>> entry : groupNameService.getNamesFromIds(groupIds).entrySet()) {
                     UserPermission up = new UserPermission();
                     up.setGroupId(entry.getKey());
                     up.setGroupCompleteName(entry.getValue());
diff --git a/gms/src/main/resources/sql/init.sql b/gms/src/main/resources/sql/init.sql
index 05aec53ca465b3b7fe8241685973524e74e2daae..56103d17830c7a050f92fbc250e40f74188d6910 100644
--- a/gms/src/main/resources/sql/init.sql
+++ b/gms/src/main/resources/sql/init.sql
@@ -63,3 +63,16 @@ CREATE TABLE invited_registration_request_group (
   FOREIGN KEY (request_id) REFERENCES invited_registration_request(id),
   FOREIGN KEY (group_id) REFERENCES gms_group(id)
 );
+
+CREATE VIEW group_complete_name AS
+SELECT id, string_agg(name, '.') AS complete_name
+FROM (
+    SELECT replace(name, '.', '\.') AS name, p.id
+    FROM gms_group g
+    JOIN (
+        SELECT UNNEST(string_to_array(path::varchar, '.')) AS rel_id, id
+        FROM gms_group
+    ) AS p ON g.id = p.rel_id
+    ORDER BY p.id, nlevel(g.path)
+) AS j
+GROUP BY id ORDER BY complete_name;
diff --git a/gms/src/test/java/it/inaf/ia2/gms/GmsTestUtils.java b/gms/src/test/java/it/inaf/ia2/gms/GmsTestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed79a9b487d01f080584b3eca67379d05dba4e4c
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/GmsTestUtils.java
@@ -0,0 +1,19 @@
+package it.inaf.ia2.gms;
+
+import java.security.Principal;
+import javax.servlet.http.HttpServletRequest;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class GmsTestUtils {
+
+    public static void mockPrincipal(HttpServletRequest mockedServletRequest) {
+        mockPrincipal(mockedServletRequest, "admin_id");
+    }
+
+    public static void mockPrincipal(HttpServletRequest mockedServletRequest, String userId) {
+        Principal principal = mock(Principal.class);
+        when(principal.getName()).thenReturn(userId);
+        when(mockedServletRequest.getUserPrincipal()).thenReturn(principal);
+    }
+}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/authn/SessionDataTest.java b/gms/src/test/java/it/inaf/ia2/gms/authn/SessionDataTest.java
index 00648021317db5c3704b58e56feb509e7072f644..75d44989f48e75cce0fd7d7c62b8079c91acd6e7 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/authn/SessionDataTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/authn/SessionDataTest.java
@@ -1,6 +1,7 @@
 package it.inaf.ia2.gms.authn;
 
 import it.inaf.ia2.aa.data.User;
+import it.inaf.ia2.rap.client.RapClient;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import static org.junit.Assert.assertTrue;
@@ -18,6 +19,9 @@ public class SessionDataTest {
 
     @Mock
     private HttpServletRequest request;
+    
+    @Mock
+    private RapClient rapClient;
 
     @InjectMocks
     private SessionData sessionData;
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
index f09287e45d31828aad7464f635384ecc9eca5fab..cdbdb8b42f3ce19f906ebcb99d35b604c01a58a2 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
@@ -1,7 +1,7 @@
 package it.inaf.ia2.gms.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.GmsTestUtils;
 import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.GroupNode;
@@ -14,6 +14,7 @@ import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
 import java.util.ArrayList;
 import java.util.List;
+import javax.servlet.http.HttpServletRequest;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import org.junit.Before;
@@ -50,7 +51,7 @@ public class GroupsControllerTest {
     private GroupsService groupsService;
 
     @Mock
-    private SessionData session;
+    private HttpServletRequest servletRequest;
 
     @Mock
     private PermissionsManager permissionsManager;
@@ -71,6 +72,7 @@ public class GroupsControllerTest {
     @Before
     public void init() {
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+        GmsTestUtils.mockPrincipal(servletRequest);
     }
 
     @Test
@@ -104,8 +106,6 @@ public class GroupsControllerTest {
         PaginatedData<GroupNode> paginatedData = new PaginatedData<>(nodes, 1, 10);
         when(groupsTreeBuilder.listSubGroups(any(), any(), any())).thenReturn(paginatedData);
 
-        when(session.getUserId()).thenReturn("admin_id");
-
         mockMvc.perform(post("/group")
                 .content(mapper.writeValueAsString(request))
                 .contentType(MediaType.APPLICATION_JSON))
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
index 937fbe74b33dd9e0756099bcb62d47f14a00ffb1..07f1e8577751f8a8c61340f530a971457bb3b269 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
@@ -1,6 +1,6 @@
 package it.inaf.ia2.gms.controller;
 
-import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.GmsTestUtils;
 import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
@@ -13,6 +13,7 @@ import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
 import java.util.ArrayList;
+import javax.servlet.http.HttpServletRequest;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import org.junit.Test;
@@ -28,7 +29,7 @@ import org.mockito.junit.MockitoJUnitRunner;
 public class GroupsTabResponseBuilderTest {
 
     @Mock
-    private SessionData session;
+    private HttpServletRequest servletRequest;
 
     @Mock
     private GroupsManager groupsManager;
@@ -51,7 +52,7 @@ public class GroupsTabResponseBuilderTest {
     @Test
     public void testGetGroupsTab() {
 
-        when(session.getUserId()).thenReturn("admin_id");
+        GmsTestUtils.mockPrincipal(servletRequest);
 
         GroupEntity root = new GroupEntity();
         root.setId("ROOT");
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
index 89453288494407d08101c36f322287d73aae4017..1a086e3dcf0112b6437841e576ef81227f0e0393 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
@@ -4,12 +4,13 @@ import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.manager.MembershipManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.model.RapUserPermission;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.MembershipEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.service.GroupNameService;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.JoinService;
 import it.inaf.ia2.rap.data.RapUser;
@@ -81,6 +82,7 @@ public class JWTWebServiceControllerTest {
 
     @Before
     public void init() {
+        controller.groupNameService = new GroupNameService(groupsDAO);
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
         root = getRoot();
         lbt = getLbtGroup();
@@ -193,8 +195,8 @@ public class JWTWebServiceControllerTest {
         when(groupsDAO.findGroupByParentAndName("", "LBT")).thenReturn(Optional.of(lbt));
         when(groupsDAO.findGroupByParentAndName("lbt_id", "INAF")).thenReturn(Optional.of(inaf));
 
-        List<UserPermission> permissions = new ArrayList<>();
-        UserPermission up = new UserPermission();
+        List<RapUserPermission> permissions = new ArrayList<>();
+        RapUserPermission up = new RapUserPermission();
         up.setUser(getRapUser());
         up.setPermission(Permission.ADMIN);
         permissions.add(up);
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/SearchControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/SearchControllerTest.java
index f419eba57f56c1c58302ffd750434f5eaf4bd39b..bfe0b1a1bb7b81ff65c4010b430779c011d2d1f5 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/SearchControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/SearchControllerTest.java
@@ -1,12 +1,13 @@
 package it.inaf.ia2.gms.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.GmsTestUtils;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.response.SearchResponseItem;
 import it.inaf.ia2.gms.model.response.UserSearchResponse;
 import it.inaf.ia2.gms.service.SearchService;
 import java.util.ArrayList;
+import javax.servlet.http.HttpServletRequest;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +30,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 public class SearchControllerTest {
 
     @Mock
-    private SessionData session;
+    private HttpServletRequest servletRequest;
 
     @Mock
     private SearchService searchService;
@@ -44,8 +45,7 @@ public class SearchControllerTest {
     @Before
     public void init() {
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
-
-        when(session.getUserId()).thenReturn("admin_id");
+        GmsTestUtils.mockPrincipal(servletRequest);
     }
 
     @Test
@@ -56,7 +56,7 @@ public class SearchControllerTest {
         when(searchService.search(any(), any(), anyInt(), anyInt())).thenReturn(response);
 
         mockMvc.perform(get("/search?query=searchText&page=1&pageSize=10")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
+                .contentType(MediaType.APPLICATION_JSON_VALUE))
                 .andExpect(status().isOk());
 
         verify(searchService, times(1)).search(eq("searchText"), eq("admin_id"), eq(1), eq(10));
@@ -68,7 +68,7 @@ public class SearchControllerTest {
         when(searchService.getUserSearchResult(any(), any())).thenReturn(new UserSearchResponse());
 
         mockMvc.perform(get("/search/user/user_id")
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
+                .contentType(MediaType.APPLICATION_JSON_VALUE))
                 .andExpect(status().isOk());
 
         verify(searchService, times(1)).getUserSearchResult(eq("admin_id"), eq("user_id"));
diff --git a/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java b/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java
index 192c62e1a42b1750b4849c44660cc7d5e238745c..4ce897a21f44e4e3c047087336188913ba198e60 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java
@@ -1,6 +1,6 @@
 package it.inaf.ia2.gms.manager;
 
-import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.GmsTestUtils;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.InvitedRegistrationDAO;
@@ -19,6 +19,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +52,7 @@ public class InvitedRegistrationManagerTest {
     @Mock
     private RapClient rapClient;
     @Mock
-    private SessionData sessionData;
+    private HttpServletRequest servletRequest;
     @Mock
     private LoggingDAO loggingDAO;
     @Mock
@@ -95,7 +96,7 @@ public class InvitedRegistrationManagerTest {
 
         when(httpSession.getAttribute(eq("invited-registration"))).thenReturn(regFromToken);
 
-        when(sessionData.getUserId()).thenReturn(USER_ID);
+        GmsTestUtils.mockPrincipal(servletRequest, USER_ID);
 
         RapUser user = new RapUser();
         user.setId(USER_ID);
@@ -145,7 +146,7 @@ public class InvitedRegistrationManagerTest {
 
         when(httpSession.getAttribute(eq("invited-registration"))).thenReturn(regFromToken);
 
-        when(sessionData.getUserId()).thenReturn(USER_ID);
+        GmsTestUtils.mockPrincipal(servletRequest, USER_ID);
 
         RapUser user = new RapUser();
         user.setId(USER_ID);
diff --git a/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
index 5eec6c7d48b9dcb3ee02e115f3e7c9ba873fb002..93190b4c73ed43306a2784da6d1e51262bbb4f8e 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
@@ -2,7 +2,7 @@ package it.inaf.ia2.gms.manager;
 
 import it.inaf.ia2.gms.DataSourceConfig;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.model.RapUserPermission;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
@@ -73,7 +73,7 @@ public class PermissionsManagerIntegrationTest {
         superAdminPermission.setGroupPath(root.getPath());
         permissionsDAO.createOrUpdatePermission(superAdminPermission);
 
-        List<UserPermission> permissions = permissionsManager.getAllPermissions(root);
+        List<RapUserPermission> permissions = permissionsManager.getAllPermissions(root);
 
         assertEquals(1, permissions.size());
         assertEquals(Permission.ADMIN, permissions.get(0).getPermission());
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
index fd56e8a6302bab237d6d0ffb69930f77e8a59dd7..17fb805a987694fb50bcd2fda50c433a4d3b0839 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
@@ -5,9 +5,11 @@ import it.inaf.ia2.gms.HooksConfig;
 import it.inaf.ia2.gms.model.GroupBreadcrumb;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.hook.GroupsHook;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import javax.sql.DataSource;
 import static org.junit.Assert.assertEquals;
@@ -114,6 +116,15 @@ public class GroupsDAOTest {
         assertTrue(optGroup.isPresent());
         assertEquals(lbtInaf.getId(), optGroup.get().getId());
 
+        // Complete names
+        Set<String> groupIds = new HashSet<>();
+        groupIds.add(groups.get(0).getId());
+        groupIds.add(lbt.getId());
+        Map<String, String> completeGroupNames = dao.getGroupCompleteNamesFromId(groupIds);
+        assertEquals(2, completeGroupNames.size());
+        assertEquals("LBT", completeGroupNames.get(lbt.getId()));
+        assertEquals("LBT.INAF", completeGroupNames.get(groups.get(0).getId()));
+
         // Children map
         Map<String, Boolean> childrenMap = dao.getHasChildrenMap(Sets.newSet(root.getId()));
         assertEquals(1, childrenMap.size());
@@ -151,4 +162,9 @@ public class GroupsDAOTest {
     private String getNewGroupId() {
         return UUID.randomUUID().toString().replaceAll("-", "");
     }
+
+    @Test
+    public void testGroupCompleteNamesEmptyInput() {
+        assertTrue(dao.getGroupCompleteNamesFromId(new HashSet<>()).isEmpty());
+    }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/GroupNameServiceTest.java b/gms/src/test/java/it/inaf/ia2/gms/service/GroupNameServiceTest.java
index 1ec9f9aee50f263f31ff2ac0cfaad63f9c76aa18..2b4b74c84f830201b263fdb7241bebd94e554fdc 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/service/GroupNameServiceTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/service/GroupNameServiceTest.java
@@ -4,8 +4,11 @@ import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.util.AbstractMap;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import static org.junit.Assert.assertEquals;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,54 +30,38 @@ public class GroupNameServiceTest {
     @Test
     public void getNamesTest() {
 
-        mockGroupsDAO();
+        GroupEntity group = new GroupEntity();
+        group.setName("Child\\.withDot");
+        group.setId("def");
+        group.setPath("abc.def");
 
-        List<Map.Entry<String, String>> groupsIdPath = new ArrayList<>();
-        groupsIdPath.add(new AbstractMap.SimpleEntry<>("def", "abc.def"));
-
-        Map<String, List<String>> names = groupNameService.getNames(groupsIdPath);
-        assertEquals(1, names.size());
-        assertEquals(2, names.get("def").size());
-        assertEquals("Group 1", names.get("def").get(0));
-        assertEquals("Group 2", names.get("def").get(1));
-    }
+        Set<GroupEntity> groups = new HashSet<>();
+        groups.add(group);
 
-    public void mockGroupsDAO() {
+        Map<String, String> daoResponse = new HashMap<>();
+        daoResponse.put("def", "Parent_group.Child\\.withDot");
 
-        List<GroupEntity> groups = new ArrayList<>();
+        when(groupsDAO.getGroupCompleteNamesFromId(any())).thenReturn(daoResponse);
 
-        GroupEntity group1 = new GroupEntity();
-        group1.setId("abc");
-        group1.setName("Group 1");
-        group1.setPath("abc");
-        groups.add(group1);
-
-        GroupEntity group2 = new GroupEntity();
-        group2.setId("def");
-        group2.setName("Group 2");
-        group2.setPath("abc.def");
-        groups.add(group2);
-
-        when(groupsDAO.findGroupsByIds(any())).thenReturn(groups);
+        Map<String, List<String>> names = groupNameService.getNames(groups);
+        assertEquals(1, names.size());
+        assertEquals(2, names.get("def").size());
+        assertEquals("Parent_group", names.get("def").get(0));
+        assertEquals("Child\\.withDot", names.get("def").get(1));
     }
 
     @Test
     public void getRootTest() {
 
-        List<GroupEntity> groups = new ArrayList<>();
-
-        GroupEntity root = new GroupEntity();
-        root.setId("ROOT");
-        root.setName("Root");
-        root.setPath("");
-        groups.add(root);
+        Set<String> groupIds = new HashSet<>();
+        groupIds.add("ROOT");
 
-        when(groupsDAO.findGroupsByIds(any())).thenReturn(groups);
+        when(groupsDAO.getGroupCompleteNamesFromId(any())).thenReturn(new HashMap<>());
 
         List<Map.Entry<String, String>> groupsIdPath = new ArrayList<>();
         groupsIdPath.add(new AbstractMap.SimpleEntry<>("ROOT", ""));
 
-        Map<String, List<String>> names = groupNameService.getNames(groupsIdPath);
+        Map<String, List<String>> names = groupNameService.getNamesFromIds(groupIds);
         assertEquals(1, names.size());
         assertEquals(1, names.get("ROOT").size());
         assertEquals("Root", names.get("ROOT").get(0));
diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/SearchServiceTest.java b/gms/src/test/java/it/inaf/ia2/gms/service/SearchServiceTest.java
index bb44208ef9f8a15c0a99a6e231e869a1f442831d..72b06ce8161daa4c1b08b6327346c1e410d77439 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/service/SearchServiceTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/service/SearchServiceTest.java
@@ -20,7 +20,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Set;
 import static org.junit.Assert.assertEquals;
 import org.junit.Before;
 import org.junit.Test;
@@ -64,10 +64,10 @@ public class SearchServiceTest {
 
         when(groupNameService.getNames(any())).then(invocation -> {
             Map<String, List<String>> result = new HashMap<>();
-            List<Map.Entry<String, String>> arg = invocation.getArgument(0);
-            for (Entry<String, String> entry : arg) {
+            Set<GroupEntity> arg = invocation.getArgument(0);
+            for (GroupEntity group : arg) {
                 List<String> names = new ArrayList<>();
-                switch (entry.getKey()) {
+                switch (group.getId()) {
                     case "ROOT":
                         names.add("Root");
                         break;
@@ -75,7 +75,7 @@ public class SearchServiceTest {
                         names.add("Group 1");
                         break;
                 }
-                result.put(entry.getKey(), names);
+                result.put(group.getId(), names);
             }
             return result;
         });
@@ -136,6 +136,11 @@ public class SearchServiceTest {
     @Test
     public void testGetUserSearchResult() {
 
+        Map<String, List<String>> nameResult = new HashMap<>();
+        nameResult.put("group1_id", Collections.singletonList("Group 1"));
+
+        when(groupNameService.getNamesFromIds(any())).thenReturn(nameResult);
+
         GroupEntity group1 = new GroupEntity();
         group1.setId("group1_id");
         group1.setName("Group 1");