You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2023/03/28 09:18:25 UTC

[openmeetings] 02/02: [OPENMEETINGS-2762] Invitation hash check is more strict

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git

commit a28dea888fca1c5c3e0ce4c8a4c62f501aebe0cd
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Tue Mar 28 16:18:08 2023 +0700

    [OPENMEETINGS-2762] Invitation hash check is more strict
---
 .../db/dao/calendar/AppointmentDao.java            | 11 ++--
 .../db/dao/calendar/MeetingMemberDao.java          |  7 +--
 .../db/dao/calendar/OmCalendarDao.java             |  6 +--
 .../openmeetings/db/dao/file/FileItemDao.java      |  6 +--
 .../db/dao/record/RecordingChunkDao.java           |  7 +--
 .../openmeetings/db/dao/room/InvitationDao.java    | 16 +++---
 .../apache/openmeetings/db/dao/room/PollDao.java   |  6 +--
 .../openmeetings/db/dao/server/LdapConfigDao.java  |  6 +--
 .../openmeetings/db/dao/server/OAuth2Dao.java      | 15 +++---
 .../openmeetings/db/dao/server/SOAPLoginDao.java   | 14 ++---
 .../apache/openmeetings/db/dao/user/GroupDao.java  |  6 +--
 .../db/dao/user/PrivateMessageDao.java             |  6 +--
 .../db/dao/user/PrivateMessageFolderDao.java       | 12 ++---
 .../openmeetings/db/dao/user/UserContactDao.java   | 12 ++---
 .../org/apache/openmeetings/db/util/DaoHelper.java | 10 ++++
 .../apache/openmeetings/util/mail/IcalHandler.java |  2 +-
 .../openmeetings/invitiation/TestInvitation.java   | 59 ++++++++++++++--------
 17 files changed, 114 insertions(+), 87 deletions(-)

diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
index 0ad2a752b..209860fb7 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
@@ -19,6 +19,7 @@
 package org.apache.openmeetings.db.dao.calendar;
 
 import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.UNSUPPORTED;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_CALENDAR_ROOM_CAPACITY;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
@@ -75,15 +76,13 @@ public class AppointmentDao implements IDataProviderDao<Appointment>{
 	// -----------------------------------------------------------------------------------------------
 	@Override
 	public Appointment get(Long id) {
-		List<Appointment> list = em.createNamedQuery("getAppointmentById", Appointment.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getAppointmentById", Appointment.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public Appointment getAny(Long id) {
-		List<Appointment> list = em.createNamedQuery("getAppointmentByIdAny", Appointment.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getAppointmentByIdAny", Appointment.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public List<Appointment> get() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
index 56569cd94..a45c24663 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
@@ -18,6 +18,8 @@
  */
 package org.apache.openmeetings.db.dao.calendar;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -39,9 +41,8 @@ public class MeetingMemberDao {
 	private EntityManager em;
 
 	public MeetingMember get(Long id) {
-		List<MeetingMember> list = em.createNamedQuery("getMeetingMemberById", MeetingMember.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getMeetingMemberById", MeetingMember.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public List<MeetingMember> get() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/OmCalendarDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/OmCalendarDao.java
index 48fe917b5..21f9c834c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/OmCalendarDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/OmCalendarDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.calendar;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.UNSUPPORTED;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
 
@@ -53,9 +54,8 @@ public class OmCalendarDao implements IDataProviderDao<OmCalendar> {
 	 */
 	@Override
 	public OmCalendar get(Long calId) {
-		List<OmCalendar> list = em.createNamedQuery("getCalendarbyId", OmCalendar.class)
-				.setParameter("calId", calId).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getCalendarbyId", OmCalendar.class)
+				.setParameter("calId", calId).getResultList());
 	}
 
 	/**
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
index ad97f20eb..e3e5d3485 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.file;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 
 import java.io.File;
@@ -103,10 +104,9 @@ public class FileItemDao extends BaseFileItemDao {
 	public FileItem get(String externalId, String externalType) {
 		log.debug("get started");
 
-		List<FileItem> list = em.createNamedQuery("getFileExternal", FileItem.class)
+		return only(em.createNamedQuery("getFileExternal", FileItem.class)
 				.setParameter("externalFileId", externalId).setParameter("externalType", externalType)
-				.getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+				.getResultList());
 	}
 
 	public List<FileItem> get() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingChunkDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingChunkDao.java
index bd24a8d0a..bc09bb106 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingChunkDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingChunkDao.java
@@ -18,6 +18,8 @@
  */
 package org.apache.openmeetings.db.dao.record;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
+
 import java.util.Date;
 import java.util.List;
 
@@ -44,9 +46,8 @@ public class RecordingChunkDao {
 	private RecordingDao recordingDao;
 
 	public RecordingChunk get(Long id) {
-		List<RecordingChunk> list = em.createNamedQuery("getChunkById", RecordingChunk.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getChunkById", RecordingChunk.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public List<RecordingChunk> getByRecording(Long recordingId) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/InvitationDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/InvitationDao.java
index 7c7aa4972..54c7b015c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/InvitationDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/InvitationDao.java
@@ -19,6 +19,7 @@
 package org.apache.openmeetings.db.dao.room;
 
 import static org.apache.openmeetings.db.util.DaoHelper.getRoot;
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.util.CalendarHelper.getZoneId;
 
 import java.time.LocalDateTime;
@@ -61,9 +62,8 @@ public class InvitationDao implements IDataProviderDao<Invitation> {
 
 	@Override
 	public Invitation get(Long invId) {
-		List<Invitation> list = em.createNamedQuery("getInvitationbyId", Invitation.class)
-				.setParameter("id", invId).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getInvitationbyId", Invitation.class)
+				.setParameter("id", invId).getResultList());
 	}
 
 	@Override
@@ -159,10 +159,14 @@ public class InvitationDao implements IDataProviderDao<Invitation> {
 		}
 	}
 
+	private Invitation get(String hash) {
+		Invitation i = only(em.createNamedQuery("getInvitationByHashCode", Invitation.class)
+				.setParameter("hashCode", hash).getResultList());
+		return i != null && i.getHash().equals(hash) ? i : null;
+	}
+
 	public Invitation getByHash(String hash, boolean hidePass) {
-		List<Invitation> list = em.createNamedQuery("getInvitationByHashCode", Invitation.class)
-				.setParameter("hashCode", hash).getResultList();
-		Invitation i = list != null && list.size() == 1 ? list.get(0) : null;
+		Invitation i = get(hash);
 		if (i != null) {
 			switch (i.getValid()) {
 				case ONE_TIME:
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
index 0210e27bb..c2d42b649 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.room;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
 
 import java.util.Date;
@@ -85,9 +86,8 @@ public class PollDao {
 
 	public RoomPoll getByRoom(Long roomId) {
 		log.debug(" :: getPoll :: {}", roomId);
-		List<RoomPoll> list = em.createNamedQuery("getPoll", RoomPoll.class)
-				.setParameter(PARAM_ROOMID, roomId).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getPoll", RoomPoll.class)
+				.setParameter(PARAM_ROOMID, roomId).getResultList());
 	}
 
 	public List<RoomPoll> get() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
index 91cb12e1e..d2a6a5ea0 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.server;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 
 import java.util.ArrayList;
@@ -60,9 +61,8 @@ public class LdapConfigDao implements IDataProviderDao<LdapConfig> {
 
 	@Override
 	public LdapConfig get(Long id) {
-		List<LdapConfig> list = em.createNamedQuery("getLdapConfigById", LdapConfig.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getLdapConfigById", LdapConfig.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public List<LdapConfig> getActive() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/OAuth2Dao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/OAuth2Dao.java
index a81d76bea..c626c510c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/OAuth2Dao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/OAuth2Dao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.server;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.isAllowRegisterOauth;
 
@@ -25,7 +26,6 @@ import java.util.List;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
-import javax.persistence.TypedQuery;
 
 import org.apache.openmeetings.db.dao.IDataProviderDao;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
@@ -50,15 +50,14 @@ public class OAuth2Dao implements IDataProviderDao<OAuthServer> {
 		if (!isAllowRegisterOauth()) {
 			return List.of();
 		}
-		TypedQuery<OAuthServer> query = em.createNamedQuery("getEnabledOAuthServers", OAuthServer.class);
-		return query.getResultList();
+		return em.createNamedQuery("getEnabledOAuthServers", OAuthServer.class)
+				.getResultList();
 	}
 
 	@Override
 	public OAuthServer get(Long id) {
-		List<OAuthServer> list = em.createNamedQuery("getOAuthServerById", OAuthServer.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getOAuthServerById", OAuthServer.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	@Override
@@ -74,8 +73,8 @@ public class OAuth2Dao implements IDataProviderDao<OAuthServer> {
 
 	@Override
 	public long count() {
-		TypedQuery<Long> q = em.createNamedQuery("countOAuthServers", Long.class);
-		return q.getSingleResult();
+		return em.createNamedQuery("countOAuthServers", Long.class)
+				.getSingleResult();
 	}
 
 	@Override
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SOAPLoginDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SOAPLoginDao.java
index 899684315..5dbeab254 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SOAPLoginDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SOAPLoginDao.java
@@ -19,9 +19,9 @@
 package org.apache.openmeetings.db.dao.server;
 
 import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 
 import java.util.Date;
-import java.util.List;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
@@ -74,21 +74,17 @@ public class SOAPLoginDao {
 		}
 		try {
 			//MSSql find nothing in case SID is passed as-is without wildcarting '%hash%'
-			List<SOAPLogin> sList = em.createNamedQuery("getSoapLoginByHash", SOAPLogin.class)
-					.setParameter("hash", String.format("%%%s%%", hash))
-					.getResultList();
+			SOAPLogin sl = only(em.createNamedQuery("getSoapLoginByHash", SOAPLogin.class)
+					.setParameter("hash", '%' + hash + '%')
+					.getResultList());
 
-			if (sList.size() == 1) {
-				SOAPLogin sl = sList.get(0);
+			if (sl != null) {
 				if (hash.equals(sl.getHash())) {
 					return sl;
 				} else {
 					log.error("[get]: Wrong SOAPLogin was found by hash! {}", hash);
 				}
 			}
-			if (sList.size() > 1) {
-				log.error("[get]: there are more then one SOAPLogin with identical hash! {}", hash);
-			}
 		} catch (Exception ex2) {
 			log.error("[get]: ", ex2);
 		}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
index 273672f77..7c0303479 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
@@ -19,6 +19,7 @@
 package org.apache.openmeetings.db.dao.user;
 
 import static org.apache.openmeetings.db.util.DaoHelper.getRoot;
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 
 import java.util.Collection;
@@ -49,9 +50,8 @@ public class GroupDao implements IGroupAdminDataProviderDao<Group> {
 
 	@Override
 	public Group get(Long id) {
-		List<Group> list = em.createNamedQuery("getGroupById", Group.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getGroupById", Group.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public Group get(String name) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageDao.java
index 51a3e3d60..b6ad0bd7d 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageDao.java
@@ -21,6 +21,7 @@ package org.apache.openmeetings.db.dao.user;
 import static org.apache.openmeetings.db.entity.user.PrivateMessage.INBOX_FOLDER_ID;
 import static org.apache.openmeetings.db.util.DaoHelper.UNSUPPORTED;
 import static org.apache.openmeetings.db.util.DaoHelper.getStringParam;
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 
 import java.util.Collection;
@@ -82,9 +83,8 @@ public class PrivateMessageDao implements IDataProviderDao<PrivateMessage> {
 
 	@Override
 	public PrivateMessage get(Long id) {
-		List<PrivateMessage> list = em.createNamedQuery("getPrivateMessageById", PrivateMessage.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getPrivateMessageById", PrivateMessage.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	@Override
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageFolderDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageFolderDao.java
index 08fca325a..9e1607ebe 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageFolderDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/PrivateMessageFolderDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.user;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.UNSUPPORTED;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 
@@ -66,17 +67,16 @@ public class PrivateMessageFolderDao implements IDataProviderDao<PrivateMessageF
 
 	@Override
 	public PrivateMessageFolder get(Long id) {
-		final String hql = "select c from PrivateMessageFolder c where c.id = :id ";
-
-		List<PrivateMessageFolder> list = em.createQuery(hql, PrivateMessageFolder.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createQuery("select c from PrivateMessageFolder c where c.id = :id "
+					, PrivateMessageFolder.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	@Override
 	public List<PrivateMessageFolder> get(long start, long count) {
 		return setLimits(
-				em.createQuery("SELECT c FROM PrivateMessageFolder c ORDER BY c.id", PrivateMessageFolder.class)
+				em.createQuery("SELECT c FROM PrivateMessageFolder c ORDER BY c.id"
+					, PrivateMessageFolder.class)
 				, start, count)
 				.getResultList();
 	}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
index 5fdbdde1e..f6576d09e 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.db.dao.user;
 
+import static org.apache.openmeetings.db.util.DaoHelper.only;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
 
@@ -78,12 +79,10 @@ public class UserContactDao {
 	}
 
 	public UserContact get(Long userId, Long ownerId) {
-		List<UserContact> ll = em.createNamedQuery("getContactByUserOwner", UserContact.class)
+		return only(em.createNamedQuery("getContactByUserOwner", UserContact.class)
 				.setParameter(PARAM_USER_ID, userId)
 				.setParameter(PARAM_OWNERID, ownerId)
-				.getResultList();
-		log.info("number of contacts:: {}", (ll == null ? null : ll.size()));
-		return ll != null && ll.size() == 1 ? ll.get(0) : null;
+				.getResultList());
 	}
 
 	public boolean isContact(Long userId, Long ownerId) {
@@ -118,9 +117,8 @@ public class UserContactDao {
 	}
 
 	public UserContact get(Long id) {
-		List<UserContact> list = em.createNamedQuery("getUserContactsById", UserContact.class)
-				.setParameter("id", id).getResultList();
-		return list.size() == 1 ? list.get(0) : null;
+		return only(em.createNamedQuery("getUserContactsById", UserContact.class)
+				.setParameter("id", id).getResultList());
 	}
 
 	public List<UserContact> get() {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
index 9c6d96509..3e5515e7e 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
@@ -42,8 +42,11 @@ import org.apache.openjpa.persistence.OpenJPAQuery;
 import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DaoHelper {
+	private static final Logger log = LoggerFactory.getLogger(DaoHelper.class);
 	public static final UnsupportedOperationException UNSUPPORTED = new UnsupportedOperationException("Should not be used");
 
 	private DaoHelper() {}
@@ -205,6 +208,13 @@ public class DaoHelper {
 		return l.isEmpty() ? null : l.get(0);
 	}
 
+	public static <T> T only(List<T> l) {
+		if (l.size() > 1) {
+			log.error("More than one ({}) record found", l.size());
+		}
+		return l.isEmpty() ? null : l.get(0);
+	}
+
 	@SuppressWarnings("unchecked")
 	public static <T> Root<T> getRoot(CriteriaQuery<?> query, Class<T> clazz) {
 		return query.getRoots().stream()
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
index 35642ec34..0bd234f34 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
@@ -169,7 +169,7 @@ public class IcalHandler {
 			params.add(chair ? PartStat.ACCEPTED : PartStat.NEEDS_ACTION);
 			params.add(Rsvp.TRUE);
 		}
-		meetingProperties.add(new Attendee(new ParameterList(params), URI.create(MAILTO + email)));
+		meetingProperties.add(new Attendee(new ParameterList(params), getMailto(email)));
 		return this;
 	}
 
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/invitiation/TestInvitation.java b/openmeetings-web/src/test/java/org/apache/openmeetings/invitiation/TestInvitation.java
index c777733e8..43de067c1 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/invitiation/TestInvitation.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/invitiation/TestInvitation.java
@@ -20,11 +20,13 @@ package org.apache.openmeetings.invitiation;
 
 import static org.apache.openmeetings.util.CalendarHelper.getDate;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.time.LocalDateTime;
 import java.util.Date;
 
 import org.apache.openmeetings.AbstractWicketTesterTest;
+import org.apache.openmeetings.db.dao.room.InvitationDao;
 import org.apache.openmeetings.db.dao.room.RoomDao;
 import org.apache.openmeetings.db.entity.calendar.Appointment;
 import org.apache.openmeetings.db.entity.room.Invitation;
@@ -37,21 +39,41 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 class TestInvitation extends AbstractWicketTesterTest {
+	@Autowired
+	private InvitationDao inviteDao;
 	@Autowired
 	private InvitationManager invitationManager;
 	@Autowired
 	private RoomDao roomDao;
 
-	@Test
-	void testSendInvitationLink() throws Exception {
+	private Invitation create(Valid valid) {
+		return create(valid, false);
+	}
+
+	private Invitation create(Valid valid, boolean createApp) {
+		LocalDateTime base = LocalDateTime.now().plusDays(1).withHour(12).withMinute(0).withSecond(0);
 		User us = userDao.getByLogin(adminUsername, User.Type.USER, null);
 
-		LocalDateTime from = LocalDateTime.now().plusDays(1).withHour(12).withMinute(0).withSecond(0);
-		User invitee = userDao.getContact("sebawagner@apache.org", "Testname", "Testlastname", us.getId());
-		Invitation i = invitationManager.getInvitation(invitee, roomDao.get(1L),
-				false, "", Valid.ONE_TIME
+		Date from = getDate(base, "GMT");
+		Date to = getDate(base.plusHours(2), "GMT");
+		Appointment app = null;
+		Room r = roomDao.get(1L);
+		if (createApp) {
+			app = getAppointment(us, r, from, to);
+			appointmentDao.update(app, null, false);
+		}
+		return invitationManager.getInvitation(
+				userDao.getContact("sebawagner@apache.org", "Testname", "Testlastname", us.getId())
+				, r
+				, false, "", valid
 				, us, us.getLanguageId(),
-				getDate(from, "GMT"), getDate(from.plusHours(2), "GMT"), null);
+				from, to, app);
+	}
+
+
+	@Test
+	void testSendInvitationLink() throws Exception {
+		Invitation i = create(Valid.ONE_TIME);
 
 		invitationManager.sendInvitationLink(i, MessageType.CREATE, "subject", "message", false, null);
 		assertNotNull(i);
@@ -59,21 +81,18 @@ class TestInvitation extends AbstractWicketTesterTest {
 
 	@Test
 	void testSendCancel() throws Exception {
-		User us = userDao.getByLogin(adminUsername, User.Type.USER, null);
-
-		LocalDateTime from = LocalDateTime.now().plusDays(1).withHour(12).withMinute(0).withSecond(0);
-		Date start = getDate(from, "GMT");
-		Date end = getDate(from.plusHours(2), "GMT");
-		Room r = roomDao.get(1L);
-		User invitee = userDao.getContact("sebawagner@apache.org", "Testname", "Testlastname", us.getId());
-		Appointment app = getAppointment(us, r, start, end);
-		appointmentDao.update(app, null, false);
-		Invitation i = invitationManager.getInvitation(invitee, r,
-				false, "", Valid.ONE_TIME
-				, us, us.getLanguageId(),
-				start, end, app);
+		Invitation i = create(Valid.ONE_TIME, true);
 
 		invitationManager.sendInvitationLink(i, MessageType.CANCEL, "subject", "message", true, "https://localhost:5443/openmeetings");
 		assertNotNull(i);
 	}
+
+	@Test
+	void testGetByHash() {
+		final Invitation i1 = create(Valid.ENDLESS);
+		final String hash = i1.getHash();
+
+		assertNull(inviteDao.getByHash(hash.substring(0, 2) + '%', false));
+		assertNotNull(inviteDao.getByHash(hash, false));
+	}
 }