diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceController.java
deleted file mode 100644
index 9b9d27fa478d9a7040a90e32d07b5b1844ff32e2..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceController.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package it.inaf.ia2.gms.controller;
-
-import it.inaf.ia2.gms.exception.BadRequestException;
-import it.inaf.ia2.gms.model.request.AddMemberWsRequest;
-import it.inaf.ia2.gms.model.request.AddPermissionWsRequest;
-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.GroupsService;
-import it.inaf.ia2.gms.service.MembersService;
-import it.inaf.ia2.gms.service.PermissionsService;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import javax.validation.Valid;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * Controller for programmatic access using registered clients.
- */
-@RestController
-@RequestMapping("/ws/basic")
-public class BasicAuthWebServiceController {
-
-    @Autowired
-    private GroupsService groupsService;
-
-    @Autowired
-    private MembersService membersService;
-
-    @Autowired
-    private PermissionsService permissionsService;
-
-    /**
-     * Creates a group and its ancestors if they are missing. It doesn't fail if
-     * the last group already exists.
-     */
-    @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<GroupEntity> createGroup(@RequestBody List<String> names) {
-
-        GroupEntity group = groupsService.getRoot();
-
-        for (String name : names) {
-            Optional<GroupEntity> optGroup = groupsService.findGroupByParentAndName(group, name);
-            if (optGroup.isPresent()) {
-                group = optGroup.get();
-            } else {
-                group = groupsService.addGroup(group, name, false);
-            }
-        }
-
-        return new ResponseEntity<>(group, HttpStatus.CREATED);
-    }
-
-    @DeleteMapping("/group")
-    public ResponseEntity<?> deleteGroupByPath(@RequestParam("names") String[] names) {
-
-        GroupEntity groupToDelete = getGroupByNames(Arrays.asList(names));
-
-        groupsService.deleteGroup(groupToDelete);
-
-        return ResponseEntity.noContent().build();
-    }
-
-    @PostMapping(value = "/member", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<MembershipEntity> addMember(@RequestBody AddMemberWsRequest request) {
-
-        GroupEntity group = getGroupByNames(request.getNames());
-
-        MembershipEntity membership = membersService.addMember(group.getId(), request.getUserId());
-
-        return new ResponseEntity<>(membership, HttpStatus.CREATED);
-    }
-
-    @DeleteMapping("/member")
-    public ResponseEntity<?> removeMember(@RequestParam("names") String[] names, @RequestParam("userId") String userId) {
-
-        GroupEntity group = getGroupByNames(Arrays.asList(names));
-
-        membersService.removeMember(group.getId(), userId);
-
-        return ResponseEntity.noContent().build();
-    }
-
-    @PostMapping(value = "/permission", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<PermissionEntity> addPermission(@Valid @RequestBody AddPermissionWsRequest request) {
-
-        GroupEntity group = getGroupByNames(request.getNames());
-
-        PermissionEntity newPermission = permissionsService.addPermission(group, request.getUserId(), request.getPermission());
-
-        return new ResponseEntity<>(newPermission, HttpStatus.CREATED);
-    }
-
-    @DeleteMapping("/permission")
-    public ResponseEntity<?> deletePermission(@RequestParam("names") String[] names, @RequestParam("userId") String userId) {
-
-        GroupEntity group = getGroupByNames(Arrays.asList(names));
-
-        permissionsService.removePermission(group, userId);
-
-        return ResponseEntity.noContent().build();
-    }
-
-    private GroupEntity getGroupByNames(List<String> names) {
-        return groupsService.findGroupByNames(names)
-                .orElseThrow(() -> new BadRequestException("Unable to find requested group"));
-    }
-}
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 6029b734f8dfbe0fdc3d17ec08fc630e65e60ef8..ab97ed1d689799c905051c1663a2de4887419c4a 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,21 +1,17 @@
 package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
-import it.inaf.ia2.gms.exception.UnauthorizedException;
+import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.model.request.AddGroupRequest;
 import it.inaf.ia2.gms.model.GroupNode;
 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.request.DeleteGroupRequest;
 import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.model.request.RenameGroupRequest;
 import it.inaf.ia2.gms.model.request.SearchFilterRequest;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
-import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
-import it.inaf.ia2.gms.service.PermissionsService;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -36,10 +32,7 @@ public class GroupsController {
     private SessionData session;
 
     @Autowired
-    private PermissionsService permissionsService;
-
-    @Autowired
-    private GroupsService groupsService;
+    private GroupsManager groupsManager;
 
     @Autowired
     private GroupsTreeBuilder groupsTreeBuilder;
@@ -47,14 +40,11 @@ public class GroupsController {
     @Autowired
     private GroupsTabResponseBuilder groupsTabResponseBuilder;
 
-    @Autowired
-    private LoggingDAO loggingDAO;
-
     @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<?> getGroupsTab(@Valid GroupsRequest request) {
         if (request.isOnlyPanel()) {
             // Only groupsPanel
-            GroupEntity group = groupsService.getGroupById(request.getGroupId());
+            GroupEntity group = groupsManager.getGroupById(request.getGroupId());
             return ResponseEntity.ok(getGroupsPanel(group, request));
         } else {
             // Complete GroupsTabResponse
@@ -65,15 +55,7 @@ public class GroupsController {
     @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody AddGroupRequest request) {
 
-        GroupEntity parent = groupsService.getGroupById(request.getParentGroupId());
-
-        if (permissionsService.getUserPermissionForGroup(parent, session.getUserId()) != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized create group request, group_name=" + request.getNewGroupName());
-            throw new UnauthorizedException("Missing admin permission");
-        }
-
-        groupsService.addGroup(parent, request.getNewGroupName(), request.isLeaf());
-        loggingDAO.logAction("Added group: parent_path=" + parent.getPath() + ", group_name=" + request.getNewGroupName());
+        GroupEntity parent = groupsManager.createGroup(request.getParentGroupId(), request.getNewGroupName(), request.isLeaf());
 
         PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
@@ -81,19 +63,9 @@ public class GroupsController {
     }
 
     @PutMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
-    public ResponseEntity<PaginatedData<GroupNode>> renameGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) {
+    public ResponseEntity<PaginatedData<GroupNode>> updateGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) {
 
-        GroupEntity group = groupsService.getGroupById(groupId);
-
-        if (permissionsService.getUserPermissionForGroup(group, session.getUserId()) != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized rename group request, group_id=" + groupId);
-            throw new UnauthorizedException("Missing admin permission");
-        }
-
-        GroupEntity renamedGroup = groupsService.renameGroup(group, request.getNewGroupName(), request.isLeaf());
-        loggingDAO.logAction("Group renamed, group_id=" + groupId + ", new name: " + request.getNewGroupName());
-
-        GroupEntity parent = groupsService.getGroupByPath(renamedGroup.getParentPath());
+        GroupEntity parent = groupsManager.updateGroup(groupId, request.getNewGroupName(), request.isLeaf());
 
         PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
@@ -103,15 +75,7 @@ public class GroupsController {
     @DeleteMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, DeleteGroupRequest request) {
 
-        GroupEntity group = groupsService.getGroupById(groupId);
-
-        if (permissionsService.getUserPermissionForGroup(group, session.getUserId()) != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized delete group request, group_id=" + groupId);
-            throw new UnauthorizedException("Missing admin permission");
-        }
-
-        GroupEntity parent = groupsService.deleteGroup(group);
-        loggingDAO.logAction("Group deleted, group_id=" + groupId);
+        GroupEntity parent = groupsManager.deleteGroup(groupId);
         PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
 
         return ResponseEntity.ok(groupsPanel);
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 30d8463305953e48d5fb8b8823f87f403f309fee..f560d5d236777bf2515ed508bd233d03192a3510 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,13 +1,14 @@
 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.PermissionsManager;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.GroupsRequest;
 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 it.inaf.ia2.gms.service.PermissionsService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -18,7 +19,10 @@ public class GroupsTabResponseBuilder {
     private SessionData session;
 
     @Autowired
-    private PermissionsService permissionsService;
+    private PermissionsManager permissionsManager;
+
+    @Autowired
+    private GroupsManager groupsManager;
 
     @Autowired
     private GroupsService groupsService;
@@ -29,13 +33,14 @@ public class GroupsTabResponseBuilder {
     public GroupsTabResponse getGroupsTab(GroupsRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
+        groupsManager.verifyUserCanReadGroup(group);
 
         GroupsTabResponse response = new GroupsTabResponse();
 
         response.setBreadcrumbs(groupsService.getBreadcrumbs(group.getPath()));
 
-        Permission currentNodePermissions = permissionsService.getUserPermissionForGroup(group, session.getUserId());
-        response.setPermission(currentNodePermissions);
+        Permission permission = permissionsManager.getCurrentUserPermission(group);
+        response.setPermission(permission);
 
         response.setGroupsPanel(groupsListBuilder.listSubGroups(group, request, session.getUserId()));
 
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 ad153276b0643fb28a7917e8f25a62423ed93dd0..845e9dc487f3f58d264837bb0a2c0fbdc70a3811 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
@@ -2,19 +2,17 @@ package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.RapPrincipal;
 import it.inaf.ia2.gms.exception.BadRequestException;
-import it.inaf.ia2.gms.exception.UnauthorizedException;
+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.persistence.GroupsDAO;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
-import it.inaf.ia2.gms.persistence.MembershipsDAO;
 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.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.JoinService;
-import it.inaf.ia2.gms.service.MembersService;
 import it.inaf.ia2.gms.service.PermissionUtils;
-import it.inaf.ia2.gms.service.PermissionsService;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.security.Principal;
@@ -43,9 +41,6 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/ws/jwt")
 public class JWTWebServiceController {
 
-    @Autowired
-    private MembershipsDAO membershipsDAO;
-
     @Autowired
     private JoinService joinService;
 
@@ -53,27 +48,27 @@ public class JWTWebServiceController {
     private GroupsDAO groupsDAO;
 
     @Autowired
-    private GroupsService groupsService;
+    private GroupsManager groupsManager;
 
     @Autowired
-    private MembersService membersService;
+    private GroupsService groupsService;
 
     @Autowired
-    private PermissionsService permissionsService;
+    private MembershipManager membershipManager;
 
     @Autowired
-    private PermissionsDAO permissionsDAO;
+    private PermissionsManager permissionsManager;
 
     @Autowired
-    private LoggingDAO loggingDAO;
+    private PermissionsDAO permissionsDAO;
 
     /**
      * This endpoint is compliant with the IVOA GMS standard.
      */
     @GetMapping(value = "/search", produces = MediaType.TEXT_PLAIN_VALUE)
-    public void getGroups(Principal principal, HttpServletResponse response) throws IOException {
+    public void getGroups(HttpServletResponse response) throws IOException {
 
-        List<GroupEntity> memberships = membershipsDAO.getUserMemberships(principal.getName());
+        List<GroupEntity> memberships = membershipManager.getCurrentUserMemberships();
 
         List<String> names = getGroupsNames(memberships);
 
@@ -92,13 +87,11 @@ public class JWTWebServiceController {
      * extension (thanks https://stackoverflow.com/a/16333149/771431)
      */
     @GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
-    public void isMemberOf(@PathVariable("group") String group, Principal principal, HttpServletResponse response) throws IOException {
-
-        String userId = principal.getName();
+    public void isMemberOf(@PathVariable("group") String group, HttpServletResponse response) throws IOException {
 
         List<String> groupNames = extractGroupNames(group);
 
-        boolean isMember = membershipsDAO.isMemberOf(userId, "ROOT");
+        boolean isMember = membershipManager.isCurrentUserMemberOf("ROOT");
         if (!isMember) {
             String parentPath = ""; // starting from ROOT
             for (String groupName : groupNames) {
@@ -106,7 +99,7 @@ public class JWTWebServiceController {
                 if (optionalGroup.isPresent()) {
                     GroupEntity groupEntity = optionalGroup.get();
                     parentPath = groupEntity.getPath();
-                    isMember = membershipsDAO.isMemberOf(userId, groupEntity.getId());
+                    isMember = membershipManager.isCurrentUserMemberOf(groupEntity.getId());
                     if (isMember) {
                         break;
                     }
@@ -156,57 +149,51 @@ 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)
-    public void createGroup(@PathVariable("group") String group, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void createGroup(@PathVariable("group") String groupParam, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        String userId = principal.getName();
-
-        List<String> groupNames = extractGroupNames(group);
-        GroupEntity parent = getParentFromNames(groupNames);
-
-        String newGroupName = groupNames.get(groupNames.size() - 1);
-
-        if (permissionsService.getUserPermissionForGroup(parent, userId) != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized create group request, group_name=" + newGroupName);
-            throw new UnauthorizedException("Missing admin permission");
-        }
+        List<String> groupNames = extractGroupNames(groupParam);
 
         String leafParam = request.getParameter("leaf");
         boolean leaf = leafParam == null ? false : Boolean.valueOf(leafParam);
 
-        groupsService.addGroup(parent, newGroupName, leaf);
-        loggingDAO.logAction("Added group: parent_path=" + parent.getPath() + ", group_name=" + newGroupName);
+        GroupEntity group = groupsManager.getRoot();
+        for (int i = 0; i < groupNames.size(); i++) {
+            String name = groupNames.get(i);
+            Optional<GroupEntity> optGroup = groupsService.findGroupByParentAndName(group, name);
+            if (optGroup.isPresent()) {
+                group = optGroup.get();
+            } else {
+                group = groupsManager.createGroup(group, name, i == groupNames.size() - 1 ? leaf : false);
+            }
+        }
 
         response.setStatus(HttpServletResponse.SC_CREATED);
         try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
-            pw.println(group);
+            pw.println(groupParam);
         }
     }
 
     @PostMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
     public void addMember(@PathVariable("group") Optional<String> group, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
-
-        String userId = principal.getName();
-        membersService.verifyUserCanManageMembers(groupEntity, userId);
-
         String targetUserId = request.getParameter("user_id");
         if (targetUserId == null) {
             response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing user_id parameter");
             return;
         }
-        membersService.addMember(groupEntity.getId(), targetUserId);
-        loggingDAO.logAction("Added member, group_id=" + groupEntity.getId() + ", user_id=" + targetUserId);
-    }
 
-    @PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
-    public void addPermission(@PathVariable("group") Optional<String> groupNames, Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {
+        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
 
-        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+        membershipManager.addMember(groupEntity, targetUserId);
+    }
 
-        String userId = principal.getName();
-        permissionsService.verifyUserCanManagePermissions(groupEntity, userId);
+    @PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
+    public void addPermission(@PathVariable("group") Optional<String> groupNames, HttpServletRequest request, HttpServletResponse response) throws IOException {
 
         String targetUserId = request.getParameter("user_id");
         if (targetUserId == null) {
@@ -218,10 +205,10 @@ public class JWTWebServiceController {
             response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing permission parameter");
             return;
         }
-        Permission permission = Permission.valueOf(permissionParam);
-        permissionsService.addPermission(groupEntity, targetUserId, permission);
-        loggingDAO.logAction("Permission added, group_id=" + groupEntity.getId() + ", user_id="
-                + targetUserId + ", permission=" + permission);
+
+        GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
+
+        permissionsManager.addPermission(groupEntity, targetUserId, Permission.valueOf(permissionParam));
     }
 
     private GroupEntity getGroupFromNames(List<String> groupNames) {
@@ -231,13 +218,6 @@ public class JWTWebServiceController {
         return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1);
     }
 
-    private GroupEntity getParentFromNames(List<String> groupNames) {
-        if (groupNames.size() == 1) {
-            return getRoot();
-        }
-        return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 2);
-    }
-
     private GroupEntity getGroupFromNamesAndIndex(List<String> groupNames, int index) {
         String parentPath = ""; // starting from ROOT
         GroupEntity group = null;
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java
index 31a6f1ebe22064ded1ee60a0c2a0612aa53ea607..77469ed03cc8e42b5be01d970119611d9e9b6089 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/MembersController.java
@@ -1,7 +1,8 @@
 package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
-import it.inaf.ia2.gms.exception.UnauthorizedException;
+import it.inaf.ia2.gms.manager.MembershipManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.request.AddMemberRequest;
 import it.inaf.ia2.gms.model.request.MemberRequest;
 import it.inaf.ia2.gms.model.response.PaginatedData;
@@ -9,11 +10,8 @@ import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.RapUser;
 import it.inaf.ia2.gms.model.request.RemoveMemberRequest;
 import it.inaf.ia2.gms.model.request.TabRequest;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.MembersService;
-import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.List;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,32 +27,21 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 public class MembersController {
 
-    @Autowired
-    private SessionData session;
-
     @Autowired
     private GroupsService groupsService;
 
     @Autowired
-    private PermissionsService permissionsService;
+    private MembershipManager membershipManager;
 
     @Autowired
-    private MembersService membersService;
-
-    @Autowired
-    private LoggingDAO loggingDAO;
+    private PermissionsManager permissionsManager;
 
     @GetMapping(value = "/members", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<RapUser>> getMembersTab(TabRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        Permission currentNodePermissions = permissionsService.getUserPermissionForGroup(group, session.getUserId());
 
-        if (currentNodePermissions == Permission.TRAVERSE) {
-            throw new UnauthorizedException("You have not the permission to view members");
-        }
-
-        List<RapUser> members = membersService.getMembers(group.getId());
+        List<RapUser> members = membershipManager.getMembers(group);
         PaginatedData<RapUser> membersPanel = new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize());
 
         return ResponseEntity.ok(membersPanel);
@@ -64,33 +51,30 @@ public class MembersController {
     public ResponseEntity<PaginatedData<RapUser>> addMember(@Valid @RequestBody AddMemberRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        Permission currentUserPermission = membersService.verifyUserCanManageMembers(group, session.getUserId());
 
-        membersService.addMember(request.getGroupId(), request.getUserId());
-        loggingDAO.logAction("Added member, group_id=" + group.getId() + ", user_id=" + request.getUserId());
+        membershipManager.addMember(group, request.getUserId());
+
+        Permission currentUserPermission = permissionsManager.getCurrentUserPermission(group);
+
         if (currentUserPermission == Permission.MANAGE_MEMBERS) {
             // Automatically assign the VIEW_MEMBERS permission ("Add collaborator" feature)
-            permissionsService.addPermission(group, request.getUserId(), Permission.VIEW_MEMBERS);
-            loggingDAO.logAction("Added permission, group_id=" + group.getId() + ", user_id="
-                    + request.getUserId() + ", permission=" + Permission.VIEW_MEMBERS);
+            permissionsManager.addPermission(group, request.getUserId(), Permission.VIEW_MEMBERS);
         } else if (request.getPermission() != null) {
             // Admin users can specify a permission
-            permissionsService.addPermission(group, request.getUserId(), request.getPermission());
-            loggingDAO.logAction("Added permission, group_id=" + group.getId() + ", user_id="
-                    + request.getUserId() + ", permission=" + request.getPermission());
+            permissionsManager.addPermission(group, request.getUserId(), request.getPermission());
         }
 
-        return new ResponseEntity<>(getMembersPanel(request), HttpStatus.CREATED);
+        return new ResponseEntity<>(getMembersPanel(request, group), HttpStatus.CREATED);
     }
 
     @DeleteMapping(value = "/member", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<RapUser>> removeMember(@Valid RemoveMemberRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        Permission currentUserPermission = membersService.verifyUserCanManageMembers(group, session.getUserId());
 
-        membersService.removeMember(group.getId(), request.getUserId());
-        loggingDAO.logAction("Member removed, group_id=" + group.getId() + ", user_id=" + request.getUserId());
+        membershipManager.removeMember(group, request.getUserId());
+
+        Permission currentUserPermission = permissionsManager.getCurrentUserPermission(group);
 
         // For users having the MANAGE_MEMBERS permission, the VIEW_MEMBERS permission
         // is automatically assigned when they add a member ("Add collaborator" feature).
@@ -98,21 +82,20 @@ public class MembersController {
         // If the member permission is not VIEW_MEMBERS that means that it has been
         // changed by an ADMIN user, so we don't remove it.
         boolean removeCollaborator = currentUserPermission == Permission.MANAGE_MEMBERS
-                && permissionsService.getUserPermissionForGroup(group, request.getUserId()) == Permission.VIEW_MEMBERS;
+                && permissionsManager.getUserPermission(group, request.getUserId()) == Permission.VIEW_MEMBERS;
 
         // ADMIN users can choose if delete also the permission or not.
         boolean adminRemovePermission = currentUserPermission == Permission.ADMIN && request.isRemoveAlsoPermission();
 
         if (removeCollaborator || adminRemovePermission) {
-            permissionsService.removePermission(group, request.getUserId());
-            loggingDAO.logAction("Permission removed, group_id=" + group.getId() + ", user_id=" + request.getUserId());
+            permissionsManager.removePermission(group, request.getUserId());
         }
 
-        return ResponseEntity.ok(getMembersPanel(request));
+        return ResponseEntity.ok(getMembersPanel(request, group));
     }
 
-    private PaginatedData<RapUser> getMembersPanel(MemberRequest request) {
-        List<RapUser> members = membersService.getMembers(request.getGroupId());
+    private PaginatedData<RapUser> getMembersPanel(MemberRequest request, GroupEntity group) {
+        List<RapUser> members = membershipManager.getMembers(group);
         return new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize());
     }
 }
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 7cb9658268b7c1e470c8b08d27ca486945a507bd..1e39803e82ed3e9c63291886d8ea1a2f1dd0f371 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,7 +1,7 @@
 package it.inaf.ia2.gms.controller;
 
-import it.inaf.ia2.gms.authn.SessionData;
-import it.inaf.ia2.gms.exception.UnauthorizedException;
+import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.request.AddPermissionRequest;
 import it.inaf.ia2.gms.model.request.MemberRequest;
 import it.inaf.ia2.gms.model.response.PaginatedData;
@@ -9,10 +9,7 @@ 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.request.TabRequest;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
-import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -32,29 +29,16 @@ import org.springframework.web.bind.annotation.RestController;
 public class PermissionsController {
 
     @Autowired
-    private SessionData session;
+    private GroupsManager groupsManager;
 
     @Autowired
-    private GroupsService groupsService;
-
-    @Autowired
-    private PermissionsService permissionsService;
-
-    @Autowired
-    private LoggingDAO loggingDAO;
+    private PermissionsManager permissionsManager;
 
     @GetMapping(value = "/permissions", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<UserPermission>> getPermissionsTab(TabRequest request) {
 
-        GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        Permission currentNodePermissions = permissionsService.getUserPermissionForGroup(group, session.getUserId());
-
-        if (currentNodePermissions != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized attempt to see permissions");
-            throw new UnauthorizedException("Only administrators can see the permissions");
-        }
-
-        List<UserPermission> permissions = permissionsService.getAllPermissions(group);
+        GroupEntity group = groupsManager.getGroupById(request.getGroupId());
+        List<UserPermission> permissions = permissionsManager.getAllPermissions(group);
         PaginatedData<UserPermission> permissionsPanel = new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize());
 
         return ResponseEntity.ok(permissionsPanel);
@@ -63,10 +47,9 @@ public class PermissionsController {
     @GetMapping(value = "/permission", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<Map<String, Permission>> getUserPermission(@RequestParam("groupId") String groupId, @RequestParam("userId") String userId) {
 
-        GroupEntity group = groupsService.getGroupById(groupId);
-        permissionsService.verifyUserCanManagePermissions(group, session.getUserId());
+        GroupEntity group = groupsManager.getGroupById(groupId);
 
-        Permission permission = permissionsService.getUserPermissionForGroup(group, userId);
+        Permission permission = permissionsManager.getUserPermission(group, userId);
         if (permission == null) {
             return ResponseEntity.notFound().build();
         } else {
@@ -79,12 +62,8 @@ public class PermissionsController {
     @PostMapping(value = "/permission", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<UserPermission>> addPermission(@Valid @RequestBody AddPermissionRequest request) {
 
-        GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        permissionsService.verifyUserCanManagePermissions(group, session.getUserId());
-
-        permissionsService.addPermission(group, request.getUserId(), request.getPermission());
-        loggingDAO.logAction("Permission added, group_id=" + request.getGroupId() + ", user_id="
-                + request.getUserId() + ", permission=" + request.getPermission());
+        GroupEntity group = groupsManager.getGroupById(request.getGroupId());
+        permissionsManager.addPermission(group, request.getUserId(), request.getPermission());
 
         return new ResponseEntity<>(getPermissionsPanel(group, request), HttpStatus.CREATED);
     }
@@ -92,17 +71,14 @@ public class PermissionsController {
     @DeleteMapping(value = "/permission", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<UserPermission>> deletePermission(@Valid MemberRequest request) {
 
-        GroupEntity group = groupsService.getGroupById(request.getGroupId());
-        permissionsService.verifyUserCanManagePermissions(group, session.getUserId());
-
-        permissionsService.removePermission(group, request.getUserId());
-        loggingDAO.logAction("Permission removed, group_id=" + request.getGroupId() + ", user_id=" + request.getUserId());
+        GroupEntity group = groupsManager.getGroupById(request.getGroupId());
+        permissionsManager.removePermission(group, request.getUserId());
 
         return ResponseEntity.ok(getPermissionsPanel(group, request));
     }
 
     private PaginatedData<UserPermission> getPermissionsPanel(GroupEntity group, PaginatedModelRequest request) {
-        List<UserPermission> permissions = permissionsService.getAllPermissions(group);
+        List<UserPermission> permissions = permissionsManager.getAllPermissions(group);
         return new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize());
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/GroupsManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/GroupsManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d7ae822c6211bbdc184fd1f3478ae8fbc6bf66c
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/GroupsManager.java
@@ -0,0 +1,81 @@
+package it.inaf.ia2.gms.manager;
+
+import it.inaf.ia2.gms.exception.BadRequestException;
+import it.inaf.ia2.gms.exception.UnauthorizedException;
+import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.LoggingDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.service.GroupsService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GroupsManager {
+
+    private final GroupsService groupsService;
+    private final PermissionsManager permissionsManager;
+    private final LoggingDAO loggingDAO;
+
+    public GroupsManager(GroupsService groupsService, PermissionsManager permissionsManager, LoggingDAO loggingDAO) {
+        this.groupsService = groupsService;
+        this.permissionsManager = permissionsManager;
+        this.loggingDAO = loggingDAO;
+    }
+
+    public GroupEntity getRoot() {
+        return getGroupById(GroupsService.ROOT);
+    }
+
+    public GroupEntity getGroupById(String groupId) {
+        GroupEntity group = groupsService.getGroupById(groupId);
+        verifyUserCanReadGroup(group);
+        return group;
+    }
+
+    public GroupEntity createGroup(String parentGroupId, String childGroupName, boolean leaf) {
+        GroupEntity parent = groupsService.getGroupById(parentGroupId);
+        return createGroup(parent, childGroupName, leaf);
+    }
+
+    public GroupEntity createGroup(GroupEntity parent, String childGroupName, boolean leaf) {
+
+        if (parent.isLeaf()) {
+            throw new BadRequestException("Unable to create a sub group inside a leaf group");
+        }
+
+        verifyUserCanManageGroup(parent);
+
+        groupsService.addGroup(parent, childGroupName, leaf);
+
+        return parent;
+    }
+
+    public GroupEntity updateGroup(String groupId, String newGroupName, boolean leaf) {
+
+        GroupEntity group = groupsService.getGroupById(groupId);
+        verifyUserCanManageGroup(group);
+
+        GroupEntity updatedGroup = groupsService.updateGroup(group, newGroupName, leaf);
+
+        return groupsService.getGroupByPath(updatedGroup.getParentPath());
+    }
+
+    public GroupEntity deleteGroup(String groupId) {
+        GroupEntity group = groupsService.getGroupById(groupId);
+        verifyUserCanManageGroup(group);
+        return groupsService.deleteGroup(group);
+    }
+
+    public void verifyUserCanReadGroup(GroupEntity group) {
+        if (permissionsManager.getCurrentUserPermission(group) == null) {
+            loggingDAO.logAction("Unauthorized group management request, group_id=" + group.getId());
+            throw new UnauthorizedException("Missing permission to see this group");
+        }
+    }
+
+    private void verifyUserCanManageGroup(GroupEntity group) {
+        if (permissionsManager.getCurrentUserPermission(group) != Permission.ADMIN) {
+            loggingDAO.logAction("Unauthorized group management request, group_id=" + group.getId());
+            throw new UnauthorizedException("Missing admin permission");
+        }
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/MembershipManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/MembershipManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..61d3652489303141d224a2763d32459c6e0bb85c
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/MembershipManager.java
@@ -0,0 +1,84 @@
+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.RapUser;
+import it.inaf.ia2.gms.persistence.LoggingDAO;
+import it.inaf.ia2.gms.persistence.MembershipsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.MembershipEntity;
+import it.inaf.ia2.gms.rap.RapClient;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MembershipManager extends UserAwareComponent {
+
+    @Autowired
+    private MembershipsDAO membershipsDAO;
+
+    @Autowired
+    private PermissionsManager permissionsManager;
+
+    @Autowired
+    private RapClient rapClient;
+
+    @Autowired
+    private LoggingDAO loggingDAO;
+
+    public List<GroupEntity> getCurrentUserMemberships() {
+        return membershipsDAO.getUserMemberships(getCurrentUserId());
+    }
+
+    public boolean isCurrentUserMemberOf(String groupId) {
+        return membershipsDAO.isMemberOf(getCurrentUserId(), groupId);
+    }
+
+    public List<RapUser> getMembers(GroupEntity group) {
+
+        Permission groupPermission = permissionsManager.getCurrentUserPermission(group);
+
+        if (groupPermission == Permission.TRAVERSE) {
+            throw new UnauthorizedException("You don't have the permission to view members");
+        }
+
+        List<MembershipEntity> memberships = membershipsDAO.findByGroup(group.getId());
+
+        Set<String> userIdentifiers = memberships.stream()
+                .map(m -> m.getUserId())
+                .collect(Collectors.toSet());
+
+        return rapClient.getUsers(userIdentifiers);
+    }
+
+    public MembershipEntity addMember(GroupEntity group, String userId) {
+
+        verifyUserCanManageMembers(group);
+
+        MembershipEntity membership = new MembershipEntity();
+        membership.setGroupId(group.getId());
+        membership.setUserId(userId);
+
+        membership = membershipsDAO.addMember(membership);
+        loggingDAO.logAction("Added member, group_id=" + group.getId() + ", user_id=" + userId);
+
+        return membership;
+    }
+
+    public void removeMember(GroupEntity group, String userId) {
+        verifyUserCanManageMembers(group);
+        membershipsDAO.removeMembership(group.getId(), userId);
+        loggingDAO.logAction("Member removed, group_id=" + group.getId() + ", user_id=" + userId);
+    }
+
+    private Permission verifyUserCanManageMembers(GroupEntity group) {
+        Permission permission = permissionsManager.getCurrentUserPermission(group);
+        if (permission != Permission.ADMIN && permission != Permission.MANAGE_MEMBERS) {
+            throw new UnauthorizedException("Missing admin or manage members permissions");
+        }
+        return permission;
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..d0cb4ca99c58f12bb9e727777b8b62c4396b414b
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java
@@ -0,0 +1,92 @@
+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.RapUser;
+import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.persistence.LoggingDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.rap.RapClient;
+import it.inaf.ia2.gms.service.PermissionUtils;
+import it.inaf.ia2.gms.service.PermissionsService;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PermissionsManager extends UserAwareComponent {
+
+    private final PermissionsService permissionsService;
+    private final RapClient rapClient;
+    private final LoggingDAO loggingDAO;
+
+    @Autowired
+    public PermissionsManager(PermissionsService permissionsService, RapClient rapClient, LoggingDAO loggingDAO) {
+        this.permissionsService = permissionsService;
+        this.rapClient = rapClient;
+        this.loggingDAO = loggingDAO;
+    }
+
+    public List<UserPermission> getAllPermissions(GroupEntity group) {
+
+        verifyUserCanManagePermissions(group);
+
+        List<PermissionEntity> permissions = permissionsService.getGroupPermissions(group);
+
+        Set<String> userIdentifiers = permissions.stream()
+                .map(p -> p.getUserId())
+                .collect(Collectors.toSet());
+
+        Map<String, RapUser> users = rapClient.getUsers(userIdentifiers).stream()
+                .collect(Collectors.toMap(RapUser::getId, Function.identity()));
+
+        List<UserPermission> result = new ArrayList<>();
+
+        for (PermissionEntity p : permissions) {
+            RapUser rapUser = users.get(p.getUserId());
+            if (rapUser != null) {
+                UserPermission permission = new UserPermission();
+                permission.setPermission(p.getPermission());
+                permission.setUser(rapUser);
+                result.add(permission);
+            }
+        }
+
+        return result;
+    }
+
+    public Permission getUserPermission(GroupEntity group, String userId) {
+        verifyUserCanManagePermissions(group);
+        List<PermissionEntity> permissions = permissionsService.findUserPermissions(group, getCurrentUserId());
+        return PermissionUtils.getGroupPermission(group, permissions).orElse(null);
+    }
+
+    public PermissionEntity addPermission(GroupEntity group, String userId, Permission permission) {
+        verifyUserCanManagePermissions(group);
+        return permissionsService.addPermission(group, userId, permission);
+    }
+
+    public void removePermission(GroupEntity group, String userId) {
+        verifyUserCanManagePermissions(group);
+        permissionsService.removePermission(group, userId);
+    }
+
+    private void verifyUserCanManagePermissions(GroupEntity group) {
+        Permission permission = getCurrentUserPermission(group);
+        if (permission != Permission.ADMIN) {
+            loggingDAO.logAction("Unauthorized attempt to manage permissions");
+            throw new UnauthorizedException("Only admin users can handle permissions");
+        }
+    }
+
+    public Permission getCurrentUserPermission(GroupEntity group) {
+        List<PermissionEntity> permissions = permissionsService.findUserPermissions(group, getCurrentUserId());
+        return PermissionUtils.getGroupPermission(group, permissions).orElse(null);
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..f6af17ca33b29f359814e53043a3aa3eceb938d0
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/UserAwareComponent.java
@@ -0,0 +1,23 @@
+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();
+        }
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddMemberWsRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddMemberWsRequest.java
deleted file mode 100644
index 72667392213b3ffb4aea0533cdfedda0e9c31cc4..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddMemberWsRequest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package it.inaf.ia2.gms.model.request;
-
-import java.util.List;
-
-public class AddMemberWsRequest {
-
-    private List<String> names;
-    private String userId;
-
-    public List<String> getNames() {
-        return names;
-    }
-
-    public void setNames(List<String> names) {
-        this.names = names;
-    }
-
-    public String getUserId() {
-        return userId;
-    }
-
-    public void setUserId(String userId) {
-        this.userId = userId;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionWsRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionWsRequest.java
deleted file mode 100644
index e8a4a17be0c023ad54b168bc19bc67d8d3b8e4ab..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionWsRequest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package it.inaf.ia2.gms.model.request;
-
-import it.inaf.ia2.gms.model.Permission;
-import java.util.List;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-public class AddPermissionWsRequest {
-
-    @NotNull
-    private List<String> names;
-    @NotEmpty
-    private String userId;
-    @NotNull
-    private Permission permission;
-
-    public List<String> getNames() {
-        return names;
-    }
-
-    public void setNames(List<String> names) {
-        this.names = names;
-    }
-
-    public String getUserId() {
-        return userId;
-    }
-
-    public void setUserId(String userId) {
-        this.userId = userId;
-    }
-
-    public Permission getPermission() {
-        return permission;
-    }
-
-    public void setPermission(Permission permission) {
-        this.permission = permission;
-    }
-}
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 b236ec757a2409c704db0aa9897b2c5d1ccc0e05..b82e83d9ec6f85abc91de205c5b159de1bcf8f4e 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,5 +1,7 @@
 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;
@@ -21,6 +23,9 @@ public class LoggingDAO {
     @Autowired(required = false)
     private HttpServletRequest request;
 
+    @Autowired(required = false)
+    private SessionData sessionData;
+
     @Autowired
     public LoggingDAO(DataSource dataSource) {
         jdbcTemplate = new JdbcTemplate(dataSource);
@@ -83,9 +88,10 @@ public class LoggingDAO {
     }
 
     private String getUser(HttpServletRequest request) {
-        if (request.getUserPrincipal() != null) {
+        if (request.getUserPrincipal() != null && request.getUserPrincipal() instanceof RapPrincipal) {
             return request.getUserPrincipal().getName();
+        } else {
+            return sessionData.getUserId();
         }
-        return null;
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
index 8889c1b75b84706621e9edd529b8fbbceb5c2964..d6416c95c33fec735675bfd408feeb18baa2c7c0 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
@@ -6,6 +6,7 @@ import it.inaf.ia2.gms.model.GroupBreadcrumb;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
+import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.MembershipsDAO;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
@@ -16,22 +17,24 @@ import java.util.stream.Collectors;
 
 @Service
 public class GroupsService {
-    
+
     public static final String ROOT = "ROOT";
-    
+
     private final GroupsDAO groupsDAO;
     private final PermissionsDAO permissionsDAO;
     private final MembershipsDAO membershipsDAO;
-    
+    private final LoggingDAO loggingDAO;
+
     @Autowired
-    public GroupsService(GroupsDAO groupsDAO,
-            PermissionsDAO permissionsDAO, MembershipsDAO membershipsDAO) {
+    public GroupsService(GroupsDAO groupsDAO, PermissionsDAO permissionsDAO,
+            MembershipsDAO membershipsDAO, LoggingDAO loggingDAO) {
         this.groupsDAO = groupsDAO;
         this.permissionsDAO = permissionsDAO;
         this.membershipsDAO = membershipsDAO;
+        this.loggingDAO = loggingDAO;
         createRootIfNecessary();
     }
-    
+
     private void createRootIfNecessary() {
         if (groupsDAO.count() == 0) {
             GroupEntity root = new GroupEntity();
@@ -41,90 +44,93 @@ public class GroupsService {
             groupsDAO.createGroup(root);
         }
     }
-    
+
     public GroupEntity addGroup(GroupEntity parent, String groupName, boolean leaf) {
-        
+
         if (groupsDAO.getDirectSubGroups(parent.getPath()).stream()
                 .anyMatch(g -> g.getName().equals(groupName))) {
             throw new BadRequestException("There is already a group named " + groupName);
         }
-        
+
         String newGroupId = UUID.randomUUID().toString().replaceAll("-", "");
-        
+
         String path = parent.getPath();
         if (!path.isEmpty()) {
             path += ".";
         }
         path += newGroupId;
-        
+
         GroupEntity group = new GroupEntity();
         group.setId(newGroupId);
         group.setName(groupName);
         group.setPath(path);
         group.setLeaf(leaf);
-        
+
         groupsDAO.createGroup(group);
-        
+        loggingDAO.logAction("Added group: parent_path=" + parent.getPath() + ", group_name=" + groupName);
+
         return group;
     }
-    
-    public GroupEntity renameGroup(GroupEntity group, String newGroupName, boolean leaf) {
-        
+
+    public GroupEntity updateGroup(GroupEntity group, String newGroupName, boolean leaf) {
+
         if (groupsDAO.getDirectSubGroups(group.getPath()).stream()
                 .anyMatch(g -> g.getName().equals(newGroupName))) {
             throw new BadRequestException("There is already a group named " + newGroupName);
         }
-        
+
         group.setName(newGroupName);
         group.setLeaf(leaf);
-        
-        return groupsDAO.updateGroup(group);
+
+        GroupEntity entity = groupsDAO.updateGroup(group);
+        loggingDAO.logAction("Group updated, group_id=" + group.getId()
+                + ", new name: " + newGroupName + ", leaf: " + leaf);
+
+        return entity;
     }
-    
+
     public GroupEntity deleteGroup(GroupEntity group) {
-        
+
         if (ROOT.equals(group.getId())) {
             throw new UnauthorizedException("It is not possible to remove the ROOT");
         }
-        
+
         String parentPath = group.getParentPath();
         GroupEntity parent = groupsDAO.findGroupByPath(parentPath)
                 .orElseThrow(() -> new BadRequestException("No group found at path " + parentPath));
-        
+
         List<GroupEntity> groupsToDelete = groupsDAO.getAllChildren(group.getPath());
         groupsToDelete.add(group);
-        
+
         List<String> groupsToDeleteIds = groupsToDelete.stream()
                 .map(g -> g.getId()).collect(Collectors.toList());
-        
+
         membershipsDAO.deleteAllGroupsMembership(groupsToDeleteIds);
         permissionsDAO.deleteAllGroupsPermissions(groupsToDeleteIds);
-        
+
         for (String groupId : groupsToDeleteIds) {
             groupsDAO.deleteGroupById(groupId);
         }
-        
+
+        loggingDAO.logAction("Group deleted, group_id=" + group.getId());
+
         return parent;
     }
-    
-    public GroupEntity getRoot() {
-        return getGroupById(ROOT);
-    }
-    
+
     public GroupEntity getGroupById(String groupId) {
         return groupsDAO.findGroupById(groupId)
                 .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found"));
     }
-    
+
     public GroupEntity getGroupByPath(String path) {
         return groupsDAO.findGroupByPath(path)
                 .orElseThrow(() -> new BadRequestException("Group not found at path " + path));
     }
-    
+
     public List<GroupBreadcrumb> getBreadcrumbs(String path) {
         return groupsDAO.getBreadcrumbs(path);
     }
-    
+
     public Optional<GroupEntity> findGroupByParentAndName(GroupEntity parent, String childName) {
         return groupsDAO.findGroupByParentAndName(parent.getPath(), childName);
     }
@@ -139,7 +145,7 @@ public class GroupsService {
         // after retrieving this list it is necessary to match for the correct
         // group
         List<GroupEntity> groups = groupsDAO.findGroupsByNames(names);
-        
+
         String parentPath = "";
         GroupEntity group = null;
         for (String name : names) {
@@ -150,10 +156,10 @@ public class GroupsService {
                 parentPath = group.getPath();
             }
         }
-        
+
         return Optional.ofNullable(group);
     }
-    
+
     private GroupEntity findGroup(List<GroupEntity> groups, String parentPath, String groupName) {
         for (GroupEntity group : groups) {
             if (parentPath.equals(group.getParentPath()) && groupName.equals(group.getName())) {
@@ -162,7 +168,7 @@ public class GroupsService {
         }
         return null;
     }
-    
+
     public List<GroupEntity> searchGroups(String searchText) {
         return groupsDAO.searchGroups(searchText);
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java
deleted file mode 100644
index f46ce267033a67fc1a31cf13b3e443c2794c9910..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package it.inaf.ia2.gms.service;
-
-import it.inaf.ia2.gms.exception.UnauthorizedException;
-import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.RapUser;
-import it.inaf.ia2.gms.persistence.MembershipsDAO;
-import it.inaf.ia2.gms.persistence.model.GroupEntity;
-import it.inaf.ia2.gms.persistence.model.MembershipEntity;
-import it.inaf.ia2.gms.rap.RapClient;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service
-public class MembersService {
-
-    @Autowired
-    private MembershipsDAO membershipsDAO;
-
-    @Autowired
-    private PermissionsService permissionsService;
-
-    @Autowired
-    private RapClient rapClient;
-
-    public List<RapUser> getMembers(String groupId) {
-
-        List<MembershipEntity> memberships = membershipsDAO.findByGroup(groupId);
-
-        Set<String> userIdentifiers = memberships.stream()
-                .map(m -> m.getUserId())
-                .collect(Collectors.toSet());
-
-        return rapClient.getUsers(userIdentifiers);
-    }
-
-    public MembershipEntity addMember(String groupId, String userId) {
-
-        MembershipEntity membership = new MembershipEntity();
-        membership.setGroupId(groupId);
-        membership.setUserId(userId);
-
-        return membershipsDAO.addMember(membership);
-    }
-
-    public void removeMember(String groupId, String userId) {
-        membershipsDAO.removeMembership(groupId, userId);
-    }
-    
-    public Permission verifyUserCanManageMembers(GroupEntity group, String userId) {
-        Permission currentNodePermission = permissionsService.getUserPermissionForGroup(group, userId);
-        if (currentNodePermission != Permission.ADMIN && currentNodePermission != Permission.MANAGE_MEMBERS) {
-            throw new UnauthorizedException("Missing admin or manage members permissions");
-        }
-        return currentNodePermission;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
index 3e36f300f3e543f934e73ec4fee14ea48e32faa6..af9a942c8e0b9f715a6b78ed0170c4a95766f52e 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
@@ -1,21 +1,12 @@
 package it.inaf.ia2.gms.service;
 
-import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.RapUser;
-import it.inaf.ia2.gms.model.UserPermission;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
-import it.inaf.ia2.gms.rap.RapClient;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -23,57 +14,25 @@ import org.springframework.stereotype.Service;
 public class PermissionsService {
 
     private final PermissionsDAO permissionsDAO;
-    private final RapClient rapClient;
     private final LoggingDAO loggingDAO;
 
     @Autowired
-    public PermissionsService(PermissionsDAO permissionsDAO, RapClient rapClient, LoggingDAO loggingDAO) {
+    public PermissionsService(PermissionsDAO permissionsDAO, LoggingDAO loggingDAO) {
         this.permissionsDAO = permissionsDAO;
-        this.rapClient = rapClient;
         this.loggingDAO = loggingDAO;
     }
 
-    public List<UserPermission> getAllPermissions(GroupEntity group) {
-
-        List<PermissionEntity> permissions = permissionsDAO.getGroupsPermissions(group.getId());
-
-        Set<String> userIdentifiers = permissions.stream()
-                .map(p -> p.getUserId())
-                .collect(Collectors.toSet());
-
-        Map<String, RapUser> users = rapClient.getUsers(userIdentifiers).stream()
-                .collect(Collectors.toMap(RapUser::getId, Function.identity()));
-
-        List<UserPermission> result = new ArrayList<>();
-
-        for (PermissionEntity p : permissions) {
-            RapUser rapUser = users.get(p.getUserId());
-            if (rapUser != null) {
-                UserPermission permission = new UserPermission();
-                permission.setPermission(p.getPermission());
-                permission.setUser(rapUser);
-                result.add(permission);
-            }
-        }
-
-        return result;
-    }
-
-    public void verifyUserCanManagePermissions(GroupEntity group, String userId) {
-        Permission currentNodePermissions = getUserPermissionForGroup(group, userId);
-        if (currentNodePermissions != Permission.ADMIN) {
-            loggingDAO.logAction("Unauthorized attempt to manage permissions");
-            throw new UnauthorizedException("Only admin users can handle permissions");
-        }
+    public List<PermissionEntity> getGroupPermissions(GroupEntity group) {
+        return permissionsDAO.getGroupsPermissions(group.getId());
     }
 
-    public Permission getUserPermissionForGroup(GroupEntity group, String userId) {
-        List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId, group.getPath());
-        return PermissionUtils.getGroupPermission(group, permissions).orElse(null);
+    public List<PermissionEntity> findUserPermissions(GroupEntity group, String userId) {
+        return permissionsDAO.findUserPermissions(userId, group.getPath());
     }
 
     public void removePermission(GroupEntity group, String userId) {
         permissionsDAO.deletePermission(group.getId(), userId);
+        loggingDAO.logAction("Removed permission for " + userId + "in group " + group.getId());
     }
 
     public PermissionEntity addPermission(GroupEntity group, String userId, Permission permission) {
@@ -97,6 +56,9 @@ public class PermissionsService {
             permissionEntity.setGroupPath(group.getPath());
         }
 
-        return permissionsDAO.createOrUpdatePermission(permissionEntity);
+        permissionEntity = permissionsDAO.createOrUpdatePermission(permissionEntity);
+        loggingDAO.logAction("Added " + permission + " permission for " + userId + "in group " + group.getId());
+
+        return permissionEntity;
     }
 }
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 90e6ccbfe9aa4ad56236e4b7329a6ae5ce12e554..62651fb1112e04ace1610b0a486d9e8199503a68 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
@@ -1,5 +1,6 @@
 package it.inaf.ia2.gms.service;
 
+import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.model.response.SearchResponseItem;
@@ -28,7 +29,7 @@ public class SearchService {
     private RapClient rapClient;
 
     @Autowired
-    private GroupsService groupsService;
+    private GroupsManager groupsManager;
 
     @Autowired
     private GroupsDAO groupsDAO;
@@ -149,7 +150,7 @@ public class SearchService {
         List<UserPermission> permissions = new ArrayList<>();
 
         // Super-admin user is able to see also other user permissions
-        PermissionUtils.getGroupPermission(groupsService.getRoot(), actorPermissions).ifPresent(permission -> {
+        PermissionUtils.getGroupPermission(groupsManager.getRoot(), actorPermissions).ifPresent(permission -> {
             if (permission.equals(Permission.ADMIN)) {
 
                 Map<String, PermissionEntity> targetUserPermissions
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceControllerTest.java
deleted file mode 100644
index 8594bdb8f82f02ab3822f95fcfae75ee21d2c83d..0000000000000000000000000000000000000000
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/BasicAuthWebServiceControllerTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package it.inaf.ia2.gms.controller;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import it.inaf.ia2.gms.model.request.AddMemberWsRequest;
-import it.inaf.ia2.gms.model.request.AddPermissionWsRequest;
-import it.inaf.ia2.gms.model.Permission;
-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.GroupsService;
-import it.inaf.ia2.gms.service.MembersService;
-import it.inaf.ia2.gms.service.PermissionsService;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import static org.hamcrest.CoreMatchers.is;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.springframework.http.MediaType;
-import org.springframework.test.web.servlet.MockMvc;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-
-@RunWith(MockitoJUnitRunner.class)
-public class BasicAuthWebServiceControllerTest {
-
-    @Mock
-    private GroupsService groupsService;
-
-    @Mock
-    private MembersService membersService;
-
-    @Mock
-    private PermissionsService permissionsService;
-
-    @InjectMocks
-    private BasicAuthWebServiceController controller;
-
-    private MockMvc mockMvc;
-
-    private final ObjectMapper mapper = new ObjectMapper();
-
-    @Before
-    public void init() {
-        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
-    }
-
-    @Test
-    public void testCreateGroup() throws Exception {
-
-        GroupEntity root = new GroupEntity();
-        root.setId(GroupsService.ROOT);
-        root.setName(GroupsService.ROOT);
-        root.setPath("");
-
-        GroupEntity lbt = new GroupEntity();
-        lbt.setId("lbt_id");
-        lbt.setName("LBT");
-        lbt.setPath("lbt_id");
-
-        GroupEntity inaf = getInafGroup();
-
-        when(groupsService.getRoot()).thenReturn(root);
-        when(groupsService.findGroupByParentAndName(eq(root), eq("LBT"))).thenReturn(Optional.of(lbt));
-        when(groupsService.addGroup(eq(lbt), eq("INAF"), anyBoolean())).thenReturn(inaf);
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-
-        mockMvc.perform(post("/ws/basic/group")
-                .content(mapper.writeValueAsString(names))
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
-                .andExpect(status().isCreated())
-                .andExpect(jsonPath("$.id", is("inaf_id")))
-                .andExpect(jsonPath("$.name", is("INAF")))
-                .andExpect(jsonPath("$.path", is("lbt_id.inaf_id")))
-                .andExpect(jsonPath("$.parentPath", is("lbt_id")));
-    }
-
-    @Test
-    public void testDeleteGroupByPath() throws Exception {
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-
-        GroupEntity inaf = getInafGroup();
-
-        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
-
-        mockMvc.perform(delete("/ws/basic/group?names=LBT&names=INAF"))
-                .andExpect(status().isNoContent());
-
-        verify(groupsService, times(1)).deleteGroup(eq(inaf));
-    }
-
-    @Test
-    public void testAddMember() throws Exception {
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-
-        GroupEntity inaf = getInafGroup();
-
-        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
-
-        AddMemberWsRequest request = new AddMemberWsRequest();
-        request.setNames(names);
-        request.setUserId("user_id");
-
-        MembershipEntity membership = new MembershipEntity();
-        membership.setGroupId(inaf.getId());
-        membership.setUserId(request.getUserId());
-
-        when(membersService.addMember(eq(inaf.getId()), eq(request.getUserId())))
-                .thenReturn(membership);
-
-        mockMvc.perform(post("/ws/basic/member")
-                .content(mapper.writeValueAsString(request))
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
-                .andExpect(status().isCreated())
-                .andExpect(jsonPath("$.groupId", is(inaf.getId())))
-                .andExpect(jsonPath("$.userId", is(request.getUserId())));
-    }
-
-    @Test
-    public void testRemoveMember() throws Exception {
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-
-        GroupEntity inaf = getInafGroup();
-
-        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
-
-        mockMvc.perform(delete("/ws/basic/member?names=LBT&names=INAF&userId=user_id"))
-                .andExpect(status().isNoContent());
-
-        verify(membersService, times(1)).removeMember(eq(inaf.getId()), eq("user_id"));
-    }
-
-    @Test
-    public void testAddPermission() throws Exception {
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-
-        AddPermissionWsRequest request = new AddPermissionWsRequest();
-        request.setNames(names);
-        request.setUserId("user_id");
-        request.setPermission(Permission.ADMIN);
-
-        GroupEntity inaf = getInafGroup();
-        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
-
-        PermissionEntity permissionEntity = new PermissionEntity();
-        permissionEntity.setGroupId(inaf.getId());
-        permissionEntity.setUserId(request.getUserId());
-        permissionEntity.setPermission(request.getPermission());
-        permissionEntity.setGroupPath(inaf.getPath());
-
-        when(permissionsService.addPermission(eq(inaf), eq(request.getUserId()),
-                eq(request.getPermission()))).thenReturn(permissionEntity);
-
-        mockMvc.perform(post("/ws/basic/permission")
-                .content(mapper.writeValueAsString(request))
-                .contentType(MediaType.APPLICATION_JSON_UTF8))
-                .andExpect(status().isCreated())
-                .andExpect(jsonPath("$.groupId", is(inaf.getId())))
-                .andExpect(jsonPath("$.userId", is(request.getUserId())))
-                .andExpect(jsonPath("$.permission", is("ADMIN")));
-
-        verify(permissionsService, times(1))
-                .addPermission(eq(inaf), eq(request.getUserId()), eq(request.getPermission()));
-    }
-
-    @Test
-    public void testRemovePermission() throws Exception {
-
-        List<String> names = Arrays.asList("LBT", "INAF");
-        GroupEntity inaf = getInafGroup();
-        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
-
-        mockMvc.perform(delete("/ws/basic/permission?names=LBT&names=INAF&userId=user_id&permission=ADMIN"))
-                .andExpect(status().isNoContent());
-
-        verify(permissionsService, times(1)).removePermission(eq(inaf), eq("user_id"));
-    }
-
-    private GroupEntity getInafGroup() {
-        GroupEntity inaf = new GroupEntity();
-        inaf.setId("inaf_id");
-        inaf.setName("INAF");
-        inaf.setPath("lbt_id.inaf_id");
-        return inaf;
-    }
-}
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 b7da36ad0a6e0777bb72f900ad741572027f5416..ee269d4026351ab2febed161b49410e56dad1701 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
@@ -2,16 +2,16 @@ package it.inaf.ia2.gms.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.AddGroupRequest;
 import it.inaf.ia2.gms.model.response.GroupsTabResponse;
 import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
-import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
-import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.ArrayList;
 import java.util.List;
 import static org.hamcrest.CoreMatchers.is;
@@ -38,6 +38,9 @@ public class GroupsControllerTest {
     @Mock
     private GroupsTabResponseBuilder groupsTabResponseBuilder;
 
+    @Mock
+    private GroupsManager groupsManager;
+
     @Mock
     private GroupsService groupsService;
 
@@ -45,11 +48,11 @@ public class GroupsControllerTest {
     private SessionData session;
 
     @Mock
-    private PermissionsService permissionsService;
+    private PermissionsManager permissionsManager;
 
     @Mock
     private GroupsTreeBuilder groupsTreeBuilder;
-    
+
     @Mock
     private LoggingDAO loggingDAO;
 
@@ -92,11 +95,6 @@ public class GroupsControllerTest {
         request.setPaginatorPage(1);
         request.setPaginatorPageSize(10);
 
-        GroupEntity parent = new GroupEntity();
-        when(groupsService.getGroupById(any())).thenReturn(parent);
-
-        when(permissionsService.getUserPermissionForGroup(any(), any())).thenReturn(Permission.ADMIN);
-
         List<GroupNode> nodes = new ArrayList<>();
         PaginatedData<GroupNode> paginatedData = new PaginatedData<>(nodes, 1, 10);
         when(groupsTreeBuilder.listSubGroups(any(), any(), any())).thenReturn(paginatedData);
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 07151d13506c5737cd5d658694314d492b75f90b..c4ac899f69c721adb51db8354ef17162370d05e8 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,8 @@
 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.PermissionsManager;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.GroupsRequest;
@@ -9,7 +11,6 @@ import it.inaf.ia2.gms.model.response.PaginatedData;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
-import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.ArrayList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -28,11 +29,14 @@ public class GroupsTabResponseBuilderTest {
     @Mock
     private SessionData session;
 
+    @Mock
+    private GroupsManager groupsManager;
+    
     @Mock
     private GroupsService groupsService;
 
     @Mock
-    private PermissionsService permissionsService;
+    private PermissionsManager permissionsManager;
 
     @Mock
     private GroupsTreeBuilder groupsTreeBuilder;
@@ -52,7 +56,7 @@ public class GroupsTabResponseBuilderTest {
 
         when(groupsService.getGroupById(eq("ROOT"))).thenReturn(root);
 
-        when(permissionsService.getUserPermissionForGroup(eq(root), eq("admin_id")))
+        when(permissionsManager.getCurrentUserPermission(eq(root)))
                 .thenReturn(Permission.ADMIN);
 
         PaginatedData<GroupNode> groupsPanel = new PaginatedData<>(new ArrayList<>(), 1, 10);
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 918cbe3979f96f47408e5ff1eb74759a84c9757a..257fd5f139da385261db613332b70deb5a41bebe 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
@@ -1,21 +1,37 @@
 package it.inaf.ia2.gms.controller;
 
+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.persistence.GroupsDAO;
-import it.inaf.ia2.gms.persistence.MembershipsDAO;
+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.GroupsService;
+import it.inaf.ia2.gms.service.JoinService;
 import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -24,11 +40,26 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 public class JWTWebServiceControllerTest {
 
     @Mock
-    private MembershipsDAO membershipsDAO;
+    private JoinService joinService;
 
     @Mock
     private GroupsDAO groupsDAO;
 
+    @Mock
+    private GroupsManager groupsManager;
+
+    @Mock
+    private GroupsService groupsService;
+
+    @Mock
+    private MembershipManager membershipManager;
+
+    @Mock
+    private PermissionsManager permissionsManager;
+
+    @Mock
+    private PermissionsDAO permissionsDAO;
+
     @InjectMocks
     private JWTWebServiceController controller;
 
@@ -41,9 +72,16 @@ public class JWTWebServiceControllerTest {
 
     private MockMvc mockMvc;
 
+    private GroupEntity root;
+    private GroupEntity lbt;
+    private GroupEntity inaf;
+
     @Before
     public void init() {
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+        root = getRoot();
+        lbt = getLbtGroup();
+        inaf = getInafGroup();
     }
 
     @Test
@@ -71,7 +109,7 @@ public class JWTWebServiceControllerTest {
         when(groupsDAO.findGroupByParentAndName(eq("1.2"), eq("subsubgroup")))
                 .thenReturn(Optional.of(group3));
 
-        when(membershipsDAO.isMemberOf(anyString(), eq(group3.getId()))).thenReturn(true);
+        when(membershipManager.isCurrentUserMemberOf(eq(group3.getId()))).thenReturn(true);
 
         String group = "group\\.1.subgroup.subsubgroup";
 
@@ -79,4 +117,132 @@ public class JWTWebServiceControllerTest {
                 .andExpect(status().isOk())
                 .andExpect(content().string(group + "\n"));
     }
+
+    @Test
+    public void testCreateGroup() throws Exception {
+
+        when(groupsManager.getRoot()).thenReturn(root);
+
+        mockMvc.perform(post("/ws/jwt/group")
+                .param("leaf", "false")
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED))
+                .andExpect(status().isCreated());
+    }
+
+    @Test
+    @Ignore
+    public void testDeleteGroupByPath() throws Exception {
+
+        List<String> names = Arrays.asList("LBT", "INAF");
+
+        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
+
+        mockMvc.perform(delete("/ws/jwt/group/LBT.INAF"))
+                .andExpect(status().isNoContent());
+
+        verify(groupsService, times(1)).deleteGroup(eq(inaf));
+    }
+
+    @Test
+    public void testAddMember() throws Exception {
+
+        when(groupsDAO.findGroupByParentAndName("", "LBT")).thenReturn(Optional.of(lbt));
+        when(groupsDAO.findGroupByParentAndName("lbt_id", "INAF")).thenReturn(Optional.of(inaf));
+
+        String userId = "target_user";
+
+        MembershipEntity membership = new MembershipEntity();
+        membership.setGroupId(inaf.getId());
+        membership.setUserId(userId);
+
+        when(membershipManager.addMember(eq(inaf), eq(userId)))
+                .thenReturn(membership);
+
+        mockMvc.perform(post("/ws/jwt/membership/LBT.INAF")
+                .param("user_id", userId)
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    @Ignore
+    public void testRemoveMember() throws Exception {
+
+        List<String> names = Arrays.asList("LBT", "INAF");
+
+        GroupEntity inaf = getInafGroup();
+
+        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
+
+        mockMvc.perform(delete("/ws/jwt/membership/LBT.INAF?userId=user_id"))
+                .andExpect(status().isNoContent());
+
+        verify(membershipManager, times(1)).removeMember(eq(inaf), eq("user_id"));
+    }
+
+    @Test
+    public void testAddPermission() throws Exception {
+
+        String userId = "target_user";
+        Permission permission = Permission.ADMIN;
+
+        when(groupsDAO.findGroupByParentAndName("", "LBT")).thenReturn(Optional.of(lbt));
+        when(groupsDAO.findGroupByParentAndName("lbt_id", "INAF")).thenReturn(Optional.of(inaf));
+
+        PermissionEntity permissionEntity = new PermissionEntity();
+        permissionEntity.setGroupId(inaf.getId());
+        permissionEntity.setUserId(userId);
+        permissionEntity.setPermission(permission);
+        permissionEntity.setGroupPath(inaf.getPath());
+
+        when(permissionsManager.addPermission(eq(inaf), eq(userId),
+                eq(permission))).thenReturn(permissionEntity);
+
+        mockMvc.perform(post("/ws/jwt/permission/LBT.INAF")
+                .param("user_id", userId)
+                .param("permission", permission.toString())
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED))
+                .andExpect(status().isOk());
+
+        verify(permissionsManager, times(1))
+                .addPermission(eq(inaf), eq(userId), eq(permission));
+    }
+
+    @Test
+    @Ignore
+    public void testRemovePermission() throws Exception {
+
+        List<String> names = Arrays.asList("LBT", "INAF");
+        GroupEntity inaf = getInafGroup();
+        when(groupsService.findGroupByNames(names)).thenReturn(Optional.of(inaf));
+
+        mockMvc.perform(delete("/ws/jwt/permission?LBT.INAF?userId=user_id&permission=ADMIN"))
+                .andExpect(status().isNoContent());
+
+        verify(permissionsManager, times(1)).removePermission(eq(inaf), eq("user_id"));
+    }
+
+    private GroupEntity getRoot() {
+        GroupEntity root = new GroupEntity();
+        root.setId(GroupsService.ROOT);
+        root.setName(GroupsService.ROOT);
+        root.setPath("");
+        return root;
+    }
+
+    private GroupEntity getLbtGroup() {
+        GroupEntity lbt = new GroupEntity();
+        lbt.setId("lbt_id");
+        lbt.setName("LBT");
+        lbt.setPath("lbt_id");
+        return lbt;
+    }
+
+    private GroupEntity getInafGroup() {
+        GroupEntity inaf = new GroupEntity();
+        inaf.setId("inaf_id");
+        inaf.setName("INAF");
+        inaf.setPath("lbt_id.inaf_id");
+        return inaf;
+    }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/MembersControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/MembersControllerTest.java
index 8774b951743f5c9db9e354ee9ab0229da68f743f..fdf853c33e1ddba6b17e6e99a19716c6c784e110 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/MembersControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/MembersControllerTest.java
@@ -2,17 +2,17 @@ package it.inaf.ia2.gms.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.manager.MembershipManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.request.AddMemberRequest;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.MembersService;
-import it.inaf.ia2.gms.service.PermissionsService;
 import static org.hamcrest.CoreMatchers.is;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -31,20 +31,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 @RunWith(MockitoJUnitRunner.class)
 public class MembersControllerTest {
 
-    @Mock
-    private SessionData session;
-
     @Mock
     private GroupsService groupsService;
 
     @Mock
-    private PermissionsService permissionsService;
+    private PermissionsManager permissionsManager;
 
     @Mock
-    private MembersService membersService;
-
-    @Mock
-    private LoggingDAO loggingDAO;
+    private MembershipManager membershipManager;
 
     @InjectMocks
     private MembersController controller;
@@ -57,15 +51,13 @@ public class MembersControllerTest {
     public void init() {
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
 
-        when(session.getUserId()).thenReturn("admin_id");
-
         GroupEntity group = new GroupEntity();
         group.setId("group_id");
         group.setName("GroupName");
         group.setPath("parent_id.group_id");
 
         when(groupsService.getGroupById(eq("group_id"))).thenReturn(group);
-        when(permissionsService.getUserPermissionForGroup(eq(group), eq("admin_id"))).thenReturn(Permission.ADMIN);
+        when(permissionsManager.getCurrentUserPermission(eq(group))).thenReturn(Permission.ADMIN);
     }
 
     @Test
@@ -83,7 +75,7 @@ public class MembersControllerTest {
                 .andExpect(status().isCreated())
                 .andExpect(jsonPath("$.currentPage", is(1)));
 
-        verify(membersService, times(1)).addMember(eq("group_id"), eq("user_id"));
+        verify(membershipManager, times(1)).addMember(any(), eq("user_id"));
     }
 
     @Test
@@ -93,6 +85,6 @@ public class MembersControllerTest {
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.currentPage", is(1)));
 
-        verify(membersService, times(1)).removeMember(eq("group_id"), eq("user_id"));
+        verify(membershipManager, times(1)).removeMember(any(), eq("user_id"));
     }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/PermissionsControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/PermissionsControllerTest.java
index 20e469fd6d7f9f75bf2a92c702aff544098381d3..d4c2f7ca060683c41127a822217d42d46e30264c 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/PermissionsControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/PermissionsControllerTest.java
@@ -1,13 +1,11 @@
 package it.inaf.ia2.gms.controller;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.AddPermissionRequest;
-import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
-import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.PermissionsService;
 import static org.hamcrest.CoreMatchers.is;
 import org.junit.Before;
 import org.junit.Test;
@@ -32,17 +30,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 public class PermissionsControllerTest {
 
     @Mock
-    private SessionData session;
+    private GroupsManager groupsManager;
 
     @Mock
-    private GroupsService groupsService;
+    private PermissionsManager permissionsManager;
 
-    @Mock
-    private PermissionsService permissionsService;
-
-    @Mock
-    private LoggingDAO loggingDAO;
-    
     @InjectMocks
     private PermissionsController controller;
 
@@ -56,21 +48,18 @@ public class PermissionsControllerTest {
     public void init() {
         mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
 
-        when(session.getUserId()).thenReturn("admin_id");
-
         group = new GroupEntity();
         group.setId("group_id");
         group.setName("GroupName");
         group.setPath("parent_id.group_id");
 
-        when(groupsService.getGroupById(eq("group_id"))).thenReturn(group);
-        when(permissionsService.getUserPermissionForGroup(eq(group), eq("admin_id"))).thenReturn(Permission.ADMIN);
+        when(groupsManager.getGroupById(eq("group_id"))).thenReturn(group);
     }
 
     @Test
     public void testGetPermission() throws Exception {
 
-        when(permissionsService.getUserPermissionForGroup(eq(group), eq("user_id"))).thenReturn(Permission.MANAGE_MEMBERS);
+        when(permissionsManager.getUserPermission(eq(group), eq("user_id"))).thenReturn(Permission.MANAGE_MEMBERS);
 
         mockMvc.perform(get("/permission?groupId=group_id&userId=user_id")
                 .contentType(MediaType.APPLICATION_JSON_UTF8))
@@ -102,7 +91,7 @@ public class PermissionsControllerTest {
                 .andExpect(status().isCreated())
                 .andExpect(jsonPath("$.currentPage", is(1)));
 
-        verify(permissionsService, times(1)).addPermission(eq(group), eq("user_id"), eq(Permission.ADMIN));
+        verify(permissionsManager, times(1)).addPermission(eq(group), eq("user_id"), eq(Permission.ADMIN));
     }
 
     @Test
@@ -112,6 +101,6 @@ public class PermissionsControllerTest {
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.currentPage", is(1)));
 
-        verify(permissionsService, times(1)).removePermission(eq(group), eq("user_id"));
+        verify(permissionsManager, times(1)).removePermission(eq(group), eq("user_id"));
     }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
similarity index 76%
rename from gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java
rename to gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
index 5860154e3f1b0e45a42c39d2cfe4137b50f15b7e..e5508ee7a2a6fd9d19bf7ebe34a0178095f54c88 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/manager/PermissionsManagerIntegrationTest.java
@@ -1,6 +1,7 @@
-package it.inaf.ia2.gms.service;
+package it.inaf.ia2.gms.manager;
 
 import it.inaf.ia2.gms.DataSourceConfig;
+import it.inaf.ia2.gms.authn.RapPrincipal;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.RapUser;
 import it.inaf.ia2.gms.model.UserPermission;
@@ -10,8 +11,12 @@ 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.gms.rap.RapClient;
+import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
 import javax.sql.DataSource;
 import static org.junit.Assert.assertEquals;
 import org.junit.Test;
@@ -25,7 +30,7 @@ import org.springframework.test.context.junit4.SpringRunner;
 
 @RunWith(SpringRunner.class)
 @ContextConfiguration(classes = DataSourceConfig.class)
-public class PermissionsServiceIntegrationTest {
+public class PermissionsManagerIntegrationTest {
 
     private static final String USER_ID = "USER_ID";
 
@@ -38,6 +43,9 @@ public class PermissionsServiceIntegrationTest {
     @Autowired
     private DataSource dataSource;
 
+    @MockBean
+    private HttpServletRequest request;
+
     @Test
     public void permissionsRetrievalTest() {
 
@@ -49,12 +57,14 @@ public class PermissionsServiceIntegrationTest {
         rapUser.setId(USER_ID);
         when(rapClient.getUsers(any())).thenReturn(Collections.singletonList(rapUser));
 
-        PermissionsService permissionsService = new PermissionsService(permissionsDAO, rapClient, loggingDAO);
+        PermissionsService permissionsService = new PermissionsService(permissionsDAO, loggingDAO);
+        PermissionsManager permissionsManager = new PermissionsManager(permissionsService, rapClient, loggingDAO);
+        UserAwareComponentTestUtil.setUser(permissionsManager, USER_ID);
 
         // Create root
         GroupEntity root = new GroupEntity();
-        root.setId(GroupsService.ROOT);
-        root.setName(GroupsService.ROOT);
+        root.setId("ROOT");
+        root.setName("Root");
         root.setPath("");
         root = groupsDAO.createGroup(root);
 
@@ -65,12 +75,12 @@ public class PermissionsServiceIntegrationTest {
         superAdminPermission.setGroupPath(root.getPath());
         permissionsDAO.createOrUpdatePermission(superAdminPermission);
 
-        List<UserPermission> permissions = permissionsService.getAllPermissions(root);
+        List<UserPermission> permissions = permissionsManager.getAllPermissions(root);
 
         assertEquals(1, permissions.size());
         assertEquals(Permission.ADMIN, permissions.get(0).getPermission());
         assertEquals(USER_ID, permissions.get(0).getUser().getId());
 
-        permissionsService.removePermission(root, USER_ID);
+        permissionsManager.removePermission(root, USER_ID);
     }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/manager/UserAwareComponentTestUtil.java b/gms/src/test/java/it/inaf/ia2/gms/manager/UserAwareComponentTestUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..79ebcdaad0c8b4e617a11c75d84c2b6a4675a8d5
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/manager/UserAwareComponentTestUtil.java
@@ -0,0 +1,21 @@
+package it.inaf.ia2.gms.manager;
+
+import it.inaf.ia2.gms.authn.RapPrincipal;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class UserAwareComponentTestUtil {
+
+    public static void setUser(UserAwareComponent component, String userId) {
+        Map<String, Object> jwtClaims = new HashMap<>();
+        jwtClaims.put("sub", userId);
+        RapPrincipal principal = new RapPrincipal(jwtClaims);
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getUserPrincipal()).thenReturn(principal);
+        ReflectionTestUtils.setField(component, "request", request);
+    }
+}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
index 7db372b66cbe284325bdaefea1185bbc275e373c..21d437e0857c7fae6cc368cb4f3e74749a4bce2f 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
@@ -1,13 +1,18 @@
 package it.inaf.ia2.gms.persistence;
 
 import it.inaf.ia2.gms.DataSourceConfig;
+import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.PermissionsManager;
+import it.inaf.ia2.gms.manager.UserAwareComponentTestUtil;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.GroupsRequest;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.rap.RapClient;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
+import it.inaf.ia2.gms.service.PermissionsService;
 import java.util.List;
 import javax.sql.DataSource;
 import static org.junit.Assert.assertEquals;
@@ -16,6 +21,7 @@ import static org.junit.Assert.assertTrue;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
 
@@ -23,20 +29,30 @@ import org.springframework.test.context.junit4.SpringRunner;
 @ContextConfiguration(classes = DataSourceConfig.class)
 public class NestedGroupsIntegrationTest {
 
+    @MockBean
+    private LoggingDAO loggingDAO;
+
+    @MockBean
+    private RapClient rapClient;
+
     @Autowired
     private DataSource dataSource;
 
     @Test
     public void testNestedGroupRetrieval() {
 
+        String userId = "USER_ID";
+
         GroupsDAO groupsDAO = new GroupsDAO(dataSource);
         PermissionsDAO permissionsDAO = new PermissionsDAO(dataSource);
         MembershipsDAO membershipsDAO = new MembershipsDAO(dataSource);
-        GroupsService groupsService = new GroupsService(groupsDAO, permissionsDAO, membershipsDAO);
+        PermissionsService permissionsService = new PermissionsService(permissionsDAO, loggingDAO);
+        PermissionsManager permissionsManager = new PermissionsManager(permissionsService, rapClient, loggingDAO);
+        UserAwareComponentTestUtil.setUser(permissionsManager, userId);
+        GroupsService groupsService = new GroupsService(groupsDAO, permissionsDAO, membershipsDAO, loggingDAO);
+        GroupsManager groupsManager = new GroupsManager(groupsService, permissionsManager, loggingDAO);
         GroupsTreeBuilder groupsTreeBuilder = new GroupsTreeBuilder(groupsDAO, permissionsDAO);
 
-        String userId = "USER_ID";
-
         // Test super admin
         PermissionEntity superAdminPermission = new PermissionEntity();
         superAdminPermission.setUserId(userId);
@@ -46,7 +62,7 @@ public class NestedGroupsIntegrationTest {
         permissionsDAO.createOrUpdatePermission(superAdminPermission);
 
         // Setup groups
-        GroupEntity root = groupsService.getRoot();
+        GroupEntity root = groupsManager.getRoot();
         GroupEntity lbt = groupsService.addGroup(root, "LBT", false);
         GroupEntity tng = groupsService.addGroup(root, "TNG", false);
         GroupEntity radio = groupsService.addGroup(root, "Radio", false);
diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceTest.java b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceTest.java
index 36e7010213dd62596304e8cdb1af100b2d5c4699..ad03f478d009ebbeb9b3d01c9cdc39a91ee4aa22 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceTest.java
@@ -1,6 +1,7 @@
 package it.inaf.ia2.gms.service;
 
 import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.LoggingDAO;
 import it.inaf.ia2.gms.persistence.PermissionsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.persistence.model.PermissionEntity;
@@ -25,6 +26,9 @@ public class PermissionsServiceTest {
     @Mock
     private PermissionsDAO permissionsDAO;
 
+    @Mock
+    private LoggingDAO loggingDAO;
+
     @InjectMocks
     private PermissionsService permissionsService;
 
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 b3ba18ce4ace7c2a5160c9a0b8a2b5cd748d6587..098d24c62d959296f238b88c12626b38b5c93930 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
@@ -1,5 +1,6 @@
 package it.inaf.ia2.gms.service;
 
+import it.inaf.ia2.gms.manager.GroupsManager;
 import it.inaf.ia2.gms.model.Identity;
 import it.inaf.ia2.gms.model.IdentityType;
 import it.inaf.ia2.gms.model.Permission;
@@ -40,6 +41,9 @@ public class SearchServiceTest {
     @Mock
     private GroupsService groupsService;
 
+    @Mock
+    private GroupsManager groupsManager;
+
     @Mock
     private GroupsDAO groupsDAO;
 
@@ -162,7 +166,7 @@ public class SearchServiceTest {
         root.setId("ROOT");
         root.setName("Root");
         root.setPath("");
-        when(groupsService.getRoot()).thenReturn(root);
+        when(groupsManager.getRoot()).thenReturn(root);
 
         UserSearchResponse response = searchService.getUserSearchResult("admin_id", "target_id");
         assertEquals(1, response.getGroups().size());