Skip to content

Commit

Permalink
Use caseinsensitive username for login and reset password
Browse files Browse the repository at this point in the history
  • Loading branch information
josegar74 committed Nov 28, 2024
1 parent 92c20ba commit 960ff24
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2016 Food and Agriculture Organization of the
* Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand Down Expand Up @@ -45,6 +45,10 @@ public interface UserRepository extends GeonetRepository<User, Integer>, JpaSpec

/**
* Find all users identified by the provided username ignoring the case.
*
* Old versions allowed to create users with the same username with different case.
* New versions do not allow this.
*
* @param username the username.
* @return all users with username equals ignore case the provided username.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2016 Food and Agriculture Organization of the
* Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand All @@ -25,7 +25,6 @@

import org.fao.geonet.domain.*;
import org.fao.geonet.utils.Log;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -60,8 +59,10 @@ public User findOneByEmail(final String email) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, String> joinedEmailAddresses = root.join(User_.emailAddresses);

query.where(cb.isMember(email, root.get(User_.emailAddresses)));
// Case in-sensitive email search
query.where( cb.equal(cb.lower(joinedEmailAddresses), email.toLowerCase()));
final List<User> resultList = _entityManager.createQuery(query).getResultList();
if (resultList.isEmpty()) {
return null;
Expand All @@ -78,10 +79,12 @@ public User findOneByEmailAndSecurityAuthTypeIsNullOrEmpty(final String email) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, String> joinedEmailAddresses = root.join(User_.emailAddresses);

final Path<String> authTypePath = root.get(User_.security).get(UserSecurity_.authType);
query.where(cb.and(
cb.isMember(email, root.get(User_.emailAddresses)),
// Case in-sensitive email search
cb.equal(cb.lower(joinedEmailAddresses), email.toLowerCase()),
cb.or(cb.isNull(authTypePath), cb.equal(cb.trim(authTypePath), ""))));
List<User> results = _entityManager.createQuery(query).getResultList();

Expand All @@ -101,7 +104,8 @@ public User findOneByUsernameAndSecurityAuthTypeIsNullOrEmpty(final String usern

final Path<String> authTypePath = root.get(User_.security).get(UserSecurity_.authType);
final Path<String> usernamePath = root.get(User_.username);
query.where(cb.and(cb.equal(usernamePath, username), cb.or(cb.isNull(authTypePath), cb.equal(cb.trim(authTypePath), ""))));
// Case in-sensitive username search
query.where(cb.and(cb.equal(cb.lower(usernamePath), username.toLowerCase()), cb.or(cb.isNull(authTypePath), cb.equal(cb.trim(authTypePath), ""))));
List<User> results = _entityManager.createQuery(query).getResultList();


Expand Down Expand Up @@ -130,7 +134,7 @@ public List<String> findDuplicatedUsernamesCaseInsensitive() {
@Nonnull
public List<Pair<Integer, User>> findAllByGroupOwnerNameAndProfile(@Nonnull final Collection<Integer> metadataIds,
@Nullable final Profile profile) {
List<Pair<Integer, User>> results = new ArrayList<Pair<Integer, User>>();
List<Pair<Integer, User>> results = new ArrayList<>();

results.addAll(findAllByGroupOwnerNameAndProfileInternal(metadataIds, profile, false));
results.addAll(findAllByGroupOwnerNameAndProfileInternal(metadataIds, profile, true));
Expand Down Expand Up @@ -180,7 +184,7 @@ private List<Pair<Integer, User>> findAllByGroupOwnerNameAndProfileInternal(@Non

query.distinct(true);

List<Pair<Integer, User>> results = new ArrayList<Pair<Integer, User>>();
List<Pair<Integer, User>> results = new ArrayList<>();

for (Tuple result : _entityManager.createQuery(query).getResultList()) {
Integer mdId = (Integer) result.get(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2016 Food and Agriculture Organization of the
* Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand Down Expand Up @@ -31,7 +31,6 @@
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -121,6 +120,11 @@ public void testFindByEmailAddress() {
assertNotNull(foundUser);
assertEquals(user2.getId(), foundUser.getId());

// Test case-insensitive
foundUser = _userRepo.findOneByEmail(add2b.toUpperCase());
assertNotNull(foundUser);
assertEquals(user2.getId(), foundUser.getId());

foundUser = _userRepo.findOneByEmail("xjkjk");
assertNull(foundUser);
}
Expand Down Expand Up @@ -150,10 +154,51 @@ public void testFindByUsernameAndAuthTypeIsNullOrEmpty() {
foundUser = _userRepo.findOneByUsernameAndSecurityAuthTypeIsNullOrEmpty(user3.getUsername());
assertNull(foundUser);

// Test case-insensitive
foundUser = _userRepo.findOneByUsernameAndSecurityAuthTypeIsNullOrEmpty(user3.getUsername().toUpperCase());
assertNull(foundUser);

foundUser = _userRepo.findOneByUsernameAndSecurityAuthTypeIsNullOrEmpty("blarg");
assertNull(foundUser);
}


@Test
public void testFindOneByEmailAndSecurityAuthTypeIsNullOrEmpty() {
User user1 = newUser();
user1.getSecurity().setAuthType("");
user1.getEmailAddresses().add("[email protected]");
user1 = _userRepo.save(user1);

User user2 = newUser();
user2.getSecurity().setAuthType(null);
user2.getEmailAddresses().add("[email protected]");
user2 = _userRepo.save(user2);

User user3 = newUser();
user3.getSecurity().setAuthType("nonull");
user3.getEmailAddresses().add("[email protected]");
_userRepo.save(user3);

User foundUser = _userRepo.findOneByEmailAndSecurityAuthTypeIsNullOrEmpty(user1.getEmail());
assertNotNull(foundUser);
assertEquals(user1.getId(), foundUser.getId());

foundUser = _userRepo.findOneByEmailAndSecurityAuthTypeIsNullOrEmpty(user2.getEmail());
assertNotNull(foundUser);
assertEquals(user2.getId(), foundUser.getId());

foundUser = _userRepo.findOneByEmailAndSecurityAuthTypeIsNullOrEmpty(user3.getEmail());
assertNull(foundUser);

// Test case-insensitive
foundUser = _userRepo.findOneByEmailAndSecurityAuthTypeIsNullOrEmpty(user3.getEmail().toUpperCase());
assertNull(foundUser);

foundUser = _userRepo.findOneByEmailAndSecurityAuthTypeIsNullOrEmpty("blarg");
assertNull(foundUser);
}

@Test
public void testFindByUsername() {
User user1 = newUser();
Expand Down Expand Up @@ -219,8 +264,8 @@ public void testFindAllByGroupOwnerNameAndProfile() {
assertEquals(4, found.size());
int md1Found = 0;
int md2Found = 0;
for (Pair<Integer, User> record : found) {
if (record.one() == md1.getId()) {
for (Pair<Integer, User> info : found) {
if (info.one() == md1.getId()) {
md1Found++;
} else {
md2Found++;
Expand Down Expand Up @@ -330,8 +375,6 @@ public void testFindDuplicatedUsernamesCaseInsensitive() {
}

private User newUser() {
User user = newUser(_inc);
return user;
return newUser(_inc);
}

}
32 changes: 21 additions & 11 deletions services/src/main/java/org/fao/geonet/api/users/PasswordApi.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//=============================================================================
//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
//=== Copyright (C) 2001-2024 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
Expand Down Expand Up @@ -27,7 +27,6 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jeeves.server.context.ServiceContext;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.api.API;
import org.fao.geonet.api.ApiUtils;
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.constants.Geonet;
Expand Down Expand Up @@ -57,6 +56,7 @@
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

Expand Down Expand Up @@ -117,8 +117,9 @@ public ResponseEntity<String> updatePassword(

ServiceContext context = ApiUtils.createServiceContext(request);

User user = userRepository.findOneByUsername(username);
if (user == null) {
List<User> existingUsers = userRepository.findByUsernameIgnoreCase(username);

if (existingUsers.isEmpty()) {
Log.warning(LOGGER, String.format("User update password. Can't find user '%s'",
username));

Expand All @@ -128,6 +129,9 @@ public ResponseEntity<String> updatePassword(
XslUtil.encodeForJavaScript(username)
), HttpStatus.PRECONDITION_FAILED);
}

User user = existingUsers.get(0);

if (LDAPConstants.LDAP_FLAG.equals(user.getSecurity().getAuthType())) {
Log.warning(LOGGER, String.format("User '%s' is authenticated using LDAP. Password can't be sent by email.",
username));
Expand Down Expand Up @@ -183,14 +187,16 @@ public ResponseEntity<String> updatePassword(
String content = localizedEmail.getParsedMessage(feedbackLocales);

// send change link via email with admin in CC
if (!MailUtil.sendMail(user.getEmail(),
Boolean mailSent = MailUtil.sendMail(user.getEmail(),
subject,
content,
null, sm,
adminEmail, "")) {
adminEmail, "");
if (Boolean.FALSE.equals(mailSent)) {
return new ResponseEntity<>(String.format(
messages.getString("mail_error")), HttpStatus.PRECONDITION_FAILED);
}

return new ResponseEntity<>(String.format(
messages.getString("user_password_changed"),
XslUtil.encodeForJavaScript(username)
Expand Down Expand Up @@ -225,8 +231,9 @@ public ResponseEntity<String> sendPasswordByEmail(

ServiceContext serviceContext = ApiUtils.createServiceContext(request);

final User user = userRepository.findOneByUsername(username);
if (user == null) {
List<User> existingUsers = userRepository.findByUsernameIgnoreCase(username);

if (existingUsers.isEmpty()) {
Log.warning(LOGGER, String.format("User reset password. Can't find user '%s'",
username));

Expand All @@ -236,6 +243,7 @@ public ResponseEntity<String> sendPasswordByEmail(
XslUtil.encodeForJavaScript(username)
), HttpStatus.CREATED);
}
User user = existingUsers.get(0);

if (LDAPConstants.LDAP_FLAG.equals(user.getSecurity().getAuthType())) {
Log.warning(LOGGER, String.format("User '%s' is authenticated using LDAP. Password can't be sent by email.",
Expand All @@ -249,7 +257,7 @@ public ResponseEntity<String> sendPasswordByEmail(
}

String email = user.getEmail();
if (StringUtils.isEmpty(email)) {
if (StringUtils.hasLength(email)) {
Log.warning(LOGGER, String.format("User reset password. User '%s' has no email",
username));

Expand Down Expand Up @@ -298,14 +306,16 @@ public ResponseEntity<String> sendPasswordByEmail(
String content = localizedEmail.getParsedMessage(feedbackLocales);

// send change link via email with admin in CC
if (!MailUtil.sendMail(email,
Boolean mailSent = MailUtil.sendMail(email,
subject,
content,
null, sm,
adminEmail, "")) {
adminEmail, "");
if (Boolean.FALSE.equals(mailSent)) {
return new ResponseEntity<>(String.format(
messages.getString("mail_error")), HttpStatus.PRECONDITION_FAILED);
}

return new ResponseEntity<>(String.format(
messages.getString("user_password_sent"),
XslUtil.encodeForJavaScript(username)
Expand Down

0 comments on commit 960ff24

Please sign in to comment.