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/05/12 07:18:41 UTC

[openmeetings] 02/02: [OPENMEETINGS-2773] login/email should be case insensitive

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 4de8f7c0a3622203eff02f5c707280749ebfe87e
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Fri May 12 14:18:27 2023 +0700

    [OPENMEETINGS-2773] login/email should be case insensitive
---
 .../apache/openmeetings/db/dao/user/UserDao.java   | 27 ++++----
 .../apache/openmeetings/db/entity/user/User.java   | 74 ++++++++++++++++++----
 .../apache/openmeetings/userdata/TestLogin.java    | 27 ++++++++
 3 files changed, 101 insertions(+), 27 deletions(-)

diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
index 6a0eb6497..41ecfd549 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
@@ -22,6 +22,7 @@ import static java.util.UUID.randomUUID;
 import static org.apache.openmeetings.db.util.DaoHelper.fillLazy;
 import static org.apache.openmeetings.db.util.DaoHelper.getRoot;
 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 static org.apache.openmeetings.db.util.DaoHelper.single;
 import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
@@ -45,7 +46,6 @@ import java.util.Set;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
-import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.JoinType;
@@ -434,7 +434,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 		return !Strings.isEmpty(login) && login.length() >= getMinLoginLength();
 	}
 
-	public User getByLogin(String login, Type type, Long domainId) {
+	public User getByLogin(String _login, Type type, Long domainId) {
+		String login = _login == null ? null : _login.trim().toLowerCase(Locale.ROOT);
 		return single(fillLazy(em
 				, oem -> oem.createNamedQuery("getUserByLogin", User.class)
 					.setParameter("login", login)
@@ -447,7 +448,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 		return getByEmail(email, User.Type.USER, null);
 	}
 
-	public User getByEmail(String email, User.Type type, Long domainId) {
+	public User getByEmail(String _email, User.Type type, Long domainId) {
+		String email = _email == null ? null : _email.trim().toLowerCase(Locale.ROOT);
 		return single(fillLazy(em
 				, oem -> oem.createNamedQuery("getUserByEmail", User.class)
 					.setParameter(PARAM_EMAIL, email)
@@ -474,9 +476,9 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	public Long selectMaxFromUsersWithSearch(String search) {
 		try {
 			// get all users
-			TypedQuery<Long> query = em.createNamedQuery("selectMaxFromUsersWithSearch", Long.class);
-			query.setParameter("search", StringUtils.lowerCase(search, Locale.ROOT));
-			List<Long> ll = query.getResultList();
+			List<Long> ll = em.createNamedQuery("selectMaxFromUsersWithSearch", Long.class)
+					.setParameter("search", StringUtils.lowerCase(search, Locale.ROOT))
+					.getResultList();
 			log.info("selectMaxFromUsers {}", ll.get(0));
 			return ll.get(0);
 		} catch (Exception ex2) {
@@ -493,12 +495,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	 * @return <code>true</code> if entered password is correct
 	 */
 	public boolean verifyPassword(Long userId, String password) {
-		List<String> l = em.createNamedQuery("getPassword", String.class)
-			.setParameter(PARAM_USER_ID, userId).getResultList();
-		if (l == null || l.size() != 1) {
-			return false;
-		}
-		String hash = l.get(0);
+		String hash = only(em.createNamedQuery("getPassword", String.class)
+				.setParameter(PARAM_USER_ID, userId).getResultList());
 		ICrypt crypt = CryptProvider.get();
 		if (crypt.verify(password, hash)) {
 			return true;
@@ -605,7 +603,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	 * @return User object in case of successful login
 	 * @throws OmException in case of any issue
 	 */
-	public User login(String userOrEmail, String userpass) throws OmException {
+	public User login(String _userOrEmail, String userpass) throws OmException {
+		String userOrEmail = _userOrEmail == null ? null : _userOrEmail.trim().toLowerCase(Locale.ROOT);
 		List<User> users = em.createNamedQuery("getUserByLoginOrEmail", User.class)
 				.setParameter("userOrEmail", userOrEmail)
 				.setParameter("type", Type.USER)
@@ -614,7 +613,7 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 		log.debug("login:: {} users were found", users.size());
 
 		if (users.isEmpty()) {
-			log.debug("No users was found: {}", userOrEmail);
+			log.debug("No users were found: {}", userOrEmail);
 			return null;
 		}
 		User u = users.get(0);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
index ed3365a82..7bf3340fc 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
@@ -100,26 +100,74 @@ import org.apache.wicket.util.string.Strings;
 })
 @NamedQuery(name = "getUserById", query = "SELECT u FROM User u WHERE u.id = :id")
 @NamedQuery(name = "getUsersByIds", query = "select c from User c where c.id IN :ids")
-@NamedQuery(name = "getUserByLogin", query = "SELECT u FROM User u WHERE u.deleted = false AND u.type = :type AND u.login = :login AND ((:domainId = 0 AND u.domainId IS NULL) OR (:domainId > 0 AND u.domainId = :domainId))")
-@NamedQuery(name = "getUserByEmail", query = "SELECT u FROM User u WHERE u.deleted = false AND u.type = :type AND u.address.email = :email AND ((:domainId = 0 AND u.domainId IS NULL) OR (:domainId > 0 AND u.domainId = :domainId))")
-@NamedQuery(name = "getUserByHash",  query = "SELECT u FROM User u WHERE u.deleted = false AND u.type = :type AND u.resethash = :resethash")
+@NamedQuery(name = "getUserByLogin", query = """
+	SELECT u
+	FROM User u
+	WHERE
+		u.deleted = false
+		AND u.type = :type
+		AND lower(trim(both from u.login)) = :login
+		AND ((:domainId = 0 AND u.domainId IS NULL) OR (:domainId > 0 AND u.domainId = :domainId))""")
+@NamedQuery(name = "getUserByEmail", query = """
+	SELECT u
+	FROM User u
+	WHERE
+		u.deleted = false
+		AND u.type = :type
+		AND lower(trim(both from u.address.email)) = :email
+		AND ((:domainId = 0 AND u.domainId IS NULL) OR (:domainId > 0 AND u.domainId = :domainId))""")
+@NamedQuery(name = "getUserByHash",  query = """
+	SELECT u
+	FROM User u
+	WHERE
+		u.deleted = false
+		AND u.type = :type
+		AND u.resethash = :resethash""")
 @NamedQuery(name = "getUserByExpiredHash",  query = "SELECT u FROM User u WHERE u.resetDate < :date")
-@NamedQuery(name = "getContactByEmailAndUser", query = "SELECT u FROM User u WHERE u.deleted = false AND u.address.email = :email AND u.type = :type AND u.ownerId = :ownerId")
-@NamedQuery(name = "selectMaxFromUsersWithSearch", query = "SELECT count(c.id) FROM User c "
-		+ "WHERE c.deleted = false AND ("
-		+ "lower(c.login) LIKE :search "
-		+ "OR lower(c.firstname) LIKE :search "
-		+ "OR lower(c.lastname) LIKE :search )")
+@NamedQuery(name = "getContactByEmailAndUser", query = """
+	SELECT u
+	FROM User u
+	WHERE
+		u.deleted = false
+		AND u.address.email = :email
+		AND u.type = :type
+		AND u.ownerId = :ownerId""")
+@NamedQuery(name = "selectMaxFromUsersWithSearch", query = """
+	SELECT count(c.id)
+	FROM User c
+	WHERE
+		c.deleted = false
+		AND (
+			lower(c.login) LIKE :search
+			OR lower(c.firstname) LIKE :search
+			OR lower(c.lastname) LIKE :search
+		)""")
 @NamedQuery(name = "getAllUsers", query = "SELECT u FROM User u ORDER BY u.id")
 @NamedQuery(name = "getPassword", query = "SELECT u.password FROM User u WHERE u.deleted = false AND u.id = :userId ")
 @NamedQuery(name = "updatePassword", query = "UPDATE User u SET u.password = :password WHERE u.id = :userId")
 @NamedQuery(name = "getNondeletedUsers", query = "SELECT u FROM User u WHERE u.deleted = false")
 @NamedQuery(name = "countNondeletedUsers", query = "SELECT COUNT(u) FROM User u WHERE u.deleted = false")
 @NamedQuery(name = "getUsersByGroupId", query = "SELECT u FROM User u WHERE u.deleted = false AND u.groupUsers.group.id = :groupId")
-@NamedQuery(name = "getExternalUser", query = "SELECT gu.user FROM GroupUser gu WHERE "
-		+ "gu.group.deleted = false AND gu.group.external = true AND gu.group.name = :externalType "
-		+ "AND gu.user.deleted = false AND gu.user.type = :type AND gu.user.externalId = :externalId")
-@NamedQuery(name = "getUserByLoginOrEmail", query = "SELECT u from User u WHERE u.deleted = false AND u.type = :type AND (u.login = :userOrEmail OR u.address.email = :userOrEmail)")
+@NamedQuery(name = "getExternalUser", query = """
+	SELECT gu.user
+	FROM GroupUser gu
+	WHERE
+		gu.group.deleted = false
+		AND gu.group.external = true
+		AND gu.group.name = :externalType
+		AND gu.user.deleted = false
+		AND gu.user.type = :type
+		AND gu.user.externalId = :externalId""")
+@NamedQuery(name = "getUserByLoginOrEmail", query = """
+	SELECT u
+	FROM User u
+	WHERE
+		u.deleted = false
+		AND u.type = :type
+		AND (
+			lower(trim(both from u.login)) = :userOrEmail
+			OR lower(trim(both from u.address.email)) = :userOrEmail
+		)""")
 @Table(name = "om_user", indexes = {
 		@Index(name = "login_idx", columnList = "login")
 		, @Index(name = "lastname_idx", columnList = "lastname")
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/userdata/TestLogin.java b/openmeetings-web/src/test/java/org/apache/openmeetings/userdata/TestLogin.java
index 43133d1ad..5b905b546 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/userdata/TestLogin.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/userdata/TestLogin.java
@@ -18,8 +18,11 @@
  */
 package org.apache.openmeetings.userdata;
 
+import static java.util.UUID.randomUUID;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import java.util.Locale;
+
 import org.apache.openmeetings.AbstractOmServerTest;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.util.OmException;
@@ -32,4 +35,28 @@ class TestLogin extends AbstractOmServerTest {
 		assertNotNull(us, "User is unable to login");
 	}
 
+	private User prepareUser() throws Exception {
+		User u = getUser(randomUUID().toString());
+		u.setLogin(" AB" + u.getLogin() + " ");
+		u.getAddress().setEmail(" CD_" + u.getAddress().getEmail() + " ");
+		u.updatePassword(userpass);
+		u.addGroup(groupDao.get(group));
+		return userDao.update(u, null);
+	}
+
+	@Test
+	void testMixedCaseLogin() throws Exception {
+		final String login = prepareUser().getLogin();
+
+		User us = userDao.login(login.toUpperCase(Locale.ROOT), userpass);
+		assertNotNull(us, "Uppercase User is unable to login");
+	}
+
+	@Test
+	void testMixedCaseEmail() throws Exception {
+		final String email = prepareUser().getAddress().getEmail();
+
+		User us = userDao.login(email.toUpperCase(Locale.ROOT), userpass);
+		assertNotNull(us, "Uppercase Email is unable to login");
+	}
 }