CVE-2022-23619 (published 2022-02-09T21:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In affected versions it\'s possible to guess if a user has an account on the wiki by using the "Forgot your password" form, even if the wiki is closed to guest users. This problem has been patched on XWiki 12.10.9, 13.4.1 and 13.6RC1. Users are advised yo update. There are no known workarounds for this issue.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-640
cwe_name: Weak Password Recovery Mechanism for Forgotten Password
description: The product contains a mechanism for users to recover or change their passwords without knowing the original password, but the mechanism is weak.
extended_description: 
            It is common for an application to have a mechanism that provides a means for a user to gain access to their account in the event they forget their password. Very often the password recovery mechanism is weak, which has the effect of making it more likely that it would be possible for a person other than the legitimate system user to gain access to that user's account. Weak password recovery schemes completely undermine a strong password authentication scheme.
            This weakness may be that the security question is too easy to guess or find an answer to (e.g. because the question is too common, or the answers can be found using social media). Or there might be an implementation weakness in the password recovery mechanism code that may for instance trick the system into e-mailing the new password to an e-mail account other than that of the user. There might be no throttling done on the rate of password resets so that a legitimate user can be denied service by an attacker if an attacker tries to recover their password in a rapid succession. The system may send the original password to the user rather than generating a new temporary password. In summary, password recovery functionality, if not carefully designed and implemented can often become the system's weakest link that can be misused in a way that would allow an attacker to gain unauthorized access to the system.
         
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Simon Urli
author_date: 2021-06-29 09:48:38+02:00
msg: XWIKI-18787: Authentication API does not return proper results (#1651)

  * Refactor a bit the Authentication API to be more sound
  * Fix related tests
  * Fix missing escape
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -156,6 +156,24 @@
                    </differences>
                  </revapi.differences>
             -->
+            <revapi.differences>
+              <criticality>highlight</criticality>
+              <differences>
+                <item>
+                  <ignore>true</ignore>
+                  <code>java.method.returnTypeChanged</code>
+                  <old>method javax.mail.internet.InternetAddress org.xwiki.security.authentication.script.AuthenticationScriptService::requestResetPassword(org.xwiki.user.UserReference) throws org.xwiki.security.authentication.ResetPasswordException</old>
+                  <new>method void org.xwiki.security.authentication.script.AuthenticationScriptService::requestResetPassword(org.xwiki.user.UserReference) throws org.xwiki.security.authentication.ResetPasswordException</new>
+                  <justification>Unstable API: this API was not properly designed and shouldn't have returned that in first place.</justification>
+                </item>
+                <item>
+                  <ignore>true</ignore>
+                  <code>java.method.removed</code>
+                  <old>method javax.mail.internet.InternetAddress org.xwiki.security.authentication.ResetPasswordRequestResponse::getUserEmail()</old>
+                  <justification>Unstable API: this API was not properly designed and shouldn't have returned that in first place.</justification>
+                </item>
+              </differences>
+            </revapi.differences>
           </analysisConfiguration>
         </configuration>
       </plugin>

programming_language: HTML
filename: ResetPasswordIT.java
change_type: ModificationType.MODIFY
diff: @@ -117,17 +117,19 @@ public void resetForgottenPassword(TestUtils setup) throws Exception
         // Try to reset the password of a non existent user
         resetPasswordPage.setUserName("SomeUserThatDoesNotExist");
         resetPasswordPage = resetPasswordPage.clickResetPassword();
-        assertFalse(resetPasswordPage.isResetPasswordSent());
-        assertTrue(resetPasswordPage.getMessage().contains("user does not exist"));
+
+        // there should not have any indication if the user exists or not.
+        assertTrue(resetPasswordPage.isFormSubmitted());
 
         // Try again
-        resetPasswordPage = resetPasswordPage.clickRetry();
+        resetPasswordPage = ResetPasswordPage.gotoPage();
 
         // Try to reset the password of our user, when he has no email set
         resetPasswordPage.setUserName(userName);
         resetPasswordPage.clickResetPassword();
-        assertFalse(resetPasswordPage.isResetPasswordSent());
-        assertTrue(resetPasswordPage.getMessage().contains("email address not provided"));
+
+        // there should not have any indication if an email address is provided or not.
+        assertTrue(resetPasswordPage.isFormSubmitted());
 
         // Try again. This time, set the user's email address in the profile
         setup.loginAsSuperAdmin();
@@ -145,7 +147,7 @@ public void resetForgottenPassword(TestUtils setup) throws Exception
             "Actual message: " + newResetPasswordPage.getMessage());
 
         // Check the result
-        assertTrue(resetPasswordPage.isResetPasswordSent());
+        assertTrue(resetPasswordPage.isFormSubmitted());
         // Check the emails received by the user
         assertTrue(this.mail.waitForIncomingEmail(1));
         MimeMessage[] receivedEmails = this.mail.getReceivedMessages();

programming_language: Java
filename: ResetPasswordPage.java
change_type: ModificationType.MODIFY
diff: @@ -67,7 +67,13 @@ public ResetPasswordPage clickResetPassword()
         return new ResetPasswordPage();
     }
 
-    public boolean isResetPasswordSent()
+    /**
+     * This method only checks if the form was properly submitted and didn't return an error.
+     * It does not mean that an email was necessarily sent.
+     *
+     * @return {@code true} if the form is properly submitted.
+     */
+    public boolean isFormSubmitted()
     {
         // If there is no form and we see an info box, then the request was sent.
         return !getDriver().hasElementWithoutWaiting(By.cssSelector("#resetPasswordForm"))

programming_language: Java
filename: ApplicationResources.properties
change_type: ModificationType.MODIFY
diff: @@ -2449,7 +2449,7 @@ xe.admin.passwordReset.title=Forgot your password?
 xe.admin.passwordReset.instructions=Please enter your username to start the password reset process.
 xe.admin.passwordReset.username.label=Username
 xe.admin.passwordReset.submit=Reset password
-xe.admin.passwordReset.emailSent=An e-mail was sent to {0}. Please follow the instructions in that e-mail to complete the password reset procedure.
+xe.admin.passwordReset.emailSentToUsername=An e-mail was sent to the address configured for user "{0}". Please follow the instructions in that e-mail to complete the password reset procedure.
 xe.admin.passwordReset.login=Login \u00BB
 xe.admin.passwordReset.error.noUser=The {0} user does not exist.
 xe.admin.passwordReset.error.ldapUser=The {0} user is an LDAP user. In that case the password has to be changed on the LDAP server.
@@ -5501,6 +5501,11 @@ xe.admin.forgotUsername.result=Your username is: {0}
 xe.admin.forgotUsername.multipleResults=The following usernames are registered with this email address:
 xe.admin.forgotUsername.error.noAccount=No account is registered using this email address.
 
+#######################################
+## until 12.10.9, 13.6RC1, 13.4.1
+#######################################
+xe.admin.passwordReset.emailSent=An e-mail was sent to {0}. Please follow the instructions in that e-mail to complete the password reset procedure.
+
 ## Used to indicate where deprecated keys end
 #@deprecatedend
 

programming_language: Shell
filename: ResetPasswordRequestResponse.java
change_type: ModificationType.MODIFY
diff: @@ -19,8 +19,6 @@
  */
 package org.xwiki.security.authentication;
 
-import javax.mail.internet.InternetAddress;
-
 import org.xwiki.stability.Unstable;
 import org.xwiki.user.UserReference;
 
@@ -38,11 +36,6 @@
      */
     UserReference getUserReference();
 
-    /**
-     * @return the email address of the user for whom the reset password request have been performed.
-     */
-    InternetAddress getUserEmail();
-
     /**
      * @return the verification code to be send to the user.
      */

programming_language: Java
filename: DefaultResetPasswordManager.java
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,7 @@
 import javax.mail.internet.InternetAddress;
 
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
 import org.xwiki.component.annotation.Component;
 import org.xwiki.localization.ContextualLocalizationManager;
 import org.xwiki.model.reference.DocumentReference;
@@ -110,106 +111,110 @@ public class DefaultResetPasswordManager implements ResetPasswordManager
     @Inject
     private Provider<ResetPasswordMailSender> resetPasswordMailSenderProvider;
 
-    private void checkUserReference(UserReference userReference) throws ResetPasswordException
-    {
-        if (!this.userManager.exists(userReference)) {
-            String exceptionMessage =
-                this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noUser",
-                    userReference.toString());
-            throw new ResetPasswordException(exceptionMessage);
-        }
+    @Inject
+    private Logger logger;
 
+    private boolean checkUserReference(UserReference userReference) throws ResetPasswordException
+    {
         // FIXME: This check shouldn't be needed if we'd have the proper API to determine which kind of
         // authentication is used.
         if (!(userReference instanceof DocumentUserReference)) {
             throw new ResetPasswordException("Only user having a page on the wiki can reset their password.");
         }
+
+        return this.userManager.exists(userReference);
     }
 
     @Override
     public ResetPasswordRequestResponse requestResetPassword(UserReference userReference) throws ResetPasswordException
     {
-        this.checkUserReference(userReference);
-
-        UserProperties userProperties = this.userPropertiesResolver.resolve(userReference);
-        InternetAddress email = userProperties.getEmail();
-
-        if (email == null) {
-            String exceptionMessage =
-                this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noEmail");
-            throw new ResetPasswordException(exceptionMessage);
-        }
-
-        DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
-        DocumentReference reference = documentUserReference.getReference();
-        XWikiContext context = this.contextProvider.get();
-
-        try {
-            XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
-
-            if (userDocument.getXObject(LDAP_CLASS_REFERENCE) != null) {
-                String exceptionMessage =
-                    this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.ldapUser",
-                        userReference.toString());
-                throw new ResetPasswordException(exceptionMessage);
+        if (this.checkUserReference(userReference)) {
+            UserProperties userProperties = this.userPropertiesResolver.resolve(userReference);
+            InternetAddress email = userProperties.getEmail();
+
+            if (email != null) {
+                DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
+                DocumentReference reference = documentUserReference.getReference();
+                XWikiContext context = this.contextProvider.get();
+
+                try {
+                    XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
+
+                    if (userDocument.getXObject(LDAP_CLASS_REFERENCE) != null) {
+                        String exceptionMessage =
+                            this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.ldapUser",
+                                userReference.toString());
+                        throw new ResetPasswordException(exceptionMessage);
+                    }
+
+                    BaseObject xObject = userDocument.getXObject(RESET_PASSWORD_REQUEST_CLASS_REFERENCE, true, context);
+                    String verificationCode = context.getWiki().generateRandomString(30);
+                    xObject.set(VERIFICATION_PROPERTY, verificationCode, context);
+
+                    String saveComment =
+                        this.localizationManager.getTranslationPlain("xe.admin.passwordReset.versionComment");
+                    context.getWiki().saveDocument(userDocument, saveComment, true, context);
+
+                    return new DefaultResetPasswordRequestResponse(userReference, verificationCode);
+                } catch (XWikiException e) {
+                    throw new ResetPasswordException(
+                        "Error when reading user document to perform reset password request.",
+                        e);
+                }
+            } else {
+                // In case the mail is not configured, we log a message to the admin.
+                this.logger.info("User [{}] asked to reset their password, but did not have any email configured.",
+                    userReference);
             }
-
-            BaseObject xObject = userDocument.getXObject(RESET_PASSWORD_REQUEST_CLASS_REFERENCE, true, context);
-            String verificationCode = context.getWiki().generateRandomString(30);
-            xObject.set(VERIFICATION_PROPERTY, verificationCode, context);
-
-            String saveComment =
-                this.localizationManager.getTranslationPlain("xe.admin.passwordReset.versionComment");
-            context.getWiki().saveDocument(userDocument, saveComment, true, context);
-
-            return new DefaultResetPasswordRequestResponse(userReference, email, verificationCode);
-        } catch (XWikiException e) {
-            throw new ResetPasswordException("Error when reading user document to perform reset password request.", e);
         }
+        return new DefaultResetPasswordRequestResponse(userReference, null);
     }
 
     @Override
     public void sendResetPasswordEmailRequest(ResetPasswordRequestResponse requestResponse)
         throws ResetPasswordException
     {
-        AuthenticationResourceReference resourceReference =
-            new AuthenticationResourceReference(AuthenticationAction.RESET_PASSWORD);
-
-        UserReference userReference = requestResponse.getUserReference();
-        UserProperties userProperties = this.userPropertiesResolver.resolve(userReference);
-        String serializedUserReference = this.referenceSerializer.serialize(userReference);
-
-        // FIXME: this should be provided as part of the User API.
-        String formattedName = "";
-        if (!StringUtils.isBlank(userProperties.getFirstName())) {
-            formattedName += userProperties.getFirstName();
-        }
-        if (!StringUtils.isBlank(userProperties.getLastName())) {
-            if (!StringUtils.isBlank(formattedName)) {
-                formattedName += " ";
+        if (this.checkUserReference(requestResponse.getUserReference())) {
+            AuthenticationResourceReference resourceReference =
+                new AuthenticationResourceReference(AuthenticationAction.RESET_PASSWORD);
+
+            UserReference userReference = requestResponse.getUserReference();
+            UserProperties userProperties = this.userPropertiesResolver.resolve(userReference);
+            InternetAddress email = userProperties.getEmail();
+            String serializedUserReference = this.referenceSerializer.serialize(userReference);
+
+            // FIXME: this should be provided as part of the User API.
+            String formattedName = "";
+            if (!StringUtils.isBlank(userProperties.getFirstName())) {
+                formattedName += userProperties.getFirstName();
+            }
+            if (!StringUtils.isBlank(userProperties.getLastName())) {
+                if (!StringUtils.isBlank(formattedName)) {
+                    formattedName += " ";
+                }
+                formattedName += userProperties.getLastName();
+            }
+            if (StringUtils.isBlank(formattedName)) {
+                formattedName = serializedUserReference;
+            }
+            resourceReference.addParameter("u", serializedUserReference);
+            resourceReference.addParameter("v", requestResponse.getVerificationCode());
+
+            XWikiContext context = contextProvider.get();
+
+            ExtendedURL extendedURL = null;
+            try {
+                extendedURL = this.resourceReferenceSerializer.serialize(resourceReference);
+                extendedURL = this.urlNormalizer.normalize(extendedURL);
+                URL serverURL = context.getURLFactory().getServerURL(context);
+                URL externalVerificationURL = new URL(serverURL, extendedURL.serialize());
+
+                this.resetPasswordMailSenderProvider.get()
+                    .sendResetPasswordEmail(formattedName, email, externalVerificationURL);
+            } catch (SerializeResourceReferenceException | UnsupportedResourceReferenceException
+                | MalformedURLException e) {
+                throw new ResetPasswordException("Error when processing information for creating the email.", e);
             }
-            formattedName += userProperties.getLastName();
-        }
-        if (StringUtils.isBlank(formattedName)) {
-            formattedName = serializedUserReference;
-        }
-        resourceReference.addParameter("u", serializedUserReference);
-        resourceReference.addParameter("v", requestResponse.getVerificationCode());
-
-        XWikiContext context = contextProvider.get();
-
-        ExtendedURL extendedURL = null;
-        try {
-            extendedURL = this.resourceReferenceSerializer.serialize(resourceReference);
-            extendedURL = this.urlNormalizer.normalize(extendedURL);
-            URL serverURL = context.getURLFactory().getServerURL(context);
-            URL externalVerificationURL = new URL(serverURL, extendedURL.serialize());
-
-            this.resetPasswordMailSenderProvider.get()
-                .sendResetPasswordEmail(formattedName, requestResponse.getUserEmail(), externalVerificationURL);
-        } catch (SerializeResourceReferenceException | UnsupportedResourceReferenceException | MalformedURLException e)
-        {
-            throw new ResetPasswordException("Error when processing information for creating the email.", e);
         }
     }
 
@@ -217,49 +222,50 @@ public void sendResetPasswordEmailRequest(ResetPasswordRequestResponse requestRe
     public ResetPasswordRequestResponse checkVerificationCode(UserReference userReference, String verificationCode)
         throws ResetPasswordException
     {
-        this.checkUserReference(userReference);
-        XWikiContext context = this.contextProvider.get();
-
-        UserProperties userProperties = this.userPropertiesResolver.resolve(userReference);
-        InternetAddress email = userProperties.getEmail();
-
-        DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
-        DocumentReference reference = documentUserReference.getReference();
-        String exceptionMessage =
-            this.localizationManager.getTranslationPlain("xe.admin.passwordReset.step2.error.wrongParameters",
-                userReference.toString());
-
-        try {
-            XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
-            BaseObject xObject = userDocument.getXObject(RESET_PASSWORD_REQUEST_CLASS_REFERENCE);
-            if (xObject == null) {
-                throw new ResetPasswordException(exceptionMessage);
-            }
+        if (this.checkUserReference(userReference)) {
+            XWikiContext context = this.contextProvider.get();
 
-            String storedVerificationCode = xObject.getStringValue(VERIFICATION_PROPERTY);
-            BaseClass xClass = xObject.getXClass(context);
-            PropertyInterface verification = xClass.get(VERIFICATION_PROPERTY);
-            if (!(verification instanceof PasswordClass)) {
-                throw new ResetPasswordException("Bad definition of ResetPassword XClass.");
-            }
-            PasswordClass passwordClass = (PasswordClass) verification;
-            String equivalentPassword =
-                passwordClass.getEquivalentPassword(storedVerificationCode, verificationCode);
-
-            // We ensure to reset the verification code before checking if it's correct to avoid any bruteforce attack.
-            String newVerificationCode = context.getWiki().generateRandomString(30);
-            xObject.set(VERIFICATION_PROPERTY, newVerificationCode, context);
-            String saveComment = this.localizationManager
-                .getTranslationPlain("xe.admin.passwordReset.step2.versionComment.changeValidationKey");
-            context.getWiki().saveDocument(userDocument, saveComment, true, context);
-
-            if (!storedVerificationCode.equals(equivalentPassword)) {
-                throw new ResetPasswordException(exceptionMessage);
-            }
+            DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
+            DocumentReference reference = documentUserReference.getReference();
+            String exceptionMessage =
+                this.localizationManager.getTranslationPlain("xe.admin.passwordReset.step2.error.wrongParameters",
+                    userReference.toString());
 
-            return new DefaultResetPasswordRequestResponse(userReference, email, newVerificationCode);
-        } catch (XWikiException e) {
-            throw new ResetPasswordException("Cannot open user document to check verification code.", e);
+            try {
+                XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
+                BaseObject xObject = userDocument.getXObject(RESET_PASSWORD_REQUEST_CLASS_REFERENCE);
+                if (xObject == null) {
+                    throw new ResetPasswordException(exceptionMessage);
+                }
+
+                String storedVerificationCode = xObject.getStringValue(VERIFICATION_PROPERTY);
+                BaseClass xClass = xObject.getXClass(context);
+                PropertyInterface verification = xClass.get(VERIFICATION_PROPERTY);
+                if (!(verification instanceof PasswordClass)) {
+                    throw new ResetPasswordException("Bad definition of ResetPassword XClass.");
+                }
+                PasswordClass passwordClass = (PasswordClass) verification;
+                String equivalentPassword =
+                    passwordClass.getEquivalentPassword(storedVerificationCode, verificationCode);
+
+                // We ensure to reset the verification code before checking if it's correct to avoid
+                // any bruteforce attack.
+                String newVerificationCode = context.getWiki().generateRandomString(30);
+                xObject.set(VERIFICATION_PROPERTY, newVerificationCode, context);
+                String saveComment = this.localizationManager
+                    .getTranslationPlain("xe.admin.passwordReset.step2.versionComment.changeValidationKey");
+                context.getWiki().saveDocument(userDocument, saveComment, true, context);
+
+                if (!storedVerificationCode.equals(equivalentPassword)) {
+                    throw new ResetPasswordException(exceptionMessage);
+                }
+
+                return new DefaultResetPasswordRequestResponse(userReference, newVerificationCode);
+            } catch (XWikiException e) {
+                throw new ResetPasswordException("Cannot open user document to check verification code.", e);
+            }
+        } else {
+            return new DefaultResetPasswordRequestResponse(userReference, null);
         }
     }
 
@@ -267,23 +273,24 @@ public ResetPasswordRequestResponse checkVerificationCode(UserReference userRefe
     public void resetPassword(UserReference userReference, String newPassword)
         throws ResetPasswordException
     {
-        this.checkUserReference(userReference);
-        XWikiContext context = this.contextProvider.get();
-
-        DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
-        DocumentReference reference = documentUserReference.getReference();
-
-        try {
-            XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
-            userDocument.removeXObjects(RESET_PASSWORD_REQUEST_CLASS_REFERENCE);
-            BaseObject userXObject = userDocument.getXObject(USER_CLASS_REFERENCE);
-            userXObject.setStringValue("password", newPassword);
-
-            String saveComment = this.localizationManager.getTranslationPlain(
-                "xe.admin.passwordReset.step2.versionComment.passwordReset");
-            context.getWiki().saveDocument(userDocument, saveComment, true, context);
-        } catch (XWikiException e) {
-            throw new ResetPasswordException("Cannot open user document to perform reset password.", e);
+        if (this.checkUserReference(userReference)) {
+            XWikiContext context = this.contextProvider.get();
+
+            DocumentUserReference documentUserReference = (DocumentUserReference) userReference;
+            DocumentReference reference = documentUserReference.getReference();
+
+            try {
+                XWikiDocument userDocument = context.getWiki().getDocument(reference, context);
+                userDocument.removeXObjects(RESET_PASSWORD_REQUEST_CLASS_REFERENCE);
+                BaseObject userXObject = userDocument.getXObject(USER_CLASS_REFERENCE);
+                userXObject.setStringValue("password", newPassword);
+
+                String saveComment = this.localizationManager.getTranslationPlain(
+                    "xe.admin.passwordReset.step2.versionComment.passwordReset");
+                context.getWiki().saveDocument(userDocument, saveComment, true, context);
+            } catch (XWikiException e) {
+                throw new ResetPasswordException("Cannot open user document to perform reset password.", e);
+            }
         }
     }
 }

programming_language: Java
filename: DefaultResetPasswordRequestResponse.java
change_type: ModificationType.MODIFY
diff: @@ -19,8 +19,6 @@
  */
 package org.xwiki.security.authentication.internal;
 
-import javax.mail.internet.InternetAddress;
-
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.xwiki.security.authentication.ResetPasswordRequestResponse;
@@ -35,19 +33,16 @@
 public final class DefaultResetPasswordRequestResponse implements ResetPasswordRequestResponse
 {
     private final UserReference userReference;
-    private final InternetAddress userEmail;
     private final String verificationCode;
 
     /**
      * Default constructor.
      * @param reference the user for whom a reset password request is performed.
-     * @param userEmail the email of the user.
      * @param verificationCode the code to send for resetting the password.
      */
-    DefaultResetPasswordRequestResponse(UserReference reference, InternetAddress userEmail, String verificationCode)
+    DefaultResetPasswordRequestResponse(UserReference reference, String verificationCode)
     {
         this.userReference = reference;
-        this.userEmail = userEmail;
         this.verificationCode = verificationCode;
     }
 
@@ -59,14 +54,6 @@ public UserReference getUserReference()
         return userReference;
     }
 
-    /**
-     * @return the email of the user.
-     */
-    public InternetAddress getUserEmail()
-    {
-        return userEmail;
-    }
-
     /**
      * @return the code to send for resetting the password.
      */
@@ -90,7 +77,6 @@ public boolean equals(Object o)
 
         return new EqualsBuilder()
             .append(userReference, that.userReference)
-            .append(userEmail, that.userEmail)
             .append(verificationCode, that.verificationCode)
             .isEquals();
     }
@@ -100,7 +86,6 @@ public int hashCode()
     {
         return new HashCodeBuilder(17, 37)
             .append(userReference)
-            .append(userEmail)
             .append(verificationCode)
             .toHashCode();
     }

programming_language: Java
filename: DefaultResetPasswordManagerTest.java
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,7 @@
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 import org.xwiki.localization.ContextualLocalizationManager;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.resource.ResourceReference;
@@ -37,6 +38,8 @@
 import org.xwiki.security.authentication.ResetPasswordException;
 import org.xwiki.security.authentication.ResetPasswordManager;
 import org.xwiki.security.authentication.ResetPasswordRequestResponse;
+import org.xwiki.test.LogLevel;
+import org.xwiki.test.junit5.LogCaptureExtension;
 import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;
@@ -59,7 +62,11 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -100,6 +107,9 @@
     @MockComponent
     private Provider<ResetPasswordMailSender> resetPasswordMailSenderProvider;
 
+    @RegisterExtension
+    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.INFO);
+
     private ResetPasswordMailSender resetPasswordMailSender;
 
     private DocumentUserReference userReference;
@@ -145,24 +155,19 @@ void requestResetPassword() throws Exception
             .thenReturn("Save verification code 42");
 
         ResetPasswordRequestResponse expectedResult =
-            new DefaultResetPasswordRequestResponse(this.userReference, email,
-                verificationCode);
+            new DefaultResetPasswordRequestResponse(this.userReference, verificationCode);
         assertEquals(expectedResult, this.resetPasswordManager.requestResetPassword(this.userReference));
         verify(xObject).set(DefaultResetPasswordManager.VERIFICATION_PROPERTY, verificationCode, context);
         verify(this.xWiki).saveDocument(this.userDocument, "Save verification code 42", true, this.context);
     }
 
     @Test
-    void requestResetPasswordUnexistingUser()
+    void requestResetPasswordUnexistingUser() throws ResetPasswordException
     {
         when(this.userReference.toString()).thenReturn("user:Foobar");
         when(this.userManager.exists(this.userReference)).thenReturn(false);
-        String exceptionMessage = "User [user:Foobar] doesn't exist";
-        when(this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noUser",
-            "user:Foobar")).thenReturn(exceptionMessage);
-        ResetPasswordException resetPasswordException = assertThrows(ResetPasswordException.class,
-            () -> this.resetPasswordManager.requestResetPassword(this.userReference));
-        assertEquals(exceptionMessage, resetPasswordException.getMessage());
+        assertEquals(new DefaultResetPasswordRequestResponse(this.userReference, null),
+            this.resetPasswordManager.requestResetPassword(this.userReference));
     }
 
     @Test
@@ -180,12 +185,11 @@ void requestResetPasswordNotDocumentReferenceUser()
     void requestResetPasswordNoEmail() throws Exception
     {
         when(this.userManager.exists(this.userReference)).thenReturn(true);
-        String exceptionMessage = "User has no email address.";
-        when(this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noEmail"))
-            .thenReturn(exceptionMessage);
-        ResetPasswordException resetPasswordException = assertThrows(ResetPasswordException.class,
-            () -> this.resetPasswordManager.requestResetPassword(this.userReference));
-        assertEquals(exceptionMessage, resetPasswordException.getMessage());
+        assertEquals(new DefaultResetPasswordRequestResponse(this.userReference, null),
+            this.resetPasswordManager.requestResetPassword(this.userReference));
+        when(this.userReference.toString()).thenReturn("foo");
+        assertEquals("User [foo] asked to reset their password, but did not have any email configured.",
+            logCapture.getMessage(0));
     }
 
     @Test
@@ -210,6 +214,7 @@ void requestResetPasswordLdapUser() throws Exception
     @Test
     void sendResetPasswordEmailRequest() throws Exception
     {
+        when(this.userManager.exists(this.userReference)).thenReturn(true);
         when(this.referenceSerializer.serialize(this.userReference)).thenReturn("user:Foobar");
         when(this.userProperties.getFirstName()).thenReturn("Foo");
         when(this.userProperties.getLastName()).thenReturn("Bar");
@@ -231,9 +236,10 @@ void sendResetPasswordEmailRequest() throws Exception
         when(urlFactory.getServerURL(this.context)).thenReturn(new URL("http://xwiki.org"));
 
         InternetAddress email = new InternetAddress("foobar@xwiki.org");
+        when(this.userProperties.getEmail()).thenReturn(email);
+
         DefaultResetPasswordRequestResponse requestResponse =
-            new DefaultResetPasswordRequestResponse(this.userReference, email,
-                verificationCode);
+            new DefaultResetPasswordRequestResponse(this.userReference, verificationCode);
         this.resetPasswordManager.sendResetPasswordEmailRequest(requestResponse);
         verify(this.resetPasswordMailSender).sendResetPasswordEmail("Foo Bar", email,
             new URL("http://xwiki.org/xwiki/authenticate/reset?u=user%3AFoobar&v=foobar4242"));
@@ -266,24 +272,20 @@ void checkVerificationCode() throws Exception
             .getTranslationPlain("xe.admin.passwordReset.step2.versionComment.changeValidationKey"))
             .thenReturn(saveComment);
         DefaultResetPasswordRequestResponse expected =
-            new DefaultResetPasswordRequestResponse(this.userReference, email,
-                newVerificationCode);
+            new DefaultResetPasswordRequestResponse(this.userReference, newVerificationCode);
 
         assertEquals(expected, this.resetPasswordManager.checkVerificationCode(this.userReference, verificationCode));
         verify(this.xWiki).saveDocument(this.userDocument, saveComment, true, context);
     }
 
     @Test
-    void checkVerificationCodeUnexistingUser()
+    void checkVerificationCodeUnexistingUser() throws ResetPasswordException
     {
         when(this.userReference.toString()).thenReturn("user:Foobar");
         when(this.userManager.exists(this.userReference)).thenReturn(false);
-        String exceptionMessage = "User [user:Foobar] doesn't exist";
-        when(this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noUser",
-            "user:Foobar")).thenReturn(exceptionMessage);
-        ResetPasswordException resetPasswordException = assertThrows(ResetPasswordException.class,
-            () -> this.resetPasswordManager.checkVerificationCode(this.userReference, "some code"));
-        assertEquals(exceptionMessage, resetPasswordException.getMessage());
+        ResetPasswordRequestResponse resetPasswordRequestResponse =
+            this.resetPasswordManager.checkVerificationCode(this.userReference, "some code");
+        assertEquals(new DefaultResetPasswordRequestResponse(this.userReference, null), resetPasswordRequestResponse);
     }
 
     @Test
@@ -358,12 +360,10 @@ void resetPasswordUnexistingUser() throws Exception
     {
         when(this.userReference.toString()).thenReturn("user:Foobar");
         when(this.userManager.exists(this.userReference)).thenReturn(false);
-        String exceptionMessage = "User [user:Foobar] doesn't exist";
-        when(this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.noUser",
-            "user:Foobar")).thenReturn(exceptionMessage);
-        ResetPasswordException resetPasswordException = assertThrows(ResetPasswordException.class,
-            () -> this.resetPasswordManager.resetPassword(this.userReference, "some password"));
-        assertEquals(exceptionMessage, resetPasswordException.getMessage());
+        this.resetPasswordManager.resetPassword(this.userReference, "some password");
+        verify(this.xWiki, never()).getDocument(any(DocumentReference.class), any(XWikiContext.class));
+        verify(this.xWiki, never()).saveDocument(any(XWikiDocument.class), anyString(), anyBoolean(),
+            any(XWikiContext.class));
     }
 
     @Test

programming_language: Java
filename: AuthenticationScriptService.java
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,6 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 import javax.inject.Singleton;
-import javax.mail.internet.InternetAddress;
 
 import org.slf4j.Logger;
 import org.xwiki.component.annotation.Component;
@@ -190,21 +189,16 @@ public String getAuthenticationURL(String action, Map<String, Object> params)
      * This method returns the email address used, so that we can display it to the user.
      *
      * @param user the user for which to perform a reset password request.
-     * @return the email address used to send the verification code or {@code null} if the user calling this method
-     *         doesn't have programming rights.
      * @throws ResetPasswordException if any error occurs for performing the reset password request.
      * @since 13.1RC1
      */
     @Unstable
-    public InternetAddress requestResetPassword(UserReference user) throws ResetPasswordException
+    public void requestResetPassword(UserReference user) throws ResetPasswordException
     {
         if (this.authorizationManager.hasAccess(Right.PROGRAM)) {
             ResetPasswordRequestResponse resetPasswordRequestResponse =
                 this.resetPasswordManager.requestResetPassword(user);
             this.resetPasswordManager.sendResetPasswordEmailRequest(resetPasswordRequestResponse);
-            return resetPasswordRequestResponse.getUserEmail();
-        } else {
-            return null;
         }
     }
 

programming_language: Java
filename: AuthenticationScriptServiceTest.java
change_type: ModificationType.MODIFY
diff: @@ -195,9 +195,8 @@ void requestResetPassword() throws Exception
         ResetPasswordRequestResponse requestResponse = mock(ResetPasswordRequestResponse.class);
         when(this.resetPasswordManager.requestResetPassword(userReference)).thenReturn(requestResponse);
         InternetAddress userEmail = new InternetAddress("acme@xwiki.org");
-        when(requestResponse.getUserEmail()).thenReturn(userEmail);
 
-        assertEquals(userEmail, this.scriptService.requestResetPassword(userReference));
+        this.scriptService.requestResetPassword(userReference);
         verify(this.resetPasswordManager).sendResetPasswordEmailRequest(requestResponse);
     }
 
@@ -206,7 +205,7 @@ void requestResetPasswordWithoutPR() throws Exception
     {
         when(this.authorizationManager.hasAccess(Right.PROGRAM)).thenReturn(false);
 
-        assertNull(this.scriptService.requestResetPassword(mock(UserReference.class)));
+        this.scriptService.requestResetPassword(mock(UserReference.class));
         verify(this.resetPasswordManager, never()).requestResetPassword(any());
         verify(this.resetPasswordManager, never()).sendResetPasswordEmailRequest(any());
     }

programming_language: Java
filename: resetpasswordinline.vm
change_type: ModificationType.MODIFY
diff: @@ -77,14 +77,14 @@ $services.localization.render('xe.admin.passwordReset.instructions')
         $services.localization.render('xe.admin.passwordReset.error.csrf')
     #else
         #try()
-            #set ($email = $services.security.authentication.requestResetPassword($userName))
+            #set ($discard = $services.security.authentication.requestResetPassword($userName))
         #end
         #if ("$!exception" != '')
             #resetPasswordBoxStart("warning")
             #displayResetPasswordException()
         #else
             #resetPasswordBoxStart("default")
-            $services.localization.render('xe.admin.passwordReset.emailSent', ["$services.mail.general.obfuscate($email)"])
+            $services.localization.render('xe.admin.passwordReset.emailSentToUsername', ["$escapetool.xml($userName)"])
         #end
     #end
   <div>

programming_language: Python