diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8513c3f178dbf66ced6224536a3f1036ae0142fb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# IA2 GMS
+
+## MySQL setup
+
+```
+create database gms;
+create user gms@localhost identified by 'gms';
+grant all privileges on gms.* to gms@localhost;
+
+```
diff --git a/gms/pom.xml b/gms/pom.xml
index 6ab11e6f5f79ce17a038a8086bb3bd6ee3bf7ba8..3042e6941fcfacddc3e329665964f2d0f679b177 100644
--- a/gms/pom.xml
+++ b/gms/pom.xml
@@ -32,6 +32,15 @@
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-oauth2-client</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -49,6 +58,11 @@
             <artifactId>spring-security-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
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
new file mode 100644
index 0000000000000000000000000000000000000000..56a344ea4c8f7cf51e246f29478701c96aa73999
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsModelController.java
@@ -0,0 +1,17 @@
+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/BaseModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e93b83aed42181fe0e2c9a659221fbb04dc7209
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelRequest.java
@@ -0,0 +1,14 @@
+package it.inaf.ia2.gms.controller.model;
+
+public abstract class BaseModelRequest {
+
+    private String page;
+
+    public String getPage() {
+        return page;
+    }
+
+    public void setPage(String page) {
+        this.page = page;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..98c6b2a09062713a16b46a2b69fb5a9c003f6e32
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/model/BaseModelResponse.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.controller.model;
+
+public abstract class BaseModelResponse {
+
+    private String user;
+    private String page;
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getPage() {
+        return page;
+    }
+
+    public void setPage(String page) {
+        this.page = page;
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..8a5c00a1ab1ea02b3c45367c9da177dd33aa0977
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelRequest.java
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..8bc0046cd5a6bd04988a614a481aca27fb1dc982
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/model/GroupsModelResponse.java
@@ -0,0 +1,6 @@
+package it.inaf.ia2.gms.controller.model;
+
+public class GroupsModelResponse extends BaseModelResponse {
+
+    
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java
new file mode 100644
index 0000000000000000000000000000000000000000..039ecfe5de87742c49e5da4b04dbeb51342788ba
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java
@@ -0,0 +1,8 @@
+package it.inaf.ia2.gms.exception;
+
+public class BadRequestException extends RuntimeException {
+
+    public BadRequestException(String message) {
+        super(message);
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7ad1eb1a6f4c06df8e121ab4e462fd76fe12548
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java
@@ -0,0 +1,14 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.Group;
+import java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@Transactional
+public interface GroupsRepository extends JpaRepository<Group, String> {
+
+    List<Group> findByParentGroup(Group parent);
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..9b8b50a13afb474e9697aeca3a5fdd1a9d0056b7
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
@@ -0,0 +1,16 @@
+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 java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@Transactional
+public interface PermissionsRepository extends JpaRepository<UserGroup, UserGroupId> {
+
+    List<UserGroup> 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
new file mode 100644
index 0000000000000000000000000000000000000000..6a991061ce8fc81bfb96f8eb815ddb1277582a1f
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/UserGroupsFromParentSpecification.java
@@ -0,0 +1,5 @@
+package it.inaf.ia2.gms.persistence;
+
+public class UserGroupsFromParentSpecification {
+
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf60c63ff38fe12288a75407874bbf70757a0533
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java
@@ -0,0 +1,11 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.User;
+import javax.transaction.Transactional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@Transactional
+public interface UsersRepository extends JpaRepository<User, String> {
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..92cb5d0c1ab8326b0bc2c4b9c146c050c8f1aced
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
@@ -0,0 +1,94 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "gms_group")
+public class Group {
+
+    @Id
+    private String id;
+
+    @Column(nullable = false)
+    private String name;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "parent_group")
+    private Group parentGroup;
+
+    @OneToMany(mappedBy = "parentGroup", fetch = FetchType.LAZY)
+    @OrderBy("name ASC")
+    private List<Group> groupsMembers;
+
+    public Group() {
+        groupsMembers = new ArrayList<>();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Group getParentGroup() {
+        return parentGroup;
+    }
+
+    public void setParentGroup(Group parentGroup) {
+        this.parentGroup = parentGroup;
+    }
+
+    public List<Group> getGroupsMembers() {
+        return groupsMembers;
+    }
+
+    public void setGroupsMembers(List<Group> groupsMembers) {
+        this.groupsMembers = groupsMembers;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 3;
+        hash = 89 * hash + Objects.hashCode(this.id);
+        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 Group other = (Group) obj;
+        if (!Objects.equals(this.id, other.id)) {
+            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
new file mode 100644
index 0000000000000000000000000000000000000000..82238da1765629ce7b38629fc563fef4a6626814
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
@@ -0,0 +1,21 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "gms_user")
+public class User {
+
+    @Id
+    private String id;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+}
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/UserGroup.java
new file mode 100644
index 0000000000000000000000000000000000000000..8bfb1fad8cab5529559541db1a4a51aba54716ab
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroup.java
@@ -0,0 +1,95 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import it.inaf.ia2.gms.service.model.Permission;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+/**
+ * NOTE: underscores in variable names are used when a field is a reserved JPQL
+ * keyword.
+ */
+@Entity
+@IdClass(UserGroupId.class)
+@Table(name = "gms_user_group")
+public class UserGroup {
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "user_id", referencedColumnName = "id")
+    private User _user;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "group_id", referencedColumnName = "id")
+    private Group _group;
+
+    @Id
+    @Enumerated(EnumType.STRING)
+    @Column(name = "permission")
+    private Permission permission;
+
+    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;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public void setPermission(Permission permission) {
+        this.permission = permission;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 47 * hash + Objects.hashCode(this._user);
+        hash = 47 * hash + Objects.hashCode(this._group);
+        hash = 47 * hash + Objects.hashCode(this.permission);
+        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 UserGroup other = (UserGroup) obj;
+        if (!Objects.equals(this._user, other._user)) {
+            return false;
+        }
+        if (!Objects.equals(this._group, other._group)) {
+            return false;
+        }
+        if (this.permission != other.permission) {
+            return false;
+        }
+        return true;
+    }
+}
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/UserGroupId.java
new file mode 100644
index 0000000000000000000000000000000000000000..c546caedfa87edbc20f6d8250892233a946f02c2
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupId.java
@@ -0,0 +1,45 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import it.inaf.ia2.gms.service.model.Permission;
+import java.io.Serializable;
+import java.util.Objects;
+
+public class UserGroupId implements Serializable {
+
+    String _user;
+    String _group;
+    Permission permission;
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 71 * hash + Objects.hashCode(this._user);
+        hash = 71 * hash + Objects.hashCode(this._group);
+        hash = 71 * hash + Objects.hashCode(this.permission);
+        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 UserGroupId other = (UserGroupId) obj;
+        if (!Objects.equals(this._user, other._user)) {
+            return false;
+        }
+        if (!Objects.equals(this._group, other._group)) {
+            return false;
+        }
+        if (this.permission != other.permission) {
+            return false;
+        }
+        return true;
+    }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..f2affaf6be06d382bec45a9604ac9a11192006d2
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
@@ -0,0 +1,135 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.exception.BadRequestException;
+import it.inaf.ia2.gms.persistence.GroupsRepository;
+import it.inaf.ia2.gms.persistence.UsersRepository;
+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 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 java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Service
+public class GroupsService {
+
+    public static final String ROOT = "ROOT";
+
+    private final GroupsRepository groupsRepository;
+    private final UsersRepository usersRepository;
+    private final PermissionsRepository permissionsRepository;
+
+    @Autowired
+    public GroupsService(GroupsRepository groupsRepository,
+            UsersRepository usersRepository, PermissionsRepository permissionsRepository) {
+        this.groupsRepository = groupsRepository;
+        this.usersRepository = usersRepository;
+        this.permissionsRepository = permissionsRepository;
+        createRootIfNecessary();
+    }
+
+    private void createRootIfNecessary() {
+        if (groupsRepository.count() == 0) {
+            Group root = new Group();
+            root.setId(ROOT);
+            root.setName(ROOT);
+            groupsRepository.save(root);
+        }
+    }
+
+    public Group addGroup(String parentId, String groupName) {
+        Group parent = getGroupById(parentId);
+        Group group = new Group();
+        group.setId(UUID.randomUUID().toString());
+        group.setName(groupName);
+        group.setParentGroup(parent);
+        parent.getGroupsMembers().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(String parentGroupId, String userId) {
+
+        User user = usersRepository.findById(userId)
+                .orElseThrow(() -> new BadRequestException("User " + userId + " not found"));
+
+        Group parent = getGroupById(parentGroupId);
+
+        List<UserGroup> permissions = permissionsRepository.findBy_user(user);
+
+        Map<String, GroupNode> nodes = new LinkedHashMap<>();
+
+        for (Group childGroup : parent.getGroupsMembers()) {
+
+            Iterator<UserGroup> ite = permissions.iterator();
+            while (ite.hasNext()) {
+
+                UserGroup permission = ite.next();
+
+                boolean isChild = false;
+                if (permission.getGroup().getId().equals(childGroup.getId())
+                        || (isChild = isChildOf(permission.getGroup(), parentGroupId))) {
+
+                    GroupNode node = nodes.get(childGroup.getId());
+                    if (node == null) {
+                        node = getGroupNode(childGroup);
+                    }
+
+                    if (isChild) {
+                        // Traversal only
+                        node.getPermissions().add(Permission.READ);
+                    } else {
+                        // Direct permission
+                        node.getPermissions().add(permission.getPermission());
+                    }
+
+                    nodes.put(childGroup.getId(), node);
+                    ite.remove();
+                }
+            }
+        }
+
+        // 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());
+        return node;
+    }
+
+    private boolean isChildOf(Group group, String parentGroupId) {
+        Group parent = group.getParentGroup();
+        if (parent == null) {
+            // ROOT has no parent
+            return false;
+        }
+        if (parentGroupId.equals(parent.getId())) {
+            return true;
+        }
+        // recursive call to parent group
+        return isChildOf(parent, parentGroupId);
+    }
+
+    private 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
new file mode 100644
index 0000000000000000000000000000000000000000..cf11a1aa52e7a97e06d047cc4fbc1b9470b62045
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/MainModelService.java
@@ -0,0 +1,5 @@
+package it.inaf.ia2.gms.service;
+
+public class MainModelService {
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..920ec3d6aa19c1509faa7e921eb3cc92ea9ad2ba
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/model/GroupNode.java
@@ -0,0 +1,48 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..899a1b4fc1be254ab225a47d21285f9c15cecf8f
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/model/Permission.java
@@ -0,0 +1,8 @@
+package it.inaf.ia2.gms.service.model;
+
+public enum Permission {
+
+    ADMIN,
+    MANAGE_MEMBERS,
+    READ
+}
diff --git a/gms/src/main/resources/application.properties b/gms/src/main/resources/application.properties
index f256cf2f8f3dde5482e978eff9743de529d1e228..46854617fed06dfd5de53595b4435cfb3daee721 100644
--- a/gms/src/main/resources/application.properties
+++ b/gms/src/main/resources/application.properties
@@ -8,4 +8,10 @@ security.oauth2.resource.token-info-uri=http://localhost/rap-ia2/auth/oauth2/che
 security.oauth2.client.scope=openid,email,profile
 security.oauth2.resource.jwk.key-set-uri=http://localhost/rap-ia2/auth/oidc/jwks
 
-logging.level.org.springframework.security=DEBUG
\ No newline at end of file
+logging.level.org.springframework.security=DEBUG
+
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
+spring.datasource.url=jdbc:mysql://localhost:3306/gms?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
+spring.datasource.username=gms
+spring.datasource.password=gms
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
new file mode 100644
index 0000000000000000000000000000000000000000..c5afa9b3558d1e31069aa64bf71e112be665955a
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsRepositoryTest.java
@@ -0,0 +1,63 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..89f7f86186750a52228c33c5fef48f52d9d444d1
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
@@ -0,0 +1,58 @@
+package it.inaf.ia2.gms.persistence;
+
+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.service.GroupsService;
+import it.inaf.ia2.gms.service.model.GroupNode;
+import it.inaf.ia2.gms.service.model.Permission;
+import java.util.List;
+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 NestedGroupsIntegrationTest {
+
+    @Autowired
+    private UsersRepository usersRepository;
+
+    @Autowired
+    private GroupsRepository groupsRepository;
+
+    @Autowired
+    private PermissionsRepository permissionsRepository;
+
+    @Test
+    public void testNestedGroupRetrieval() {
+
+        GroupsService groupsService = new GroupsService(groupsRepository, usersRepository, permissionsRepository);
+
+        // Create user
+        User user = new User();
+        user.setId("user");
+        user = usersRepository.save(user);
+
+        // 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());
+        assertEquals(1, groupNodes.size());
+    }
+
+}