Skip to content

Commit

Permalink
Added the resetLink function for admin portal
Browse files Browse the repository at this point in the history
  • Loading branch information
Camelia-Orcid committed Feb 10, 2025
1 parent 4d396f2 commit bca6e50
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ public class PasswordResetToken {
// public static final String RESET_TOKEN_DATE_FORMAT = "dd/MM/yyyy HH:mm:ss";
private static final String EMAIL_PARAM_KEY = "email";
private static final String ISSUE_DATE_PARAM_KEY = "issueDate";
private static final String HOURS_PARAM_KEY = "h";
private static final String EQUALS = "=";
private static final String SEPARATOR = "&";

private String email;
private XMLGregorianCalendar issueDate;
private int durationInHours = 4;

public PasswordResetToken() {

Expand All @@ -38,19 +40,28 @@ public PasswordResetToken(String paramsString) {
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}

}
email = params.get(EMAIL_PARAM_KEY);
issueDate = DateUtils.convertToXMLGregorianCalendar(params.get(ISSUE_DATE_PARAM_KEY));
if(StringUtils.isNotBlank(params.get(HOURS_PARAM_KEY))) {
durationInHours = Integer.valueOf(params.get(HOURS_PARAM_KEY));
}

}

public String getEmail() {
return email;
}

public int getDurationInHours() {
return durationInHours;
}

public Date getIssueDate() {
return issueDate.toGregorianCalendar().getTime();
}


/**
*
Expand All @@ -61,6 +72,7 @@ public String toParamsString() {
List<Pair<String, String>> pairs = new ArrayList<Pair<String, String>>();
pairs.add(new ImmutablePair<String, String>(EMAIL_PARAM_KEY, email));
pairs.add(new ImmutablePair<String, String>(ISSUE_DATE_PARAM_KEY, String.valueOf(issueDate)));
pairs.add(new ImmutablePair<String, String>(HOURS_PARAM_KEY, String.valueOf(durationInHours)));

List<String> items = new ArrayList<String>(pairs.size());
for (Pair<String, String> pair : pairs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ public String createResetEmail(String userEmail, String baseUri) {
String resetParams = MessageFormat.format("email={0}&issueDate={1}", new Object[] { userEmail, date.toXMLFormat() });
return createEmailBaseUrl(resetParams, baseUri, "reset-password-email");
}

public String createResetLinkForAdmin(String userEmail, String baseUri) {
XMLGregorianCalendar date = DateUtils.convertToXMLGregorianCalendarNoTimeZoneNoMillis(new Date());
String resetParams = MessageFormat.format("email={0}&issueDate={1}&h=24", new Object[] { userEmail, date.toXMLFormat() });
return createEmailBaseUrl(resetParams, baseUri, "reset-password-email");
}

public String createReactivationUrl(String userEmail, String baseUri) {
XMLGregorianCalendar date = DateUtils.convertToXMLGregorianCalendarNoTimeZoneNoMillis(new Date());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.orcid.pojo;

import java.util.Date;

public class AdminResetPasswordLink {
private String resetLink;

private String email;

private String error;

private Date issueDate;

private int durationInHours = 4;

public String getResetLink() {
return resetLink;
}

public void setResetLink(String resetLink) {
this.resetLink = resetLink;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}

public Date getIssueDate() {
return issueDate;
}

public void setIssueDate(Date issueDate) {
this.issueDate = issueDate;
}

public int getDurationInHours() {
return durationInHours;
}

public void setDurationInHours(int durationInHours) {
this.durationInHours = durationInHours;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.orcid.core.manager.AdminManager;
import org.orcid.core.manager.EncryptionManager;
import org.orcid.core.manager.ProfileEntityCacheManager;
import org.orcid.core.manager.TwoFactorAuthenticationManager;
import org.orcid.core.manager.v3.ClientDetailsManager;
import org.orcid.core.manager.v3.NotificationManager;
import org.orcid.core.manager.v3.ProfileEntityManager;
import org.orcid.core.manager.v3.SpamManager;
import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly;
import org.orcid.core.utils.PasswordResetToken;
import org.orcid.core.utils.VerifyEmailUtils;
import org.orcid.frontend.email.RecordEmailSender;
import org.orcid.frontend.web.util.PasswordConstants;
import org.orcid.jaxb.model.clientgroup.ClientType;
Expand All @@ -37,6 +41,7 @@
import org.orcid.persistence.jpa.entities.ProfileEntity;
import org.orcid.pojo.AdminChangePassword;
import org.orcid.pojo.AdminDelegatesRequest;
import org.orcid.pojo.AdminResetPasswordLink;
import org.orcid.pojo.ConvertClient;
import org.orcid.pojo.LockAccounts;
import org.orcid.pojo.ProfileDeprecationRequest;
Expand All @@ -45,6 +50,7 @@
import org.orcid.utils.OrcidStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -80,6 +86,12 @@ public class AdminController extends BaseController {

@Resource(name = "clientDetailsManagerV3")
private ClientDetailsManager clientDetailsManager;

@Resource
private VerifyEmailUtils verifyEmailUtils;

@Resource
private EncryptionManager encryptionManager;

@Resource(name = "spamManager")
SpamManager spamManager;
Expand All @@ -89,6 +101,10 @@ public class AdminController extends BaseController {

@Resource
private TwoFactorAuthenticationManager twoFactorAuthenticationManager;


@Value("${org.orcid.admin.registry.url:https://orcid.org}")
private String registryUrl;

private static final String CLAIMED = "(claimed)";
private static final String DEACTIVATED = "(deactivated)";
Expand Down Expand Up @@ -624,6 +640,31 @@ public Map<String, String> findIdByEmailHelper(String csvEmails) {
}
return form;
}

/**
* Reset password validate
*
* @throws IllegalAccessException
* @throws UnsupportedEncodingException
*/
@RequestMapping(value = "/reset-password-link", method = RequestMethod.POST)
public @ResponseBody AdminResetPasswordLink resetPasswordLink(HttpServletRequest serverRequest, HttpServletResponse response,
@RequestBody AdminResetPasswordLink form) throws IllegalAccessException, UnsupportedEncodingException {
isAdmin(serverRequest, response);
form.setError(null);
String email = URLDecoder.decode(form.getEmail(), "UTF-8").trim();
if (OrcidStringUtils.isEmailValid(email) && emailManager.emailExists(email)) {
String resetLink = verifyEmailUtils.createResetLinkForAdmin(email, registryUrl);
form.setResetLink(resetLink);
//need issue date as well
PasswordResetToken passwordResetToken = buildResetTokenFromEncryptedLink(resetLink);
form.setIssueDate(passwordResetToken.getIssueDate());
form.setDurationInHours(passwordResetToken.getDurationInHours());
} else {
form.setError(getMessage("admin.errors.unexisting_email"));
}
return form;
}

/**
* Admin switch user
Expand Down Expand Up @@ -1161,4 +1202,14 @@ private String getOrcidFromParam(String orcidOrEmail) {
return data;
}
}

private PasswordResetToken buildResetTokenFromEncryptedLink(String encryptedLink) {
try {
String paramsString = encryptionManager.decryptForExternalUse(new String(Base64.decodeBase64(encryptedLink), "UTF-8"));
return new PasswordResetToken(paramsString);
} catch (UnsupportedEncodingException e) {
LOGGER.error("Could not decrypt " + encryptedLink);
throw new RuntimeException(getMessage("web.orcid.decrypt_passwordreset.exception"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ private PasswordResetToken buildResetTokenFromEncryptedLink(String encryptedLink
}

private boolean isTokenExpired(PasswordResetToken passwordResetToken) {
Date expiryDateOfOneHourFromIssueDate = org.apache.commons.lang.time.DateUtils.addHours(passwordResetToken.getIssueDate(), 4);
Date expiryDateOfOneHourFromIssueDate = org.apache.commons.lang.time.DateUtils.addHours(passwordResetToken.getIssueDate(), passwordResetToken.getDurationInHours());
Date now = new Date();
return (expiryDateOfOneHourFromIssueDate.getTime() < now.getTime());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.codec.binary.Base64;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
Expand All @@ -44,7 +47,9 @@
import org.orcid.core.common.manager.EmailFrequencyManager;
import org.orcid.core.locale.LocaleManager;
import org.orcid.core.manager.AdminManager;
import org.orcid.core.manager.EncryptionManager;
import org.orcid.core.manager.ProfileEntityCacheManager;
import org.orcid.core.manager.impl.OrcidUrlManager;
import org.orcid.core.manager.v3.ClientDetailsManager;
import org.orcid.core.manager.v3.EmailManager;
import org.orcid.core.manager.v3.NotificationManager;
Expand All @@ -58,6 +63,8 @@
import org.orcid.core.profile.history.ProfileHistoryEventType;
import org.orcid.core.security.OrcidUserDetailsService;
import org.orcid.core.security.OrcidWebRole;
import org.orcid.core.utils.PasswordResetToken;
import org.orcid.core.utils.VerifyEmailUtils;
import org.orcid.frontend.email.RecordEmailSender;
import org.orcid.frontend.web.util.BaseControllerTest;
import org.orcid.jaxb.model.clientgroup.ClientType;
Expand All @@ -73,13 +80,15 @@
import org.orcid.persistence.jpa.entities.RecordNameEntity;
import org.orcid.pojo.AdminChangePassword;
import org.orcid.pojo.AdminDelegatesRequest;
import org.orcid.pojo.AdminResetPasswordLink;
import org.orcid.pojo.ConvertClient;
import org.orcid.pojo.LockAccounts;
import org.orcid.pojo.ProfileDeprecationRequest;
import org.orcid.pojo.ProfileDetails;
import org.orcid.pojo.ajaxForm.Text;
import org.orcid.test.OrcidJUnit4ClassRunner;
import org.orcid.test.TargetProxyHelper;
import org.orcid.utils.DateUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand Down Expand Up @@ -148,6 +157,17 @@ public class AdminControllerTest extends BaseControllerTest {

HttpServletResponse mockResponse = mock(HttpServletResponse.class);

@Mock
private OrcidUrlManager mockOrcidUrlManager;

@Mock
private VerifyEmailUtils mockVerifyEmailUtils;

@Mock
private EncryptionManager mockEncryptionManager;



@Captor
private ArgumentCaptor<String> adminUser;

Expand Down Expand Up @@ -1403,4 +1423,48 @@ public void testConvertClient() throws IllegalAccessException, UnsupportedEncodi
Mockito.verify(clientDetailsManager).convertPublicClientToMember(Mockito.eq("public-client"), Mockito.eq("legal-group"));
}

@Test
public void resetPasswordLink() throws Exception {
VerifyEmailUtils verifyEmailUtils = Mockito.mock(VerifyEmailUtils.class);
EncryptionManager encryptionManager= Mockito.mock(EncryptionManager.class);
OrcidSecurityManager orcidSecurityManager = Mockito.mock(OrcidSecurityManager.class);
AdminController adminController = new AdminController();
EmailManager emailManager = Mockito.mock(EmailManager.class);
LocaleManager localeManager = Mockito.mock(LocaleManager.class);


ReflectionTestUtils.setField(adminController, "verifyEmailUtils", verifyEmailUtils);
ReflectionTestUtils.setField(adminController, "encryptionManager", encryptionManager);
ReflectionTestUtils.setField(adminController, "emailManager", emailManager);
ReflectionTestUtils.setField(adminController, "localeManager", localeManager);
ReflectionTestUtils.setField(adminController, "orcidSecurityManager", orcidSecurityManager);

Mockito.when(orcidSecurityManager.isAdmin()).thenReturn(true);

Mockito.when(emailManager.emailExists(Mockito.anyString())).thenReturn(true);
Mockito.when(emailManager.emailExists(Mockito.eq("[email protected]"))).thenReturn(false);
Mockito.when(emailManager.emailExists(Mockito.eq("[email protected]"))).thenReturn(false);

Mockito.when(localeManager.resolveMessage(Mockito.anyString(), Mockito.any())).thenReturn("That email address is not on our records");
Mockito.when(verifyEmailUtils.createResetLinkForAdmin(Mockito.anyString(), Mockito.any())).thenReturn("xyz");



AdminResetPasswordLink adminResetPasswordLink = new AdminResetPasswordLink();
adminResetPasswordLink.setEmail("[email protected]");

adminResetPasswordLink = adminController.resetPasswordLink(mockRequest, mockResponse, adminResetPasswordLink);

assertEquals("That email address is not on our records", adminResetPasswordLink.getError());

adminResetPasswordLink = new AdminResetPasswordLink();
adminResetPasswordLink.setEmail("[email protected]");
XMLGregorianCalendar date = DateUtils.convertToXMLGregorianCalendarNoTimeZoneNoMillis(new Date());
Mockito.when(encryptionManager.decryptForExternalUse(Mockito.anyString())).thenReturn("[email protected]&issueDate="+ date.toXMLFormat()+ "&h=24");
adminResetPasswordLink = adminController.resetPasswordLink(mockRequest, mockResponse, adminResetPasswordLink);
assertNotNull(adminResetPasswordLink.getResetLink());
assertEquals(24,adminResetPasswordLink.getDurationInHours());

}

}

0 comments on commit bca6e50

Please sign in to comment.