diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java
index 66f86bcd9e2ba89e3548ac0e06986747d0d52bfd..71c9da2c349c61dec4ec7d359bf91a55538a154c 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java
@@ -4,17 +4,33 @@ import java.util.Collection;
 import java.util.Map;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 
 public class CustomAuthenticationData extends UsernamePasswordAuthenticationToken {
 
     private final Map<String, Object> attributes;
+    private final OAuth2AccessToken accessToken;
+    private final OAuth2RefreshToken refreshToken;
 
-    public CustomAuthenticationData(String username, Map<String, Object> attributes, Collection<? extends GrantedAuthority> authorities) {
+    public CustomAuthenticationData(String username, Map<String, Object> attributes,
+            Collection<? extends GrantedAuthority> authorities,
+            OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) {
         super(username, "N/A", authorities);
         this.attributes = attributes;
+        this.accessToken = accessToken;
+        this.refreshToken = refreshToken;
     }
 
     public Map<String, Object> getAttributes() {
         return attributes;
     }
+
+    public OAuth2AccessToken getAccessToken() {
+        return accessToken;
+    }
+
+    public OAuth2RefreshToken getRefreshToken() {
+        return refreshToken;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java
index d3decd339b8cf51a1d9fa84c0006b68800ba873b..7d0bc239c842bc738fe100c86cc38e5aec3fab34 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java
@@ -6,6 +6,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
 import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
 
@@ -25,12 +26,12 @@ public class CustomIdTokenConverter extends DefaultUserAuthenticationConverter {
         OAuth2AccessToken token = jwkTokenStore.readAccessToken(idToken);
 
         Map<String, Object> claims = token.getAdditionalInformation();
-        //OAuth2RefreshToken refreshToken = token.getRefreshToken();
+        OAuth2RefreshToken refreshToken = token.getRefreshToken();
 
         String principal = (String) claims.get("sub");
 
         List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
 
-        return new CustomAuthenticationData(principal, claims, authorities);
+        return new CustomAuthenticationData(principal, claims, authorities, token, refreshToken);
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java b/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java
index 6e0b895164d8a49eb48e5103131813298b7b9d69..190aaf038f5bb56c593a14442d9c9455e4518c94 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/LoginController.java
@@ -1,6 +1,7 @@
 package it.inaf.ia2.gms.authn;
 
 import java.security.Principal;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -8,6 +9,9 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 public class LoginController {
 
+    @Autowired
+    private SessionData sessionData;
+
     @GetMapping("/login")
     public Principal start(Principal principal) {
         return principal;
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..130505eda977da570488e320b1d32dcf8530abaf
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
@@ -0,0 +1,16 @@
+package it.inaf.ia2.gms.authn;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@Order(1000)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.csrf().disable();
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a82085919f2548f299634a278583492fb18e3bc
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java
@@ -0,0 +1,41 @@
+package it.inaf.ia2.gms.authn;
+
+import it.inaf.ia2.gms.persistence.UsersRepository;
+import it.inaf.ia2.gms.persistence.model.User;
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.annotation.SessionScope;
+
+@Component
+@SessionScope
+public class SessionData {
+
+    @Autowired
+    private HttpServletRequest request;
+
+    @Autowired
+    private UsersRepository usersRepository;
+
+    private String userId;
+
+    @PostConstruct
+    public void init() {
+        CustomAuthenticationData authn = (CustomAuthenticationData) ((OAuth2Authentication) request.getUserPrincipal()).getUserAuthentication();
+        userId = (String) authn.getPrincipal();
+        //accessToken = (String) ((CustomAuthenticationData) request.getUserPrincipal()).getAttributes().get("access_token");
+        //System.out.println("SessionData initialized: " + accessToken);
+
+        if (!usersRepository.findById(userId).isPresent()) {
+            User user = new User();
+            user.setId(userId);
+            usersRepository.save(user);
+        }
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..7586fd84e5d04aabc3cd9bd2950f9c6f0c476e4b
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
@@ -0,0 +1,47 @@
+package it.inaf.ia2.gms.controller;
+
+import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.model.CreateGroupRequest;
+import it.inaf.ia2.gms.model.GroupsModelRequest;
+import it.inaf.ia2.gms.model.GroupsModelResponse;
+import it.inaf.ia2.gms.persistence.model.Group;
+import it.inaf.ia2.gms.persistence.model.User;
+import it.inaf.ia2.gms.service.GroupsModelService;
+import it.inaf.ia2.gms.service.GroupsService;
+import it.inaf.ia2.gms.service.UsersService;
+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.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class GroupsController {
+
+    @Autowired
+    private SessionData session;
+
+    private UsersService usersService;
+
+    @Autowired
+    private GroupsModelService groupsModelService;
+
+    @Autowired
+    private GroupsService groupsService;
+
+    @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+    public GroupsModelResponse getGroupsModelResponse(@Valid @RequestBody GroupsModelRequest groupsModelRequest) {
+        return groupsModelService.getGroupsModel(groupsModelRequest, session.getUserId());
+    }
+
+    @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+    public ResponseEntity<String> createGroup(@Valid @RequestBody CreateGroupRequest createGroupRequest) {
+        User user = usersService.getUserById(session.getUserId());
+        Group group = groupsService.addGroup(createGroupRequest.getParentGroupId(), createGroupRequest.getGroupName(), user);
+        return new ResponseEntity<>(group.getId(), HttpStatus.CREATED);
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java
deleted file mode 100644
index 56a344ea4c8f7cf51e246f29478701c96aa73999..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package it.inaf.ia2.gms.controller;
-
-import it.inaf.ia2.gms.controller.model.GroupsModelRequest;
-import it.inaf.ia2.gms.controller.model.GroupsModelResponse;
-import javax.validation.Valid;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class GroupsModelController {
-
-    @GetMapping
-    public GroupsModelResponse getGroupsModelResponse(@Valid @RequestBody GroupsModelRequest groupsModelRequest) {
-        return null;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java
deleted file mode 100644
index 8a5c00a1ab1ea02b3c45367c9da177dd33aa0977..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package it.inaf.ia2.gms.controller.model;
-
-public class GroupsModelRequest extends BaseModelRequest {
-
-    private String groupId;
-
-    public String getGroupId() {
-        return groupId;
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java
deleted file mode 100644
index 8bc0046cd5a6bd04988a614a481aca27fb1dc982..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package it.inaf.ia2.gms.controller.model;
-
-public class GroupsModelResponse extends BaseModelResponse {
-
-    
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b07d8c30bb0f9c9f5df74611726acfc0f781dcc
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java
@@ -0,0 +1,8 @@
+package it.inaf.ia2.gms.exception;
+
+public class UnauthorizedException extends RuntimeException {
+
+    public UnauthorizedException(String message) {
+        super(message);
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java
similarity index 82%
rename from gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java
rename to gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java
index 3e93b83aed42181fe0e2c9a659221fbb04dc7209..1330e697373ab887171e3839102c2d9df31d2f72 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java
@@ -1,4 +1,4 @@
-package it.inaf.ia2.gms.controller.model;
+package it.inaf.ia2.gms.model;
 
 public abstract class BaseModelRequest {
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java
similarity index 89%
rename from gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java
rename to gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java
index 98c6b2a09062713a16b46a2b69fb5a9c003f6e32..b8999c5fdaf278aa89e49283198f84b00290e3c4 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelResponse.java
@@ -1,4 +1,4 @@
-package it.inaf.ia2.gms.controller.model;
+package it.inaf.ia2.gms.model;
 
 public abstract class BaseModelResponse {
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a62c852f9186227f7ebaa2a43abc3875eb3a8d2f
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.model;
+
+public class CreateGroupRequest {
+
+    private String parentGroupId;
+    private String groupName;
+
+    public String getParentGroupId() {
+        return parentGroupId;
+    }
+
+    public void setParentGroupId(String parentGroupId) {
+        this.parentGroupId = parentGroupId;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c16742ba9e1e3b94d7a230a6a8b300b801f5a74
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupBreadcrumb.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.model;
+
+public class GroupBreadcrumb {
+
+    private String groupId;
+    private String groupName;
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ca71a1e443eb6815c707eb81837e026d6b15e57
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
@@ -0,0 +1,83 @@
+package it.inaf.ia2.gms.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class GroupNode {
+
+    private String groupId;
+    private String groupName;
+    private Set<Permission> permissions;
+    private boolean hasChildren;
+
+    public GroupNode() {
+        permissions = new HashSet<>();
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
+
+    public List<Permission> getPermissions() {
+        return Collections.unmodifiableList(new ArrayList<>(permissions));
+    }
+
+    public void setPermissions(Set<Permission> permissions) {
+        this.permissions = permissions;
+    }
+
+    public void addPermission(Permission permission) {
+
+        // remove implied permissions
+        switch (permission) {
+            case ADMIN:
+                permissions.add(permission);
+                permissions.remove(Permission.MANAGE_MEMBERS);
+                permissions.remove(Permission.VIEW_MEMBERS);
+                permissions.remove(Permission.TRAVERSE);
+                break;
+            case MANAGE_MEMBERS:
+                if (!permissions.contains(Permission.ADMIN)) {
+                    permissions.add(permission);
+                    permissions.remove(Permission.VIEW_MEMBERS);
+                    permissions.remove(Permission.TRAVERSE);
+                }
+                break;
+            case VIEW_MEMBERS:
+                if (!permissions.contains(Permission.ADMIN)
+                        && !permissions.contains(Permission.MANAGE_MEMBERS)) {
+                    permissions.add(permission);
+                    permissions.remove(Permission.TRAVERSE);
+                }
+                break;
+            case TRAVERSE:
+                if (permissions.isEmpty()) {
+                    permissions.add(permission);
+                }
+                break;
+        }
+    }
+
+    public boolean isHasChildren() {
+        return hasChildren;
+    }
+
+    public void setHasChildren(boolean hasChildren) {
+        this.hasChildren = hasChildren;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9127d87de239fccf58baa82318ffd5e6fc03fc1d
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java
@@ -0,0 +1,32 @@
+package it.inaf.ia2.gms.model;
+
+public class GroupsModelRequest extends BaseModelRequest {
+
+    private String groupId;
+    private int paginatorPage;
+    private int paginatorPageSize;
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public int getPaginatorPage() {
+        return paginatorPage;
+    }
+
+    public void setPaginatorPage(int paginatorPage) {
+        this.paginatorPage = paginatorPage;
+    }
+
+    public int getPaginatorPageSize() {
+        return paginatorPageSize;
+    }
+
+    public void setPaginatorPageSize(int paginatorPageSize) {
+        this.paginatorPageSize = paginatorPageSize;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..415ff5cb6581babcd732768c0e3b7bc289da05b9
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java
@@ -0,0 +1,53 @@
+package it.inaf.ia2.gms.model;
+
+import java.util.List;
+
+public class GroupsModelResponse extends BaseModelResponse {
+
+    private List<GroupBreadcrumb> breadcrumbs;
+    private PaginatedData<GroupNode> groupsPanel;
+    private PaginatedData<RapUser> membersPanel;
+    private PaginatedData<UserPermission> permissionsPanel;
+    // current group permissions
+    private List<Permission> permissions;
+
+    public List<GroupBreadcrumb> getBreadcrumbs() {
+        return breadcrumbs;
+    }
+
+    public void setBreadcrumbs(List<GroupBreadcrumb> breadcrumbs) {
+        this.breadcrumbs = breadcrumbs;
+    }
+
+    public PaginatedData<GroupNode> getGroupsPanel() {
+        return groupsPanel;
+    }
+
+    public void setGroupsPanel(PaginatedData<GroupNode> groupsPanel) {
+        this.groupsPanel = groupsPanel;
+    }
+
+    public PaginatedData<RapUser> getMembersPanel() {
+        return membersPanel;
+    }
+
+    public void setMembersPanel(PaginatedData<RapUser> membersPanel) {
+        this.membersPanel = membersPanel;
+    }
+
+    public PaginatedData<UserPermission> getPermissionsPanel() {
+        return permissionsPanel;
+    }
+
+    public void setPermissionsPanel(PaginatedData<UserPermission> permissionsPanel) {
+        this.permissionsPanel = permissionsPanel;
+    }
+
+    public List<Permission> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(List<Permission> permissions) {
+        this.permissions = permissions;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java b/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7031e58fe552167457f2e582e7c58b23280ed51
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/Identity.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.model;
+
+public class Identity {
+
+    private IdentityType type;
+    private String email;
+
+    public IdentityType getType() {
+        return type;
+    }
+
+    public void setType(IdentityType type) {
+        this.type = type;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java b/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2a565557eaa455d20d603e54f4fc4fed60fd7bd
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/IdentityType.java
@@ -0,0 +1,12 @@
+package it.inaf.ia2.gms.model;
+
+public enum IdentityType {
+
+    EDU_GAIN,
+    X509,
+    ORCID,
+    GOOGLE,
+    LINKEDIN,
+    FACEBOOK,
+    LOCAL_IDP
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ea07f7451ed5aab368ee84ad5e162add7778e20
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedData.java
@@ -0,0 +1,83 @@
+package it.inaf.ia2.gms.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PaginatedData<T> {
+
+    // how many page index links have to be shown
+    private static final int LINKS_SIZE = 5;
+
+    private final List<T> items;
+    private final int currentPage;
+    private final List<Integer> links;
+    private final int totalItems;
+    private final int pageSize;
+    private final int totalPages;
+    private final boolean hasPreviousPages;
+    private final boolean hasFollowingPages;
+
+    public PaginatedData(List<T> allItems, int page, int pageSize) {
+
+        totalItems = allItems.size();
+        this.currentPage = page;
+        this.pageSize = pageSize;
+
+        totalPages = (int) Math.ceil((double) totalItems / pageSize);
+
+        if (allItems.isEmpty()) {
+            items = allItems;
+        } else {
+            int firstElementIndex = (currentPage - 1) * pageSize;
+            int lastElementIndex = Math.min(currentPage * pageSize - 1, totalItems - 1);
+            items = new ArrayList<>(allItems.subList(firstElementIndex, lastElementIndex));
+        }
+
+        links = new ArrayList<>();
+        int start = currentPage - LINKS_SIZE / 2;
+        if (start > 1 && start + LINKS_SIZE > totalPages) {
+            start = totalPages - LINKS_SIZE + 1;
+        }
+        if (start < 1) {
+            start = 1;
+        }
+        int i = start;
+        hasPreviousPages = start > 1;
+        while (i < start + LINKS_SIZE && i <= totalPages) {
+            links.add(i++);
+        }
+        hasFollowingPages = totalPages > i;
+    }
+
+    public List<T> getItems() {
+        return items;
+    }
+
+    public int getCurrentPage() {
+        return currentPage;
+    }
+
+    public List<Integer> getLinks() {
+        return links;
+    }
+
+    public int getTotalItems() {
+        return totalItems;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public int getTotalPages() {
+        return totalPages;
+    }
+
+    public boolean isHasPreviousPages() {
+        return hasPreviousPages;
+    }
+
+    public boolean isHasFollowingPages() {
+        return hasFollowingPages;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java
new file mode 100644
index 0000000000000000000000000000000000000000..be00ef7092d4e17163a7499e236f5f36a258672c
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java
@@ -0,0 +1,9 @@
+package it.inaf.ia2.gms.model;
+
+public enum Permission {
+
+    ADMIN,
+    MANAGE_MEMBERS,
+    VIEW_MEMBERS,
+    TRAVERSE
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java b/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java
new file mode 100644
index 0000000000000000000000000000000000000000..e58f27da60c3afc8b7e5f8716ebfa8f6b06a6e5f
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/RapUser.java
@@ -0,0 +1,34 @@
+package it.inaf.ia2.gms.model;
+
+import java.util.List;
+
+public class RapUser {
+
+    private String id;
+    private String displayName;
+    private List<Identity> identities;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public List<Identity> getIdentities() {
+        return identities;
+    }
+
+    public void setIdentities(List<Identity> identities) {
+        this.identities = identities;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java
new file mode 100644
index 0000000000000000000000000000000000000000..66ab93a33f3bf02c4337ccd7b033c9ee91e94fdb
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/UserPermission.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.model;
+
+public class UserPermission {
+
+    private RapUser user;
+    private Permission permission;
+
+    public RapUser getUser() {
+        return user;
+    }
+
+    public void setUser(RapUser user) {
+        this.user = user;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public void setPermission(Permission permission) {
+        this.permission = permission;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd336928e77e34081c72e5101f1403aa792cc27e
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java
@@ -0,0 +1,16 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.Membership;
+import it.inaf.ia2.gms.persistence.model.MembershipId;
+import it.inaf.ia2.gms.persistence.model.User;
+import java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@Transactional
+public interface MembershipRepository extends JpaRepository<Membership, MembershipId> {
+
+    List<Membership> findBy_user(User user);
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
index 9b8b50a13afb474e9697aeca3a5fdd1a9d0056b7..f3e19f1a5cfb956db75c6cc867b7390632f4c54d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
@@ -1,8 +1,8 @@
 package it.inaf.ia2.gms.persistence;
 
 import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroup;
-import it.inaf.ia2.gms.persistence.model.UserGroupId;
+import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
+import it.inaf.ia2.gms.persistence.model.UserGroupPermissionId;
 import java.util.List;
 import javax.transaction.Transactional;
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository;
 
 @Repository
 @Transactional
-public interface PermissionsRepository extends JpaRepository<UserGroup, UserGroupId> {
+public interface PermissionsRepository extends JpaRepository<UserGroupPermission, UserGroupPermissionId> {
 
-    List<UserGroup> findBy_user(User user);
+    List<UserGroupPermission> findBy_user(User user);
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java
deleted file mode 100644
index 6a991061ce8fc81bfb96f8eb815ddb1277582a1f..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-public class UserGroupsFromParentSpecification {
-
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
index 92cb5d0c1ab8326b0bc2c4b9c146c050c8f1aced..504093e9d8e01965bcd6b3f5519a18ec6a911693 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
@@ -18,6 +18,7 @@ import javax.persistence.Table;
 public class Group {
 
     @Id
+    @Column(length = 50)
     private String id;
 
     @Column(nullable = false)
@@ -29,10 +30,10 @@ public class Group {
 
     @OneToMany(mappedBy = "parentGroup", fetch = FetchType.LAZY)
     @OrderBy("name ASC")
-    private List<Group> groupsMembers;
+    private List<Group> childrenGroups;
 
     public Group() {
-        groupsMembers = new ArrayList<>();
+        childrenGroups = new ArrayList<>();
     }
 
     public String getId() {
@@ -59,12 +60,12 @@ public class Group {
         this.parentGroup = parentGroup;
     }
 
-    public List<Group> getGroupsMembers() {
-        return groupsMembers;
+    public List<Group> getChildrenGroups() {
+        return childrenGroups;
     }
 
-    public void setGroupsMembers(List<Group> groupsMembers) {
-        this.groupsMembers = groupsMembers;
+    public void setChildrenGroups(List<Group> childrenGroups) {
+        this.childrenGroups = childrenGroups;
     }
 
     @Override
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java
new file mode 100644
index 0000000000000000000000000000000000000000..69b282fb2301558124ca29dbe4bff2b5d5ecf4ea
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java
@@ -0,0 +1,44 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+/**
+ * Note: if necessary this class could contain also a role (specific for the
+ * application using the GMS). Currently that is not implemented.
+ */
+@Entity
+@IdClass(MembershipId.class)
+@Table(name = "gms_membership")
+public class Membership {
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
+    private User _user;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
+    private Group _group;
+
+    public User getUser() {
+        return _user;
+    }
+
+    public void setUser(User _user) {
+        this._user = _user;
+    }
+
+    public Group getGroup() {
+        return _group;
+    }
+
+    public void setGroup(Group _group) {
+        this._group = _group;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0b1beab333ce7e7304a0f52b361f778a3be0a96
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java
@@ -0,0 +1,39 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+public class MembershipId implements Serializable {
+
+    String _user;
+    String _group;
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 47 * hash + Objects.hashCode(this._user);
+        hash = 47 * hash + Objects.hashCode(this._group);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final MembershipId other = (MembershipId) obj;
+        if (!Objects.equals(this._user, other._user)) {
+            return false;
+        }
+        if (!Objects.equals(this._group, other._group)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
index 82238da1765629ce7b38629fc563fef4a6626814..9d2c6d794ea996c48cc2b9158beee2153810d051 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
@@ -1,5 +1,6 @@
 package it.inaf.ia2.gms.persistence.model;
 
+import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Id;
 import javax.persistence.Table;
@@ -9,6 +10,7 @@ import javax.persistence.Table;
 public class User {
 
     @Id
+    @Column(length = 50)
     private String id;
 
     public String getId() {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
similarity index 80%
rename from gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java
rename to gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
index 8bfb1fad8cab5529559541db1a4a51aba54716ab..3eebc85e7502d3a38958156628cf6e179cd5dce6 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
@@ -1,6 +1,6 @@
 package it.inaf.ia2.gms.persistence.model;
 
-import it.inaf.ia2.gms.service.model.Permission;
+import it.inaf.ia2.gms.model.Permission;
 import java.util.Objects;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -17,23 +17,23 @@ import javax.persistence.Table;
  * keyword.
  */
 @Entity
-@IdClass(UserGroupId.class)
-@Table(name = "gms_user_group")
-public class UserGroup {
+@IdClass(UserGroupPermissionId.class)
+@Table(name = "gms_user_group_permission")
+public class UserGroupPermission {
 
     @Id
     @ManyToOne
-    @JoinColumn(name = "user_id", referencedColumnName = "id")
+    @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
     private User _user;
 
     @Id
     @ManyToOne
-    @JoinColumn(name = "group_id", referencedColumnName = "id")
+    @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
     private Group _group;
 
     @Id
     @Enumerated(EnumType.STRING)
-    @Column(name = "permission")
+    @Column(name = "permission", length = 50)
     private Permission permission;
 
     public User getUser() {
@@ -80,7 +80,7 @@ public class UserGroup {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final UserGroup other = (UserGroup) obj;
+        final UserGroupPermission other = (UserGroupPermission) obj;
         if (!Objects.equals(this._user, other._user)) {
             return false;
         }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
similarity index 85%
rename from gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java
rename to gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
index c546caedfa87edbc20f6d8250892233a946f02c2..c036bfd25d7d228207653a385e59b5f02574adf0 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
@@ -1,10 +1,10 @@
 package it.inaf.ia2.gms.persistence.model;
 
-import it.inaf.ia2.gms.service.model.Permission;
+import it.inaf.ia2.gms.model.Permission;
 import java.io.Serializable;
 import java.util.Objects;
 
-public class UserGroupId implements Serializable {
+public class UserGroupPermissionId implements Serializable {
 
     String _user;
     String _group;
@@ -30,7 +30,7 @@ public class UserGroupId implements Serializable {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final UserGroupId other = (UserGroupId) obj;
+        final UserGroupPermissionId other = (UserGroupPermissionId) obj;
         if (!Objects.equals(this._user, other._user)) {
             return false;
         }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7eff0d84c6d24acb7256eb98e00ed88338a4fd9
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java
@@ -0,0 +1,8 @@
+package it.inaf.ia2.gms.rap;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class RapClient {
+
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1673fd6eafc1707d249cf883b22d74269c7f695a
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java
@@ -0,0 +1,56 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.model.GroupBreadcrumb;
+import it.inaf.ia2.gms.model.GroupNode;
+import it.inaf.ia2.gms.model.GroupsModelRequest;
+import it.inaf.ia2.gms.model.GroupsModelResponse;
+import it.inaf.ia2.gms.model.PaginatedData;
+import it.inaf.ia2.gms.persistence.model.Group;
+import it.inaf.ia2.gms.persistence.model.User;
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GroupsModelService {
+
+    @Autowired
+    private UsersService usersService;
+
+    @Autowired
+    private GroupsService groupsService;
+
+    public GroupsModelResponse getGroupsModel(GroupsModelRequest request, String userId) {
+
+        User user = usersService.getUserById(userId);
+        Group group = groupsService.getGroupById(request.getGroupId());
+
+        GroupsModelResponse response = new GroupsModelResponse();
+
+        response.setPage("groups");
+
+        List<GroupNode> groupNodes = groupsService.getSubgroups(group, user);
+
+        response.setGroupsPanel(new PaginatedData<>(groupNodes, request.getPaginatorPage(), request.getPaginatorPageSize()));
+
+        response.setPermissions(groupsService.getPermissions(group, user));
+        response.setBreadcrumbs(buildBreadcrumbs(new ArrayList<>(), group));
+
+        return response;
+    }
+
+    private List<GroupBreadcrumb> buildBreadcrumbs(List<GroupBreadcrumb> breadcrumbs, Group group) {
+
+        GroupBreadcrumb bc = new GroupBreadcrumb();
+        bc.setGroupId(group.getId());
+        bc.setGroupName(group.getName());
+        breadcrumbs.add(bc);
+
+        if (group.getParentGroup() == null) {
+            return breadcrumbs;
+        } else {
+            return buildBreadcrumbs(breadcrumbs, group.getParentGroup());
+        }
+    }
+}
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 f2affaf6be06d382bec45a9604ac9a11192006d2..9a258178876c3da9a0c464dc970aab5e3d90f1ff 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
@@ -1,20 +1,21 @@
 package it.inaf.ia2.gms.service;
 
 import it.inaf.ia2.gms.exception.BadRequestException;
+import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.persistence.GroupsRepository;
-import it.inaf.ia2.gms.persistence.UsersRepository;
+import it.inaf.ia2.gms.persistence.MembershipRepository;
 import it.inaf.ia2.gms.persistence.model.Group;
 import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroup;
+import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
 import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import it.inaf.ia2.gms.persistence.PermissionsRepository;
-import it.inaf.ia2.gms.service.model.GroupNode;
-import it.inaf.ia2.gms.service.model.Permission;
+import it.inaf.ia2.gms.persistence.model.Membership;
+import it.inaf.ia2.gms.model.GroupNode;
+import it.inaf.ia2.gms.model.Permission;
 import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
@@ -24,15 +25,16 @@ public class GroupsService {
     public static final String ROOT = "ROOT";
 
     private final GroupsRepository groupsRepository;
-    private final UsersRepository usersRepository;
     private final PermissionsRepository permissionsRepository;
+    private final MembershipRepository membershipRepository;
 
     @Autowired
     public GroupsService(GroupsRepository groupsRepository,
-            UsersRepository usersRepository, PermissionsRepository permissionsRepository) {
+            PermissionsRepository permissionsRepository,
+            MembershipRepository membershipRepository) {
         this.groupsRepository = groupsRepository;
-        this.usersRepository = usersRepository;
         this.permissionsRepository = permissionsRepository;
+        this.membershipRepository = membershipRepository;
         createRootIfNecessary();
     }
 
@@ -45,90 +47,144 @@ public class GroupsService {
         }
     }
 
-    public Group addGroup(String parentId, String groupName) {
+    public Group addGroup(String parentId, String groupName, User user) {
+
         Group parent = getGroupById(parentId);
+
+        if (parent.getChildrenGroups().stream()
+                .anyMatch(g -> g.getName().equals(groupName))) {
+            throw new BadRequestException("There is already a group named " + groupName);
+        }
+
+        if (!getPermissions(parent, user).contains(Permission.ADMIN)) {
+            throw new UnauthorizedException("Missing admin privileges");
+        }
+
         Group group = new Group();
         group.setId(UUID.randomUUID().toString());
         group.setName(groupName);
         group.setParentGroup(parent);
-        parent.getGroupsMembers().add(group);
+        parent.getChildrenGroups().add(group);
         group = groupsRepository.save(group);
         groupsRepository.save(parent);
         return group;
     }
 
-    public List<Group> getSubgroups(String parentId) {
-        Group parent = getGroupById(parentId);
-        return groupsRepository.findByParentGroup(parent);
-    }
+    public List<GroupNode> getSubgroups(Group parent, User user) {
 
-    public List<GroupNode> getSubgroups(String parentGroupId, String userId) {
+        List<UserGroupPermission> permissions = getAllPermissions(user);
+
+        Map<String, GroupNode> nodesMap = new HashMap<>();
+
+        for (Group childGroup : parent.getChildrenGroups()) {
+            addGroupNodeToMap(childGroup, permissions, nodesMap);
+        }
+
+        List<GroupNode> nodes = new ArrayList<>(nodesMap.values());
+
+        // Sort by group name
+        nodes.sort((n1, n2) -> n1.getGroupName().compareTo(n2.getGroupName()));
+
+        return nodes;
+    }
 
-        User user = usersRepository.findById(userId)
-                .orElseThrow(() -> new BadRequestException("User " + userId + " not found"));
+    public List<Permission> getPermissions(Group group, User user) {
 
-        Group parent = getGroupById(parentGroupId);
+        List<UserGroupPermission> permissions = getAllPermissions(user);
+        Map<String, GroupNode> nodesMap = new HashMap<>();
 
-        List<UserGroup> permissions = permissionsRepository.findBy_user(user);
+        addGroupNodeToMap(group, permissions, nodesMap);
 
-        Map<String, GroupNode> nodes = new LinkedHashMap<>();
+        GroupNode groupNode = nodesMap.get(group.getId());
+        if (groupNode == null) {
+            return null;
+        }
+        return groupNode.getPermissions();
+    }
 
-        for (Group childGroup : parent.getGroupsMembers()) {
+    /**
+     * Returns all the permissions, including also the calculated ones (the ones
+     * derived from the memberships).
+     */
+    private List<UserGroupPermission> getAllPermissions(User user) {
+
+        // explicit permissions
+        List<UserGroupPermission> permissions = permissionsRepository.findBy_user(user);
+
+        List<UserGroupPermission> implicitPermissions = new ArrayList<>();
+        for (Membership membership : membershipRepository.findBy_user(user)) {
+            UserGroupPermission userGroupPermission = new UserGroupPermission();
+            userGroupPermission.setUser(user);
+            userGroupPermission.setGroup(membership.getGroup());
+            userGroupPermission.setPermission(Permission.TRAVERSE);
+            implicitPermissions.add(userGroupPermission);
+        }
 
-            Iterator<UserGroup> ite = permissions.iterator();
-            while (ite.hasNext()) {
+        permissions.addAll(implicitPermissions);
+        return permissions;
+    }
 
-                UserGroup permission = ite.next();
+    private void addGroupNodeToMap(Group group, List<UserGroupPermission> permissions, Map<String, GroupNode> nodesMap) {
 
-                boolean isChild = false;
-                if (permission.getGroup().getId().equals(childGroup.getId())
-                        || (isChild = isChildOf(permission.getGroup(), parentGroupId))) {
+        for (UserGroupPermission permission : permissions) {
 
-                    GroupNode node = nodes.get(childGroup.getId());
-                    if (node == null) {
-                        node = getGroupNode(childGroup);
-                    }
+            boolean isChild = false;
+            if (permission.getGroup().getId().equals(group.getId())
+                    || (isChild = isChildOf(permission.getGroup(), group))
+                    || (isParentOf(permission.getGroup(), group))) {
 
-                    if (isChild) {
-                        // Traversal only
-                        node.getPermissions().add(Permission.READ);
-                    } else {
-                        // Direct permission
-                        node.getPermissions().add(permission.getPermission());
-                    }
+                GroupNode node = nodesMap.get(group.getId());
+                if (node == null) {
+                    node = getGroupNode(group);
+                }
 
-                    nodes.put(childGroup.getId(), node);
-                    ite.remove();
+                if (isChild) {
+                    // Traversal only
+                    node.addPermission(Permission.TRAVERSE);
+                } else {
+                    // Direct permission or permission inherited from parent
+                    node.addPermission(permission.getPermission());
                 }
+
+                nodesMap.put(group.getId(), node);
             }
         }
-
-        // TODO: pagination
-        return new ArrayList<>(nodes.values());
     }
 
     private GroupNode getGroupNode(Group group) {
         GroupNode node = new GroupNode();
         node.setGroupId(group.getId());
         node.setGroupName(group.getName());
-        node.setHasChildren(!group.getGroupsMembers().isEmpty());
+        node.setHasChildren(!group.getChildrenGroups().isEmpty());
         return node;
     }
 
-    private boolean isChildOf(Group group, String parentGroupId) {
+    private boolean isChildOf(Group group, Group possibleParent) {
         Group parent = group.getParentGroup();
         if (parent == null) {
             // ROOT has no parent
             return false;
         }
-        if (parentGroupId.equals(parent.getId())) {
+        if (possibleParent.getId().equals(parent.getId())) {
+            return true;
+        }
+        // recursive call to parent group
+        return isChildOf(parent, possibleParent);
+    }
+
+    private boolean isParentOf(Group group, Group possibleChild) {
+        Group parent = possibleChild.getParentGroup();
+        if (parent == null) {
+            return false;
+        }
+        if (parent.getId().equals(group.getId())) {
             return true;
         }
         // recursive call to parent group
-        return isChildOf(parent, parentGroupId);
+        return isParentOf(group, parent);
     }
 
-    private Group getGroupById(String groupId) {
+    public Group getGroupById(String groupId) {
         return groupsRepository.findById(groupId)
                 .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found"));
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java b/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java
deleted file mode 100644
index cf11a1aa52e7a97e06d047cc4fbc1b9470b62045..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package it.inaf.ia2.gms.service;
-
-public class MainModelService {
-
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cf6a9905b82d8ced1582d1cbcf28ae827a0c5b1
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java
@@ -0,0 +1,24 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.exception.BadRequestException;
+import it.inaf.ia2.gms.persistence.UsersRepository;
+import it.inaf.ia2.gms.persistence.model.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UsersService {
+
+    private final UsersRepository usersRepository;
+
+    @Autowired
+    public UsersService(UsersRepository usersRepository) {
+        this.usersRepository = usersRepository;
+    }
+
+    public User getUserById(String userId) {
+
+        return usersRepository.findById(userId)
+                .orElseThrow(() -> new BadRequestException("User " + userId + " not found"));
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java
deleted file mode 100644
index 920ec3d6aa19c1509faa7e921eb3cc92ea9ad2ba..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package it.inaf.ia2.gms.service.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class GroupNode {
-
-    private String groupId;
-    private String groupName;
-    private List<Permission> permissions;
-    private boolean hasChildren;
-
-    public GroupNode() {
-        permissions = new ArrayList<>();
-    }
-
-    public String getGroupId() {
-        return groupId;
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-
-    public String getGroupName() {
-        return groupName;
-    }
-
-    public void setGroupName(String groupName) {
-        this.groupName = groupName;
-    }
-
-    public List<Permission> getPermissions() {
-        return permissions;
-    }
-
-    public void setPermissions(List<Permission> permissions) {
-        this.permissions = permissions;
-    }
-
-    public boolean isHasChildren() {
-        return hasChildren;
-    }
-
-    public void setHasChildren(boolean hasChildren) {
-        this.hasChildren = hasChildren;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java b/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java
deleted file mode 100644
index 899a1b4fc1be254ab225a47d21285f9c15cecf8f..0000000000000000000000000000000000000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package it.inaf.ia2.gms.service.model;
-
-public enum Permission {
-
-    ADMIN,
-    MANAGE_MEMBERS,
-    READ
-}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java
deleted file mode 100644
index c5afa9b3558d1e31069aa64bf71e112be665955a..0000000000000000000000000000000000000000
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import java.util.Arrays;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@DataJpaTest
-@AutoConfigureTestDatabase
-@RunWith(SpringRunner.class)
-public class GroupsRepositoryTest {
-
-    @Autowired
-    private UsersRepository usersRepository;
-
-    @Autowired
-    private GroupsRepository groupsRepository;
-
-    @Test
-    public void testCreateGroup() {
-
-        User user1 = new User();
-        user1.setId("user1");
-        user1 = usersRepository.save(user1);
-        User user2 = new User();
-        user2.setId("user2");
-        user2 = usersRepository.save(user2);
-
-        Group group = new Group();
-        group.setId("parent-group");
-        group.setName("Parent");
-
-        // Test cascade persist
-        Group childGroup = new Group();
-        childGroup.setId("child-group");
-        childGroup.setName("Child 1");
-
-        group.setGroupsMembers(Arrays.asList(childGroup));
-
-        group = groupsRepository.save(group);
-
-        // Test cascade merge
-        Group childGroup2 = new Group();
-        childGroup2.setId("child-group-2");
-        childGroup2.setName("Child 2");
-        group.getGroupsMembers().add(childGroup2);
-        groupsRepository.save(group);
-
-        // Reload
-        group = groupsRepository.findById("parent-group").get();
-
-        assertEquals("Parent", group.getName());
-        assertEquals(2, group.getGroupsMembers().size());
-        assertEquals("Child 1", group.getGroupsMembers().get(0).getName());
-        assertEquals("Child 2", group.getGroupsMembers().get(1).getName());
-    }
-}
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 89f7f86186750a52228c33c5fef48f52d9d444d1..fdf493db76e00c089a0d9d755ce3f1885a594d2a 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,16 @@
 package it.inaf.ia2.gms.persistence;
 
 import it.inaf.ia2.gms.persistence.model.Group;
+import it.inaf.ia2.gms.persistence.model.Membership;
 import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroup;
+import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
 import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.model.GroupNode;
-import it.inaf.ia2.gms.service.model.Permission;
+import it.inaf.ia2.gms.model.GroupNode;
+import it.inaf.ia2.gms.model.Permission;
 import java.util.List;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,30 +32,126 @@ public class NestedGroupsIntegrationTest {
     @Autowired
     private PermissionsRepository permissionsRepository;
 
+    @Autowired
+    private MembershipRepository membershipRepository;
+
     @Test
     public void testNestedGroupRetrieval() {
 
-        GroupsService groupsService = new GroupsService(groupsRepository, usersRepository, permissionsRepository);
+        GroupsService groupsService = new GroupsService(groupsRepository,
+                permissionsRepository, membershipRepository);
 
         // Create user
         User user = new User();
         user.setId("user");
         user = usersRepository.save(user);
 
+        // Test super admin
+        UserGroupPermission superAdminPermission = new UserGroupPermission();
+        superAdminPermission.setUser(user);
+        superAdminPermission.setGroup(groupsRepository.findById(GroupsService.ROOT).get());
+        superAdminPermission.setPermission(Permission.ADMIN);
+        permissionsRepository.save(superAdminPermission);
+
         // Setup groups
-        Group lbt = groupsService.addGroup(GroupsService.ROOT, "LBT");
-        Group lbtInaf = groupsService.addGroup(lbt.getId(), "INAF");
-        Group lbtInafProgram = groupsService.addGroup(lbtInaf.getId(), "P1");
-
-        // Setup permissions
-        UserGroup permission = new UserGroup();
-        permission.setUser(user);
-        permission.setGroup(lbtInafProgram);
-        permission.setPermission(Permission.MANAGE_MEMBERS);
-        permissionsRepository.save(permission);
-
-        List<GroupNode> groupNodes = groupsService.getSubgroups(GroupsService.ROOT, user.getId());
+        Group root = groupsService.getGroupById(GroupsService.ROOT);
+        Group lbt = groupsService.addGroup(GroupsService.ROOT, "LBT", user);
+        Group tng = groupsService.addGroup(GroupsService.ROOT, "TNG", user);
+        Group radio = groupsService.addGroup(GroupsService.ROOT, "Radio", user);
+        Group lbtInaf = groupsService.addGroup(lbt.getId(), "INAF", user);
+        Group lbtInafProgram1 = groupsService.addGroup(lbtInaf.getId(), "P1", user);
+        Group lbtInafProgram2 = groupsService.addGroup(lbtInaf.getId(), "P2", user);
+
+        // Test super admin - level 0 (ROOT)
+        List<GroupNode> groupNodes = groupNodes = groupsService.getSubgroups(root, user);
+        assertEquals(3, groupNodes.size());
+        GroupNode lbtGN = groupNodes.get(0);
+        assertEquals("LBT", lbtGN.getGroupName());
+        assertEquals(1, lbtGN.getPermissions().size());
+        assertEquals(Permission.ADMIN, lbtGN.getPermissions().get(0));
+        assertTrue(lbtGN.isHasChildren());
+        GroupNode radioGN = groupNodes.get(1);
+        assertEquals("Radio", radioGN.getGroupName());
+        assertEquals(Permission.ADMIN, radioGN.getPermissions().get(0));
+        assertFalse(radioGN.isHasChildren());
+        GroupNode tngGN = groupNodes.get(2);
+        assertEquals("TNG", tngGN.getGroupName());
+        assertEquals(Permission.ADMIN, tngGN.getPermissions().get(0));
+        assertFalse(tngGN.isHasChildren());
+
+        // Test super admin - level 1
+        groupNodes = groupsService.getSubgroups(lbt, user);
         assertEquals(1, groupNodes.size());
-    }
+        GroupNode INAFGN = groupNodes.get(0);
+        assertEquals("INAF", INAFGN.getGroupName());
+        assertEquals(Permission.ADMIN, INAFGN.getPermissions().get(0));
+        assertTrue(INAFGN.isHasChildren());
+
+        // Test super admin - level 2
+        groupNodes = groupsService.getSubgroups(lbtInaf, user);
+        assertEquals(2, groupNodes.size());
+        GroupNode p1 = groupNodes.get(0);
+        assertEquals("P1", p1.getGroupName());
+        assertEquals(1, p1.getPermissions().size());
+        assertEquals(Permission.ADMIN, p1.getPermissions().get(0));
+        assertFalse(p1.isHasChildren());
+        GroupNode p2 = groupNodes.get(1);
+        assertEquals("P2", p2.getGroupName());
+        assertEquals(Permission.ADMIN, p2.getPermissions().get(0));
+        assertFalse(p2.isHasChildren());
+
+        // Setup lower permissions
+        permissionsRepository.delete(superAdminPermission);
 
+        UserGroupPermission p1Permission = new UserGroupPermission();
+        p1Permission.setUser(user);
+        p1Permission.setGroup(lbtInafProgram1);
+        p1Permission.setPermission(Permission.MANAGE_MEMBERS);
+        permissionsRepository.save(p1Permission);
+
+        UserGroupPermission lbtPermission = new UserGroupPermission();
+        lbtPermission.setUser(user);
+        lbtPermission.setGroup(lbtInaf);
+        lbtPermission.setPermission(Permission.VIEW_MEMBERS);
+        permissionsRepository.save(lbtPermission);
+
+        // Setup membership
+        Membership membership = new Membership();
+        membership.setUser(user);
+        membership.setGroup(radio);
+        membershipRepository.save(membership);
+
+        // Check level 0 (ROOT)
+        groupNodes = groupsService.getSubgroups(root, user);
+        assertEquals(2, groupNodes.size());
+        lbtGN = groupNodes.get(0);
+        assertEquals("LBT", lbtGN.getGroupName());
+        assertEquals(Permission.TRAVERSE, lbtGN.getPermissions().get(0));
+        assertTrue(lbtGN.isHasChildren());
+        radioGN = groupNodes.get(1);
+        assertEquals("Radio", radioGN.getGroupName());
+        assertEquals(Permission.TRAVERSE, radioGN.getPermissions().get(0));
+        assertFalse(radioGN.isHasChildren());
+
+        // Check level 1
+        groupNodes = groupsService.getSubgroups(lbt, user);
+        assertEquals(1, groupNodes.size());
+        INAFGN = groupNodes.get(0);
+        assertEquals("INAF", INAFGN.getGroupName());
+        assertEquals(Permission.VIEW_MEMBERS, INAFGN.getPermissions().get(0));
+        assertTrue(INAFGN.isHasChildren());
+
+        // Check level 2
+        groupNodes = groupsService.getSubgroups(lbtInaf, user);
+        assertEquals(2, groupNodes.size());
+        p1 = groupNodes.get(0);
+        assertEquals("P1", p1.getGroupName());
+        assertEquals(1, p1.getPermissions().size());
+        assertEquals(Permission.MANAGE_MEMBERS, p1.getPermissions().get(0));
+        assertFalse(p1.isHasChildren());
+        p2 = groupNodes.get(1);
+        assertEquals("P2", p2.getGroupName());
+        assertEquals(Permission.VIEW_MEMBERS, p2.getPermissions().get(0));
+        assertFalse(p2.isHasChildren());
+    }
 }