diff --git a/gms-ui/package.json b/gms-ui/package.json
index 287b62f9087cc2cbfd90a08be5ec6ead4aecb3de..65869b384685a8301f8bd0e8fd0400c3e7614e9d 100644
--- a/gms-ui/package.json
+++ b/gms-ui/package.json
@@ -45,7 +45,9 @@
       "plugin:vue/essential",
       "eslint:recommended"
     ],
-    "rules": {},
+    "rules": {
+      "no-console": "warn"
+    },
     "parserOptions": {
       "parser": "babel-eslint"
     }
diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js
index f7079c8850ba39cd5adf841fddf58ca281cda1a6..cfb352e6cf78c9b814732462a06512e1fbbcc1ab 100644
--- a/gms-ui/src/api/server/index.js
+++ b/gms-ui/src/api/server/index.js
@@ -17,7 +17,7 @@ function apiRequest(options, showLoading = true, handleValidationErrors = false)
         loading(false);
       })
       .catch(error => {
-        if(handleValidationErrors && error.response && error.response.status === 400) {
+        if (handleValidationErrors && error.response && error.response.status === 400) {
           reject(error.response.data);
         } else {
           dispatchApiErrorEvent(error);
@@ -363,5 +363,19 @@ export default {
         'Cache-Control': 'no-cache'
       }
     }, false);
+  },
+  deleteInvitedRegistration(requestId, groupId) {
+    let url = BASE_API_URL + 'registration?' +
+      'request_id=' + requestId + '&group_id=' + groupId;
+    return apiRequest({
+      method: 'DELETE',
+      url: url,
+      withCredentials: true,
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+        'Cache-Control': 'no-cache'
+      }
+    });
   }
 };
diff --git a/gms-ui/src/components/GenericSearchResults.vue b/gms-ui/src/components/GenericSearchResults.vue
index e0277a7a1abe30798c996c043255a57bec14b4d4..ea163ca4b3ced10e2d33fa9d1ddff5162ba78381 100644
--- a/gms-ui/src/components/GenericSearchResults.vue
+++ b/gms-ui/src/components/GenericSearchResults.vue
@@ -1,18 +1,21 @@
 <template>
 <div class="mt-sm-3">
   <div>
-    <p>Search results:</p>
-    <b-list-group v-for="item in model.genericSearchResults.items" v-bind:key="item.id">
-      <b-list-group-item href="#" v-on:click="openSearchResult(item)">
-        <span class="float-left">
-          <font-awesome-icon icon="folder" v-if="item.type === 'GROUP'"></font-awesome-icon>
-          <font-awesome-icon icon="user" v-if="item.type === 'USER'"></font-awesome-icon>
-          {{item.label}}
-        </span>
-      </b-list-group-item>
-    </b-list-group>
-    <Paginator :paginatedPanel="model.genericSearchResults" :onUpdate="updateSearchResults" :paginatorInput="input.genericSearch" />
+    <div v-if="model.genericSearchResults.items && model.genericSearchResults.items.length > 0">
+      <p>Search results:</p>
+      <b-list-group v-for="item in model.genericSearchResults.items" v-bind:key="item.id">
+        <b-list-group-item href="#" v-on:click="openSearchResult(item)">
+          <span class="float-left">
+            <font-awesome-icon icon="folder" v-if="item.type === 'GROUP'"></font-awesome-icon>
+            <font-awesome-icon icon="user" v-if="item.type === 'USER'"></font-awesome-icon>
+            {{item.label}}
+          </span>
+        </b-list-group-item>
+      </b-list-group>
+      <Paginator :paginatedPanel="model.genericSearchResults" :onUpdate="updateSearchResults" :paginatorInput="input.genericSearch" />
+    </div>
   </div>
+  <p v-if="model.genericSearchResults.items && model.genericSearchResults.items.length === 0">No entries were found matching your search</p>
 </div>
 </template>
 
@@ -29,7 +32,7 @@ export default {
     model: state => state.model,
     input: state => state.input
   }),
-  created () {
+  created() {
     this.updateSearchResults();
   },
   watch: {
diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue
index 748119af309fe5e491af5422c7c134b76c452f98..b77bcb1ca27a893db4ed2e7df81369eda1f004c1 100644
--- a/gms-ui/src/components/GroupsPanel.vue
+++ b/gms-ui/src/components/GroupsPanel.vue
@@ -14,7 +14,7 @@
             <font-awesome-icon icon="edit"></font-awesome-icon>
           </a>
           &nbsp;
-          <a href="#" v-on:click.stop.prevent="openRemoveGroupModal(group)" class="text-danger" title="Delete">
+          <a href="#" v-on:click.stop.prevent="openRemoveGroupModal(group)" class="text-danger" title="Delete" v-if="!group.locked">
             <font-awesome-icon icon="trash"></font-awesome-icon>
           </a>
         </span>
diff --git a/gms-ui/src/components/InvitedRegistrationPanel.vue b/gms-ui/src/components/InvitedRegistrationPanel.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a087bc6a78abeb5874fa7c3c79217a28ff340a1a
--- /dev/null
+++ b/gms-ui/src/components/InvitedRegistrationPanel.vue
@@ -0,0 +1,50 @@
+<template>
+<b-tab :title="'Invited (' + registrations.length + ')'" :title-link-class="{ 'd-none': registrations.length === 0 }">
+  <table class="table b-table table-striped table-hover" v-if="registrations">
+    <thead>
+      <tr>
+        <th>Email</th>
+        <th>Permission</th>
+        <th>Submitted at</th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr v-for="(reg, index) in registrations" v-bind:key="index">
+        <td>{{reg.email}}</td>
+        <td>{{reg.permission}}</td>
+        <td>{{reg.creationTime}}</td>
+        <td>
+          <a href="#" v-on:click.stop="openConfirmDeleteInvitedModal(reg)" class="text-danger" title="Remove permission">
+            <font-awesome-icon icon="trash"></font-awesome-icon>
+          </a>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+  <ConfirmDeleteInvitedModal ref="confirmDeleteInvitedModal" />
+</b-tab>
+</template>
+
+<script>
+import ConfirmDeleteInvitedModal from './modals/ConfirmDeleteInvitedModal.vue';
+
+export default {
+  name: 'InvitedRegistrationPanel',
+  components: {
+    ConfirmDeleteInvitedModal
+  },
+  computed: {
+    registrations() {
+      return this.$store.state.model.invitedRegistrations === null ? [] : this.$store.state.model.invitedRegistrations;
+    }
+  },
+  methods: {
+    openConfirmDeleteInvitedModal(reg) {
+      let breadcrumbs = this.$store.state.model.breadcrumbs;
+      let currentGroupId = breadcrumbs[breadcrumbs.length - 1].groupId;
+      this.$refs.confirmDeleteInvitedModal.openConfirmDeleteInvitedModal(reg, currentGroupId);
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue
index 7c7829f99b855ba3660f150ce8a8ab3b9d7b02bf..189dcac16e56183357a02f03711eb9b068b0aa1f 100644
--- a/gms-ui/src/components/Main.vue
+++ b/gms-ui/src/components/Main.vue
@@ -6,6 +6,7 @@
       <GroupsPanel />
       <MembersPanel />
       <PermissionsPanel />
+      <InvitedRegistrationPanel />
       <template slot="tabs-end">
         <b-button variant="primary" class="in-tabs-header-btn" v-if="showAddGroupBtn" v-on:click="openAddGroupModal">Add group</b-button>
         <b-button variant="primary" class="in-tabs-header-btn" v-if="showAddMemberBtn" v-on:click="openAddMemberModal">Add member</b-button>
@@ -25,6 +26,7 @@ import GroupsBreadcrumb from './GroupsBreadcrumb.vue'
 import GroupsPanel from './GroupsPanel.vue'
 import MembersPanel from './MembersPanel.vue'
 import PermissionsPanel from './PermissionsPanel.vue'
+import InvitedRegistrationPanel from './InvitedRegistrationPanel.vue'
 import AddGroupModal from './modals/AddGroupModal.vue'
 import AddMemberModal from './modals/AddMemberModal.vue'
 import AddPermissionModal from './modals/AddPermissionModal.vue'
@@ -37,6 +39,7 @@ export default {
     GroupsPanel,
     MembersPanel,
     PermissionsPanel,
+    InvitedRegistrationPanel,
     AddGroupModal,
     AddMemberModal,
     AddPermissionModal
diff --git a/gms-ui/src/components/PermissionsPanel.vue b/gms-ui/src/components/PermissionsPanel.vue
index c6631b857c27be3925217b1628d9e9f33099e448..88f2aaa1d0710eb40cb0afe717edddfe1765aa89 100644
--- a/gms-ui/src/components/PermissionsPanel.vue
+++ b/gms-ui/src/components/PermissionsPanel.vue
@@ -1,7 +1,7 @@
 <template>
 <b-tab title="Permissions" :title-link-class="{ 'd-none': (model.permission !== 'ADMIN') }">
   <div v-if="model.permissionsPanel !== null">
-    <table class="table b-table table-striped table-hover">
+    <table class="table b-table table-striped table-hover" v-if="model.permissionsPanel.items.length > 0">
       <thead>
         <tr>
           <th>User</th>
diff --git a/gms-ui/src/components/UserSearchResult.vue b/gms-ui/src/components/UserSearchResult.vue
index d5ccf14e50fb4b99fd6fc39b22bb18ead0bb696a..0f95b90025efe4e1c32e93268a7521fa3247df41 100644
--- a/gms-ui/src/components/UserSearchResult.vue
+++ b/gms-ui/src/components/UserSearchResult.vue
@@ -29,7 +29,7 @@
         <b-row>
           <b-col lg="10">
             <b-list-group>
-              <b-list-group-item v-for="(identity, index) in user.identities" v-bind:key="index">
+              <b-list-group-item :class="{ 'list-group-item-info': identity.primary }" v-for="(identity, index) in user.identities" v-bind:key="index">
                 <dl class="mb-0 ml-0 row">
                   <dt class="col-3">Type</dt><dd class="col-9">{{identity.type}}</dd>
                   <dt class="col-3">Email</dt><dd class="col-9">{{identity.email}}</dd>
diff --git a/gms-ui/src/components/modals/AddMemberModal.vue b/gms-ui/src/components/modals/AddMemberModal.vue
index 13e91d97725be0b646fdf00ae611a10d357fc38f..11634bdefab42cab9a5d124856481ca18bb5dafb 100644
--- a/gms-ui/src/components/modals/AddMemberModal.vue
+++ b/gms-ui/src/components/modals/AddMemberModal.vue
@@ -1,5 +1,5 @@
 <template>
-<b-modal id="add-member-modal" title="Add member" ok-title="Add" @shown="afterShow" @ok="addMember">
+<b-modal id="add-member-modal" title="Add member" ok-title="Add" @shown="afterShow" @ok="addMember" size="lg">
   <SearchUser ref="searchUser" @searchUserEnter="addMember" />
 </b-modal>
 </template>
diff --git a/gms-ui/src/components/modals/AddPermissionModal.vue b/gms-ui/src/components/modals/AddPermissionModal.vue
index b210f476598bebbcddf8e404659fbdabe8c48526..e988ee326b81024e71be7767d16225c849dd3f4b 100644
--- a/gms-ui/src/components/modals/AddPermissionModal.vue
+++ b/gms-ui/src/components/modals/AddPermissionModal.vue
@@ -1,5 +1,5 @@
 <template>
-<b-modal id="add-permission-modal" title="Add permission" @show="beforeShow" @shown="afterShow" :ok-title="okTitle" @ok="addPermission" :ok-variant="okBtnVariant">
+<b-modal id="add-permission-modal" title="Add permission" @show="beforeShow" @shown="afterShow" :ok-title="okTitle" @ok="addPermission" :ok-variant="okBtnVariant" size="lg">
   <SearchUser ref="searchUser" @searchUserEnter="addPermission" />
   <b-alert :show="!!existingPermission" variant="warning" class="mt-3">
     <strong>Warning</strong>: the user has already a permission ({{existingPermission}}). Click confirm to override it.
diff --git a/gms-ui/src/components/modals/ConfirmDeleteInvitedModal.vue b/gms-ui/src/components/modals/ConfirmDeleteInvitedModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e248834fa1b0c1ae39facb38eba52b7504e4076d
--- /dev/null
+++ b/gms-ui/src/components/modals/ConfirmDeleteInvitedModal.vue
@@ -0,0 +1,37 @@
+<template>
+<b-modal id="confirm-delete-invited-modal" title="Confirm action" ok-title="Delete" @ok="deleteInvitedRegistration" ok-variant="danger">
+  <p v-if="invitedRegistration">Are you sure that you want to remove the invited registration for {{invitedRegistration.email}}?</p>
+</b-modal>
+</template>
+
+<script>
+import client from 'api-client';
+
+export default {
+  name: 'ConfirmDeleteInvitedModal',
+  data: function() {
+    return {
+      invitedRegistration: null,
+      groupId: null
+    }
+  },
+  methods: {
+    openConfirmDeleteInvitedModal(invitedRegistration, groupId) {
+      this.invitedRegistration = invitedRegistration;
+      this.groupId = groupId;
+      this.$bvModal.show('confirm-delete-invited-modal');
+    },
+    deleteInvitedRegistration() {
+      let regId = this.invitedRegistration.id;
+      client.deleteInvitedRegistration(regId, this.groupId)
+        .then(() => {
+          let model = this.$store.state.model;
+          this.$store.commit('removeInvitedRegistration', regId);
+          if (model.invitedRegistrations.length === 0) {
+            this.$store.commit('setTabIndex', model.leaf ? 1 : 0);
+          }
+        });
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/components/modals/SearchUser.vue b/gms-ui/src/components/modals/SearchUser.vue
index a6b058100713be53d11c7d349fffae1f2d346ac2..5f96f4bbdfdb6ffcb7d3e417cc6bfcd9601a57d8 100644
--- a/gms-ui/src/components/modals/SearchUser.vue
+++ b/gms-ui/src/components/modals/SearchUser.vue
@@ -50,7 +50,7 @@ export default {
             let user = res[i];
             this.users.push({
               value: user.id,
-              text: user.displayName
+              text: user.displayName + ' [' + user.id + ']'
             });
           }
           if (this.users.length > 0) {
diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js
index fe02ab560b3b5fc89ff032f2f038595e6f154de7..a0e8e3866f725d60e6473a30d920c4d0f1f61b68 100644
--- a/gms-ui/src/store.js
+++ b/gms-ui/src/store.js
@@ -15,6 +15,7 @@ export default new Vuex.Store({
       groupsPanel: null,
       permissionsPanel: null,
       membersPanel: null,
+      invitedRegistrations: null,
       permission: null,
       leaf: false,
       user: null,
@@ -45,12 +46,14 @@ export default new Vuex.Store({
       state.model.breadcrumbs = model.breadcrumbs;
       state.model.groupsPanel = model.groupsPanel;
       state.model.permission = model.permission;
+      state.model.invitedRegistrations = model.invitedRegistrations;
       state.model.user = model.user;
     },
     updateGroups(state, model) {
       state.model.breadcrumbs = model.breadcrumbs;
       state.model.groupsPanel = model.groupsPanel;
       state.model.permission = model.permission;
+      state.model.invitedRegistrations = model.invitedRegistrations;
       state.model.leaf = model.leaf;
     },
     updateGroupsPanel(state, groupsPanel) {
@@ -59,7 +62,7 @@ export default new Vuex.Store({
     },
     updatePermissionsPanel(state, permissionsPanel) {
       state.model.permissionsPanel = permissionsPanel;
-      for(let up of permissionsPanel.items) {
+      for (let up of permissionsPanel.items) {
         Vue.set(up, 'editable', false);
       }
       state.input.paginatorPage = permissionsPanel.currentPage;
@@ -94,6 +97,18 @@ export default new Vuex.Store({
     },
     setGenericSearchFilter(state, filter) {
       state.input.genericSearch.filter = filter;
+    },
+    removeInvitedRegistration(state, regId) {
+      let index = -1;
+      for (var i = 0; i < state.model.invitedRegistrations.length; i++) {
+        if (state.model.invitedRegistrations[i].id === regId) {
+          index = i;
+          break;
+        }
+      }
+      if (index !== -1) {
+        state.model.invitedRegistrations.splice(index, 1);
+      }
     }
   },
   actions: {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
index f560d5d236777bf2515ed508bd233d03192a3510..2718b19cd8d586c6fd8ca882b31466a4538f29bb 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilder.java
@@ -2,6 +2,7 @@ package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.request.GroupsRequest;
@@ -30,6 +31,9 @@ public class GroupsTabResponseBuilder {
     @Autowired
     private GroupsTreeBuilder groupsListBuilder;
 
+    @Autowired
+    private InvitedRegistrationManager invitedRegistrationManager;
+
     public GroupsTabResponse getGroupsTab(GroupsRequest request) {
 
         GroupEntity group = groupsService.getGroupById(request.getGroupId());
@@ -46,6 +50,8 @@ public class GroupsTabResponseBuilder {
 
         response.setLeaf(group.isLeaf());
 
+        response.setInvitedRegistrations(invitedRegistrationManager.getInvitedRegistrationsForGroup(group));
+
         return response;
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java
index 0a4554cad6fe0b406ef329446d0779239705e8c8..82dd95a212ba2aa5211cea7e6d613a036bac64da 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java
@@ -10,7 +10,9 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@@ -55,6 +57,12 @@ public class InvitedRegistrationController {
         }
     }
 
+    @DeleteMapping(value = "/registration", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<?> deleteInvitedRegistration(@RequestParam("request_id") String requestId, @RequestParam("group_id") String groupId) {
+        invitedRegistrationManager.deleteInvitedRegistration(requestId, groupId);
+        return ResponseEntity.noContent().build();
+    }
+
     private String getFileContent(String templateFileName) throws IOException {
         try (InputStream in = InvitedRegistrationController.class.getClassLoader().getResourceAsStream("templates/" + templateFileName)) {
             Scanner s = new Scanner(in).useDelimiter("\\A");
diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
index 73bdea1bf962b3d451624294111d7fbfad141fc0..f30dca3c123a25dc9c7afec26bbe5669cfa81e5f 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java
@@ -1,8 +1,10 @@
 package it.inaf.ia2.gms.manager;
 
+import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.exception.NotFoundException;
 import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.model.response.InvitedRegistrationItem;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.InvitedRegistrationDAO;
 import it.inaf.ia2.gms.persistence.LoggingDAO;
@@ -14,8 +16,10 @@ import it.inaf.ia2.gms.service.PermissionsService;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Base64;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
@@ -119,4 +123,47 @@ public class InvitedRegistrationManager extends UserAwareComponent {
 
         return Optional.ofNullable(invitedRegistration);
     }
+
+    public List<InvitedRegistrationItem> getInvitedRegistrationsForGroup(GroupEntity group) {
+
+        if (permissionsManager.getCurrentUserPermission(group) != Permission.ADMIN) {
+            return null;
+        }
+
+        List<InvitedRegistrationItem> items = new ArrayList<>();
+
+        for (InvitedRegistration reg : invitedRegistrationDAO.getPendingInvitedRegistrationsForGroup(group.getId())) {
+
+            Map<String, Permission> map = reg.getGroupsPermissions();
+
+            if (map != null) {
+                for (Permission permission : map.values()) {
+                    InvitedRegistrationItem item = new InvitedRegistrationItem()
+                            .setId(reg.getId())
+                            .setEmail(reg.getEmail())
+                            .setPermission(permission)
+                            .setCreationTime(reg.getCreationTime());
+                    items.add(item);
+                }
+            }
+        }
+
+        return items;
+    }
+
+    public void deleteInvitedRegistration(String registrationId, String groupId) {
+
+        GroupEntity group = groupsDAO.findGroupById(groupId)
+                .orElseThrow(() -> new BadRequestException("No group found for given id: " + groupId));
+
+        if (permissionsManager.getUserPermission(group, getCurrentUserId()) != Permission.ADMIN) {
+            throw new UnauthorizedException("Only administrators can delete invited registrations!");
+        }
+
+        invitedRegistrationDAO.deleteInvitedRegistrationRequest(registrationId, groupId);
+
+        loggingDAO.logAction("Deleted invited registration request. "
+                + "[request_id=" + registrationId + ", group_id=" + groupId
+                + ", group_name=" + group.getName() + "]");
+    }
 }
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
index 381d846578c781a40ccb4463bd23983c785d0aaf..b7237fcaa6ed190116a94bc19ae68bc75bf88556 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
@@ -7,6 +7,7 @@ public class GroupNode {
     private Permission permission;
     private boolean hasChildren;
     private boolean leaf;
+    private boolean locked;
 
     public String getGroupId() {
         return groupId;
@@ -47,4 +48,12 @@ public class GroupNode {
     public void setLeaf(boolean leaf) {
         this.leaf = leaf;
     }
+
+    public boolean isLocked() {
+        return locked;
+    }
+
+    public void setLocked(boolean locked) {
+        this.locked = locked;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/response/GroupsTabResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/response/GroupsTabResponse.java
index 0eb8665d0fa38d2d1de5eaadd0b68d94a76016b2..d3d75dab12ee4b40297da96361a67f137d5dd35c 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/response/GroupsTabResponse.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/response/GroupsTabResponse.java
@@ -11,6 +11,7 @@ public class GroupsTabResponse {
     private PaginatedData<GroupNode> groupsPanel;
     // current group permissions
     private Permission permission;
+    private List<InvitedRegistrationItem> invitedRegistrations;
 
     private boolean leaf;
 
@@ -45,4 +46,12 @@ public class GroupsTabResponse {
     public void setLeaf(boolean leaf) {
         this.leaf = leaf;
     }
+
+    public List<InvitedRegistrationItem> getInvitedRegistrations() {
+        return invitedRegistrations;
+    }
+
+    public void setInvitedRegistrations(List<InvitedRegistrationItem> invitedRegistrations) {
+        this.invitedRegistrations = invitedRegistrations;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/response/InvitedRegistrationItem.java b/gms/src/main/java/it/inaf/ia2/gms/model/response/InvitedRegistrationItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc31f77c1934108f6c911c6de67ad3590e095989
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/response/InvitedRegistrationItem.java
@@ -0,0 +1,50 @@
+package it.inaf.ia2.gms.model.response;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import it.inaf.ia2.gms.model.Permission;
+import java.util.Date;
+
+public class InvitedRegistrationItem {
+
+    private String id;
+    private String email;
+    private Permission permission;
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+    private Date creationTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public InvitedRegistrationItem setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public InvitedRegistrationItem setEmail(String email) {
+        this.email = email;
+        return this;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public InvitedRegistrationItem setPermission(Permission permission) {
+        this.permission = permission;
+        return this;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public InvitedRegistrationItem setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+        return this;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
index 42285935cb9a1324dd87212b566315ce08b8c509..3cbffa59790a17845926da0690266a4bf1e93048 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -84,7 +84,7 @@ public class GroupsDAO {
 
     public Optional<GroupEntity> findGroupById(String groupId) {
 
-        String sql = "SELECT id, name, path, is_leaf from gms_group WHERE id = ?";
+        String sql = "SELECT id, name, path, is_leaf, locked from gms_group WHERE id = ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
@@ -97,6 +97,7 @@ public class GroupsDAO {
                 group.setName(resultSet.getString("name"));
                 group.setPath(resultSet.getString("path"));
                 group.setLeaf(resultSet.getBoolean("is_leaf"));
+                group.setLocked(resultSet.getBoolean("locked"));
                 return Optional.of(group);
             }
             return Optional.empty();
@@ -105,7 +106,7 @@ public class GroupsDAO {
 
     public Optional<GroupEntity> findGroupByPath(String path) {
 
-        String sql = "SELECT id, name, is_leaf from gms_group WHERE path = ?";
+        String sql = "SELECT id, name, is_leaf, locked from gms_group WHERE path = ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
@@ -117,6 +118,7 @@ public class GroupsDAO {
                 group.setId(resultSet.getString("id"));
                 group.setName(resultSet.getString("name"));
                 group.setLeaf(resultSet.getBoolean("is_leaf"));
+                group.setLocked(resultSet.getBoolean("locked"));
                 group.setPath(path);
                 return Optional.of(group);
             }
@@ -132,7 +134,7 @@ public class GroupsDAO {
 
         return jdbcTemplate.query(conn -> {
 
-            String sql = "SELECT id, name, path, is_leaf from gms_group WHERE id IN (";
+            String sql = "SELECT id, name, path, is_leaf, locked from gms_group WHERE id IN (";
             sql += String.join(",", identifiers.stream().map(p -> "?").collect(Collectors.toList()));
             sql += ")";
 
@@ -150,6 +152,7 @@ public class GroupsDAO {
                 group.setName(resultSet.getString("name"));
                 group.setPath(resultSet.getString("path"));
                 group.setLeaf(resultSet.getBoolean("is_leaf"));
+                group.setLocked(resultSet.getBoolean("locked"));
                 groups.add(group);
             }
             return groups;
@@ -158,7 +161,7 @@ public class GroupsDAO {
 
     public Optional<GroupEntity> findGroupByParentAndName(String parentPath, String childName) {
 
-        String sql = "SELECT id, path, is_leaf from gms_group WHERE name = ? AND path ~ ?";
+        String sql = "SELECT id, path, is_leaf, locked from gms_group WHERE name = ? AND path ~ ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
@@ -172,6 +175,7 @@ public class GroupsDAO {
                 group.setName(childName);
                 group.setPath(resultSet.getString("path"));
                 group.setLeaf(resultSet.getBoolean("is_leaf"));
+                group.setLocked(resultSet.getBoolean("locked"));
                 return Optional.of(group);
             }
             return Optional.empty();
@@ -191,9 +195,9 @@ public class GroupsDAO {
 
         String sql;
         if (hasSearchFilter) {
-            sql = "SELECT id, name, path, is_leaf FROM gms_group WHERE path ~ ? AND name ILIKE ? ORDER BY name";
+            sql = "SELECT id, name, path, is_leaf, locked FROM gms_group WHERE path ~ ? AND name ILIKE ? ORDER BY name";
         } else {
-            sql = "SELECT id, name, path, is_leaf FROM gms_group WHERE path ~ ? ORDER BY name";
+            sql = "SELECT id, name, path, is_leaf, locked FROM gms_group WHERE path ~ ? ORDER BY name";
         }
 
         return jdbcTemplate.query(conn -> {
@@ -218,7 +222,7 @@ public class GroupsDAO {
 
     public List<GroupEntity> getAllChildren(String path) {
 
-        String sql = "SELECT id, name, path, is_leaf FROM gms_group WHERE path <@ ? AND path <> ? ORDER BY nlevel(path) DESC";
+        String sql = "SELECT id, name, path, is_leaf, locked FROM gms_group WHERE path <@ ? AND path <> ? ORDER BY nlevel(path) DESC";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
@@ -232,7 +236,7 @@ public class GroupsDAO {
 
     public List<GroupEntity> findGroupsByNames(List<String> names) {
 
-        String sql = "SELECT id, name, path, is_leaf from gms_group WHERE name IN ("
+        String sql = "SELECT id, name, path, is_leaf, locked from gms_group WHERE name IN ("
                 + String.join(",", names.stream().map(g -> "?").collect(Collectors.toList()))
                 + ")";
 
@@ -256,6 +260,7 @@ public class GroupsDAO {
             group.setName(resultSet.getString("name"));
             group.setPath(resultSet.getString("path"));
             group.setLeaf(resultSet.getBoolean("is_leaf"));
+            group.setLocked(resultSet.getBoolean("locked"));
             groups.add(group);
         }
         return groups;
@@ -326,7 +331,7 @@ public class GroupsDAO {
 
     public List<GroupEntity> searchGroups(String searchText) {
 
-        String sql = "SELECT id, name, path, is_leaf from gms_group WHERE name ILIKE ?";
+        String sql = "SELECT id, name, path, is_leaf, locked from gms_group WHERE name ILIKE ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java
index af61db9bfdb9fbb3f51333897cc42b15d73fe29e..43415d614786612d41c30029d7253ea1d76038a6 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java
@@ -4,6 +4,9 @@ import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.persistence.model.InvitedRegistration;
 import java.sql.PreparedStatement;
 import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +57,7 @@ public class InvitedRegistrationDAO {
 
     public Optional<InvitedRegistration> getInvitedRegistrationFromToken(String tokenHash) {
 
-        String sqlReq = "SELECT id, email FROM invited_registration_request WHERE token_hash = ? AND done IS NOT true";
+        String sqlReq = "SELECT id, email, creation_time FROM invited_registration_request WHERE token_hash = ? AND done IS NOT true";
 
         InvitedRegistration registration = jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sqlReq);
@@ -65,6 +68,7 @@ public class InvitedRegistrationDAO {
                 InvitedRegistration reg = new InvitedRegistration();
                 reg.setId(resultSet.getString("id"));
                 reg.setEmail(resultSet.getString("email"));
+                reg.setCreationTime(new Date(resultSet.getDate("creation_time").getTime()));
                 return reg;
             }
             return null;
@@ -126,7 +130,7 @@ public class InvitedRegistrationDAO {
             return;
         }
 
-        String sql = "DELETE FROM invited_registration_request_group WHERE group_id = ("
+        String sql = "DELETE FROM invited_registration_request_group WHERE group_id IN ("
                 + String.join(",", groupIds.stream().map(g -> "?").collect(Collectors.toList()))
                 + ")";
 
@@ -143,4 +147,66 @@ public class InvitedRegistrationDAO {
         jdbcTemplate.update("DELETE FROM invited_registration_request WHERE id NOT IN "
                 + "(SELECT request_id FROM invited_registration_request_group)");
     }
+
+    public List<InvitedRegistration> getPendingInvitedRegistrationsForGroup(String groupId) {
+
+        String sql = "SELECT id, email, creation_time, permission\n"
+                + "FROM invited_registration_request r\n"
+                + "JOIN invited_registration_request_group rg ON r.id = rg.request_id\n"
+                + "WHERE done IS NOT TRUE AND rg.group_id = ?";
+
+        return jdbcTemplate.query(sql,
+                ps -> {
+                    ps.setString(1, groupId);
+                },
+                rs -> {
+                    // key: id
+                    Map<String, InvitedRegistration> map = new HashMap<>();
+
+                    while (rs.next()) {
+
+                        String id = rs.getString("id");
+
+                        InvitedRegistration reg = map.get(id);
+                        if (reg == null) {
+                            String email = rs.getString("email");
+                            Date creationTime = new Date(rs.getDate("creation_time").getTime());
+                            reg = new InvitedRegistration()
+                                    .setId(id)
+                                    .setEmail(email)
+                                    .setCreationTime(creationTime);
+                            map.put(id, reg);
+                        }
+
+                        if (reg.getGroupsPermissions() == null) {
+                            reg.setGroupsPermissions(new HashMap<>());
+                        }
+
+                        Permission permission = Permission.valueOf(rs.getString("permission"));
+
+                        reg.getGroupsPermissions().put(groupId, permission);
+                    }
+
+                    List<InvitedRegistration> registrations = new ArrayList<>(map.values());
+
+                    Collections.sort(registrations, (reg1, reg2) -> reg1.getEmail().compareToIgnoreCase(reg2.getEmail()));
+
+                    return registrations;
+                });
+    }
+
+    public void deleteInvitedRegistrationRequest(String requestId, String groupId) {
+
+        String sql = "DELETE FROM invited_registration_request_group\n"
+                + "WHERE request_id = ? AND group_id = ?";
+
+        jdbcTemplate.update(sql, ps -> {
+            ps.setString(1, requestId);
+            ps.setString(2, groupId);
+        });
+
+        // Cleanup orphan invited requests
+        jdbcTemplate.update("DELETE FROM invited_registration_request WHERE id NOT IN "
+                + "(SELECT request_id FROM invited_registration_request_group)");
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
index 1dc3ed1f06e1f4a2b5199137706bf4c56deda296..20590cfc59a0922d54a234ccc90e584f054ba660 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
@@ -8,6 +8,7 @@ public class GroupEntity {
     private String name;
     private String path;
     private boolean leaf;
+    private boolean locked;
 
     public String getId() {
         return id;
@@ -41,6 +42,14 @@ public class GroupEntity {
         this.leaf = leaf;
     }
 
+    public boolean isLocked() {
+        return locked;
+    }
+
+    public void setLocked(boolean locked) {
+        this.locked = locked;
+    }
+
     public String getParentPath() {
         if (path.isEmpty()) {
             return null;
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/InvitedRegistration.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/InvitedRegistration.java
index e7a922be8d7c773046469dc3dc81c8cf0530514d..9acc15e7a07c5e692306fc2158125edf314df99b 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/InvitedRegistration.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/InvitedRegistration.java
@@ -1,6 +1,7 @@
 package it.inaf.ia2.gms.persistence.model;
 
 import it.inaf.ia2.gms.model.Permission;
+import java.util.Date;
 import java.util.Map;
 
 public class InvitedRegistration {
@@ -10,6 +11,7 @@ public class InvitedRegistration {
     private String email;
     private boolean done;
     private String userId;
+    private Date creationTime;
     private Map<String, Permission> groupsPermissions;
 
     public String getId() {
@@ -52,8 +54,18 @@ public class InvitedRegistration {
         return userId;
     }
 
-    public void setUserId(String userId) {
+    public InvitedRegistration setUserId(String userId) {
         this.userId = userId;
+        return this;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public InvitedRegistration setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+        return this;
     }
 
     public Map<String, Permission> getGroupsPermissions() {
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 14d503ee566fb2b779e08a9542d893bf75e717e6..f38717ff2f278b75e0dc74f9264a638e7ae1ae2f 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
@@ -99,6 +99,10 @@ public class GroupsService {
             throw new UnauthorizedException("It is not possible to remove the ROOT");
         }
 
+        if (group.isLocked()) {
+            throw new UnauthorizedException("Group " + group.getId() + " is locked and can't be deleted");
+        }
+
         String parentPath = group.getParentPath();
         GroupEntity parent = groupsDAO.findGroupByPath(parentPath)
                 .orElseThrow(() -> new BadRequestException("No group found at path " + parentPath));
@@ -117,7 +121,7 @@ public class GroupsService {
             groupsDAO.deleteGroup(g);
         }
 
-        loggingDAO.logAction("Group deleted, group_id=" + group.getId());
+        loggingDAO.logAction("Group deleted [group_id=" + group.getId() + ", group_name=" + group.getName() + "]");
 
         return parent;
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
index 80c5384d7e723e51685d97f9c7238c6b44586ecd..e3c214559f241ef428d0c6f21fd28333d3806a1d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
@@ -60,6 +60,7 @@ public class GroupsTreeBuilder {
                 node.setGroupId(group.getId());
                 node.setGroupName(group.getName());
                 node.setLeaf(group.isLeaf());
+                node.setLocked(group.isLocked());
                 node.setPermission(permission);
                 nodes.add(node);
             });
diff --git a/gms/src/main/resources/sql/init.sql b/gms/src/main/resources/sql/init.sql
index 6f4693efa54d57449bc7fc47e8a6df3237885656..05aec53ca465b3b7fe8241685973524e74e2daae 100644
--- a/gms/src/main/resources/sql/init.sql
+++ b/gms/src/main/resources/sql/init.sql
@@ -5,6 +5,7 @@ CREATE TABLE gms_group (
   name text NOT NULL,
   path ltree NOT NULL,
   is_leaf boolean,
+  locked boolean,
   primary key(id)
 );
 
diff --git a/gms/src/main/resources/static/help/help-admin.html b/gms/src/main/resources/static/help/help-admin.html
index f452453aba0beea927dad9fcbb399d1b4fcc2daf..093446d5b07012a309b9e922a0ba8cfa9afc1f69 100644
--- a/gms/src/main/resources/static/help/help-admin.html
+++ b/gms/src/main/resources/static/help/help-admin.html
@@ -102,6 +102,14 @@
                 You can also use the edit icon (<img src="img/pencil-icon.jpg" alt="" />)
                 in order to change a permission.</p>
 
+            <h2 class="mt-4">Pending invited registrations</h2>
+            
+            <p>If a registration invitation has been sent and the user has not yet registered an additional tab appears:</p>
+            
+            <p>
+                <img src="img/gms-invited-panel.jpg" alt="" />
+            </p>
+            
             <h2 class="mt-4">Seeing information about users</h2>
 
             <p>You can click on the user names in the Members or Permissions tab in order to see a detailed page about a specific user.</p>
diff --git a/gms/src/main/resources/static/help/img/gms-invited-panel.jpg b/gms/src/main/resources/static/help/img/gms-invited-panel.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3f2b7d1cbe2bbac8dd040dfe569ffa9f99748b3c
Binary files /dev/null and b/gms/src/main/resources/static/help/img/gms-invited-panel.jpg differ
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
index c4ac899f69c721adb51db8354ef17162370d05e8..937fbe74b33dd9e0756099bcb62d47f14a00ffb1 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsTabResponseBuilderTest.java
@@ -2,6 +2,7 @@ package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.manager.GroupsManager;
+import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
 import it.inaf.ia2.gms.manager.PermissionsManager;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
@@ -31,7 +32,7 @@ public class GroupsTabResponseBuilderTest {
 
     @Mock
     private GroupsManager groupsManager;
-    
+
     @Mock
     private GroupsService groupsService;
 
@@ -41,6 +42,9 @@ public class GroupsTabResponseBuilderTest {
     @Mock
     private GroupsTreeBuilder groupsTreeBuilder;
 
+    @Mock
+    private InvitedRegistrationManager invitedRegistrationManager;
+
     @InjectMocks
     private GroupsTabResponseBuilder groupsTabResponseBuilder;
 
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/InvitedRegistrationControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/InvitedRegistrationControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6c1c2e5a295afe822145b7f7f2c85e22a72a566
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/InvitedRegistrationControllerTest.java
@@ -0,0 +1,44 @@
+package it.inaf.ia2.gms.controller;
+
+import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.mockito.ArgumentMatchers.eq;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@RunWith(MockitoJUnitRunner.class)
+public class InvitedRegistrationControllerTest {
+
+    @Mock
+    private InvitedRegistrationManager manager;
+
+    @InjectMocks
+    private InvitedRegistrationController controller;
+
+    private MockMvc mockMvc;
+
+    @Before
+    public void init() {
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+    @Test
+    public void testDeleteInvitedRegistration() throws Exception {
+
+        mockMvc.perform(delete("/registration?request_id=req1&group_id=group1"))
+                .andDo(print())
+                .andExpect(status().isNoContent());
+
+        verify(manager, times(1)).deleteInvitedRegistration(eq("req1"), eq("group1"));
+    }
+}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..555f27b6b5d992148d60e315b2191955e0941460
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java
@@ -0,0 +1,95 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.DataSourceConfig;
+import it.inaf.ia2.gms.HooksConfig;
+import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.InvitedRegistration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.sql.DataSource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {DataSourceConfig.class, HooksConfig.class})
+public class InvitedRegistrationDAOTest {
+
+    @Autowired
+    private DataSource dataSource;
+
+    private GroupsDAO groupsDAO;
+
+    private InvitedRegistrationDAO dao;
+
+    @Before
+    public void setUp() {
+        groupsDAO = new GroupsDAO(dataSource);
+        dao = new InvitedRegistrationDAO(dataSource);
+    }
+
+    @Test
+    public void test() {
+
+        GroupEntity root = new GroupEntity();
+        root.setId("ROOT");
+        root.setName("ROOT");
+        root.setPath("");
+        groupsDAO.createGroup(root);
+
+        GroupEntity group1 = new GroupEntity();
+        group1.setId("group1");
+        group1.setName("Group1");
+        group1.setPath(group1.getId());
+        groupsDAO.createGroup(group1);
+
+        GroupEntity group2 = new GroupEntity();
+        group2.setId("group2");
+        group2.setName("Group2");
+        group2.setPath(group2.getId());
+        groupsDAO.createGroup(group2);
+
+        Map<String, Permission> groupsPermissions = new HashMap<>();
+        groupsPermissions.put("group1", Permission.VIEW_MEMBERS);
+        groupsPermissions.put("group2", Permission.MANAGE_MEMBERS);
+
+        InvitedRegistration reg = new InvitedRegistration()
+                .setId("id1")
+                .setEmail("test@inaf.it")
+                .setTokenHash("token_hash")
+                .setGroupsPermissions(groupsPermissions);
+
+        dao.addInvitedRegistration(reg);
+
+        InvitedRegistration regFromToken = dao.getInvitedRegistrationFromToken("token_hash").get();
+        assertEquals(reg.getId(), regFromToken.getId());
+        assertEquals(reg.getEmail(), regFromToken.getEmail());
+        assertNotNull(regFromToken.getCreationTime());
+
+        InvitedRegistration regFromGroup = dao.getPendingInvitedRegistrationsForGroup("group1").get(0);
+        assertEquals(reg.getId(), regFromGroup.getId());
+        assertEquals(reg.getEmail(), regFromGroup.getEmail());
+        assertNotNull(regFromGroup.getCreationTime());
+
+        dao.setRegistrationDone(regFromGroup);
+
+        assertTrue(dao.getPendingInvitedRegistrationsForGroup("group1").isEmpty());
+
+        List<String> groupsIds = new ArrayList<>();
+        groupsIds.add(group1.getId());
+        groupsIds.add(group2.getId());
+        dao.deleteAllGroupsInvitedRegistrations(groupsIds);
+
+        groupsDAO.deleteGroup(group1);
+        groupsDAO.deleteGroup(group2);
+    }
+}