CVE-2023-49280: Insufficiently Protected Credentials (CWE-522)
value: XWiki Change Request is an XWiki application allowing to request changes on a wiki without publishing directly the changes. Change request allows to edit any page by default, and the changes are then exported in an XML file that anyone can download. So it's possible for an attacker to obtain password hash of users by performing an edit on the user profiles and then downloading the XML file that has been created. This is also true for any document that might contain password field and that a user can view.
This vulnerability impacts all version of Change Request, but the impact depends on the rights that has been set on the wiki since it requires for the user to have the Change request right (allowed by default) and view rights on the page to target. This issue cannot be easily exploited in an automated way. The patch consists in denying to users the right of editing pages that contains a password field with change request. It means that already existing change request for those pages won't be removed by the patch, administrators needs to take care of it. The patch is provided in Change Request 1.10, administrators should upgrade immediately. It's possible to workaround the vulnerability by denying manually the Change request right on some spaces, such as XWiki space which will include any user profile by default.
CWE description: The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval.
CVSS v3 base score: 6.5
Published: 2023-12-04T23:15Z
Repository: https://github.com/xwiki-contrib/application-changerequest
Language: Java
Files changed: application-changerequest-api/src/main/java/org/xwiki/contrib/changerequest/ChangeRequestRightsManager.java, application-changerequest-default/src/checkstyle/checkstyle-suppressions.xml, application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManager.java, application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/handlers/AbstractChangeRequestActionHandler.java, application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/script/ChangeRequestAuthorizationScriptService.java, application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManagerTest.java, application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/AddChangesChangeRequestHandlerTest.java, application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/CreateChangeRequestHandlerTest.java, application-changerequest-ui/src/main/resources/ChangeRequest/Code/EditPageUIX.xml, application-changerequest-ui/src/main/resources/ChangeRequest/Code/SaveButtonsUIX.xml
Commit date: 2023-08-29 18:36:59 +0200
Commit message: CRAPP-302: Edit button is not displayed for guest user

  * Change the way to deal with change request edition right

Diff: diff --git a/application-changerequest-api/src/main/java/org/xwiki/contrib/changerequest/ChangeRequestRightsManager.java b/application-changerequest-api/src/main/java/org/xwiki/contrib/changerequest/ChangeRequestRightsManager.java
index 9c56f7ac..2b6091b8 100644
--- a/application-changerequest-api/src/main/java/org/xwiki/contrib/changerequest/ChangeRequestRightsManager.java
+++ b/application-changerequest-api/src/main/java/org/xwiki/contrib/changerequest/ChangeRequestRightsManager.java
@@ -199,6 +199,35 @@ public interface ChangeRequestRightsManager
      */
     default boolean isAuthorizedToSplit(UserReference userReference, ChangeRequest changeRequest)
     {
         return false;
     }
+
+    /**
+     * Check if the given user is allowed to use change request to edit the given document reference.
+     * @param userReference the user for whom to perform the check
+     * @param documentReference the reference for which to check authorization
+     * @return {@code true} if the given user is allowed to perform the edition with change request
+     * @throws ChangeRequestException in case of problem when performing the check
+     * @since 1.10
+     */
+    default boolean isEditWithChangeRequestAllowed(UserReference userReference, DocumentReference documentReference)
+        throws ChangeRequestException
+    {
+        return false;
+    }
+
+    /**
+     * Check if the given user is allowed to use change request to create a document having the given reference for
+     * parent.
+     * @param userReference the user for whom to perform the check
+     * @param parentSpaceReference the parent space where the new document would be created
+     * @return {@code true} if the given user is allowed to create a document with change request at the given place
+     * @throws ChangeRequestException in case of problem when performing the check
+     * @since 1.10
+     */
+    default boolean isCreateWithChangeRequestAllowed(UserReference userReference,
+        DocumentReference parentSpaceReference) throws ChangeRequestException
+    {
+        return false;
+    }
 }
diff --git a/application-changerequest-default/src/checkstyle/checkstyle-suppressions.xml b/application-changerequest-default/src/checkstyle/checkstyle-suppressions.xml
index 7b2ccce0..acc3478e 100644
--- a/application-changerequest-default/src/checkstyle/checkstyle-suppressions.xml
+++ b/application-changerequest-default/src/checkstyle/checkstyle-suppressions.xml
@@ -51,6 +51,7 @@
   <suppress checks="ClassFanOutComplexity" files="AbstractChangeRequestActionHandler.java"/>
   <suppress checks="ClassFanOutComplexity" files="XWikiDocumentDelegateApproverManager.java"/>
   <suppress checks="ClassFanOutComplexity" files="ChangeRequestScriptService.java"/>
   <suppress checks="ClassFanOutComplexity" files="ChangeRequestResourceHandler.java"/>
   <suppress checks="CyclomaticComplexity" files="RightsUpdatedListener.java"/>
+  <suppress checks="CyclomaticComplexity" files="AbstractChangeRequestActionHandler.java"/>
 </suppressions>
diff --git a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManager.java b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManager.java
index b4065442..61080003 100644
--- a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManager.java
+++ b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManager.java
@@ -18,18 +18,22 @@
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
 package org.xwiki.contrib.changerequest.internal;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 import javax.inject.Singleton;
 
 import org.xwiki.component.annotation.Component;
 import org.xwiki.contrib.changerequest.ApproversManager;
 import org.xwiki.contrib.changerequest.ChangeRequest;
@@ -38,10 +42,11 @@ import org.xwiki.contrib.changerequest.ChangeRequestException;
 import org.xwiki.contrib.changerequest.ChangeRequestRightsManager;
 import org.xwiki.contrib.changerequest.ChangeRequestStatus;
 import org.xwiki.contrib.changerequest.DelegateApproverManager;
 import org.xwiki.contrib.changerequest.FileChange;
 import org.xwiki.contrib.changerequest.rights.ChangeRequestApproveRight;
+import org.xwiki.contrib.changerequest.rights.ChangeRequestRight;
 import org.xwiki.contrib.rights.RightsReader;
 import org.xwiki.contrib.rights.RightsWriter;
 import org.xwiki.contrib.rights.SecurityRuleAbacus;
 import org.xwiki.contrib.rights.SecurityRuleDiff;
 import org.xwiki.contrib.rights.WritableSecurityRule;
@@ -57,11 +62,16 @@ import org.xwiki.security.authorization.Right;
 import org.xwiki.security.authorization.RightSet;
 import org.xwiki.security.authorization.RuleState;
 import org.xwiki.user.GuestUserReference;
 import org.xwiki.user.UserReference;
 
+import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.PasswordClass;
+import com.xpn.xwiki.objects.classes.PropertyClass;
 
 /**
  * Component in charge of performing right synchronization operations.
  *
  * @version $Id$
@@ -96,10 +106,13 @@ public class DefaultChangeRequestRightsManager implements ChangeRequestRightsMan
     private UserReferenceConverter userReferenceConverter;
 
     @Inject
     private ChangeRequestConfiguration configuration;
 
+    @Inject
+    private Provider<XWikiContext> contextProvider;
+
     @Override
     public void copyAllButViewRights(ChangeRequest originalChangeRequest, ChangeRequest targetChangeRequest)
         throws ChangeRequestException
     {
         DocumentReference originalDocReference =
@@ -616,6 +629,66 @@ public class DefaultChangeRequestRightsManager implements ChangeRequestRightsMan
             DocumentReference crReference = this.changeRequestDocumentReferenceResolver.resolve(changeRequest);
             result = this.authorizationManager.hasAccess(Right.ADMIN, userDocReference, crReference);
         }
         return result;
     }
+
+    @Override
+    public boolean isEditWithChangeRequestAllowed(UserReference userReference, DocumentReference documentReference)
+        throws ChangeRequestException
+    {
+        DocumentReference userDocReference = this.userReferenceConverter.convert(userReference);
+        boolean hasCREdit = this.authorizationManager.hasAccess(ChangeRequestRight.getRight(), userDocReference,
+            documentReference);
+        boolean result = false;
+        // if the user doesn't have CR edit right, then it's already solved.
+        if (hasCREdit) {
+            XWikiContext context = this.contextProvider.get();
+            try {
+                XWikiDocument document = context.getWiki().getDocument(documentReference, context);
+                // if it's a new document, we don't care
+                if (document.isNew()) {
+                    result = true;
+                } else {
+                    Map<DocumentReference, List<BaseObject>> objects = document.getXObjects();
+                    boolean foundOneObjectWithPassword = false;
+                    for (Map.Entry<DocumentReference, List<BaseObject>> objectsEntry : objects.entrySet()) {
+                        Optional<BaseObject> baseObjectOpt =
+                            objectsEntry.getValue().stream().filter(Objects::nonNull).findFirst();
+                        if (baseObjectOpt.isPresent() && this.isObjectContainingPassword(baseObjectOpt.get())) {
+                            foundOneObjectWithPassword = true;
+                            break;
+                        }
+                    }
+                    if (!foundOneObjectWithPassword) {
+                        result = true;
+                    }
+                }
+            } catch (XWikiException e) {
+                throw new ChangeRequestException(String.format("Cannot load document [%s] to check if CR right should "
+                    + "be granted for user [%s]", documentReference, userDocReference), e);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isCreateWithChangeRequestAllowed(UserReference userReference, DocumentReference parentSpaceReference)
+        throws ChangeRequestException
+    {
+        DocumentReference userDocReference = this.userReferenceConverter.convert(userReference);
+        return this.authorizationManager.hasAccess(ChangeRequestRight.getRight(), userDocReference,
+            parentSpaceReference);
+    }
+
+    private boolean isObjectContainingPassword(BaseObject baseObject)
+    {
+        XWikiContext context = contextProvider.get();
+        Collection<PropertyClass> propertyClassCollection = baseObject.getXClass(context).getFieldList();
+        for (PropertyClass propertyClass : propertyClassCollection) {
+            if (propertyClass instanceof PasswordClass) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/handlers/AbstractChangeRequestActionHandler.java b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/handlers/AbstractChangeRequestActionHandler.java
index 60c26cd7..33684287 100644
--- a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/handlers/AbstractChangeRequestActionHandler.java
+++ b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/internal/handlers/AbstractChangeRequestActionHandler.java
@@ -200,10 +200,16 @@ public abstract class AbstractChangeRequestActionHandler implements ChangeReques
         throws ChangeRequestException
     {
         XWikiContext context = this.contextProvider.get();
         String serializedDocReference = request.getParameter("docReference");
         DocumentReference documentReference = this.documentReferenceResolver.resolve(serializedDocReference);
+        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
+        if (!this.changeRequestRightsManager.isEditWithChangeRequestAllowed(currentUserReference, documentReference)) {
+            throw new ChangeRequestException(
+                String.format("User [%s] is not allowed to edit the document [%s] through a change request.",
+                    currentUserReference, documentReference));
+        }
 
         XWikiDocument modifiedDocument = null;
         try {
             if (isFromChangeRequest(request) && changeRequest != null) {
                 List<FileChange> fileChangeList = this.fileChangeStorageManager.load(changeRequest, documentReference);
diff --git a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/script/ChangeRequestAuthorizationScriptService.java b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/script/ChangeRequestAuthorizationScriptService.java
index 7b33bac2..e94d30d7 100644
--- a/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/script/ChangeRequestAuthorizationScriptService.java
+++ b/application-changerequest-default/src/main/java/org/xwiki/contrib/changerequest/script/ChangeRequestAuthorizationScriptService.java
@@ -240,6 +240,35 @@ public class ChangeRequestAuthorizationScriptService implements ScriptService
     public boolean isAuthorizedToSplit(ChangeRequest changeRequest)
     {
         UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
         return this.changeRequestRightsManager.isAuthorizedToSplit(currentUserReference, changeRequest);
     }
+
+    /**
+     * Check if the current user is allowed to use change request to edit the given document reference.
+     * @param documentReference the reference for which to check authorization
+     * @return {@code true} if current user is allowed to perform the edition with change request
+     * @throws ChangeRequestException in case of problem when performing the checks
+     * @since 1.10
+     */
+    public boolean isEditWithChangeRequestAllowed(DocumentReference documentReference) throws ChangeRequestException
+    {
+        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
+        return this.changeRequestRightsManager.isEditWithChangeRequestAllowed(currentUserReference, documentReference);
+    }
+
+    /**
+     * Check if the current user is allowed to use change request to create a document having the given reference for
+     * parent.
+     * @param parentSpaceReference the parent space where the new document would be created
+     * @return {@code true} if current user is allowed to create a document with change request at the given place
+     * @throws ChangeRequestException in case of problem when performing the checks
+     * @since 1.10
+     */
+    public boolean isCreateWithChangeRequestAllowed(DocumentReference parentSpaceReference)
+        throws ChangeRequestException
+    {
+        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
+        return this.changeRequestRightsManager.isCreateWithChangeRequestAllowed(currentUserReference,
+            parentSpaceReference);
+    }
 }
diff --git a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManagerTest.java b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManagerTest.java
index 1c5ab48d..7bd46d0d 100644
--- a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManagerTest.java
+++ b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/DefaultChangeRequestRightsManagerTest.java
@@ -21,26 +21,30 @@ package org.xwiki.contrib.changerequest.internal;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import org.junit.jupiter.api.Test;
 import org.xwiki.contrib.changerequest.ApproversManager;
 import org.xwiki.contrib.changerequest.ChangeRequest;
 import org.xwiki.contrib.changerequest.ChangeRequestConfiguration;
 import org.xwiki.contrib.changerequest.ChangeRequestException;
 import org.xwiki.contrib.changerequest.ChangeRequestStatus;
 import org.xwiki.contrib.changerequest.DelegateApproverManager;
 import org.xwiki.contrib.changerequest.FileChange;
 import org.xwiki.contrib.changerequest.internal.approvers.ChangeRequestApproversManager;
+import org.xwiki.contrib.changerequest.rights.ChangeRequestRight;
 import org.xwiki.contrib.rights.RightsReader;
 import org.xwiki.contrib.rights.RightsWriter;
 import org.xwiki.contrib.rights.SecurityRuleAbacus;
 import org.xwiki.contrib.rights.SecurityRuleDiff;
 import org.xwiki.contrib.rights.WritableSecurityRule;
@@ -59,19 +63,27 @@ import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;
 import org.xwiki.user.GuestUserReference;
 import org.xwiki.user.UserReference;
 
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.BaseClass;
+import com.xpn.xwiki.objects.classes.PasswordClass;
+import com.xpn.xwiki.objects.classes.PropertyClass;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -111,10 +123,13 @@ class DefaultChangeRequestRightsManagerTest
     private ApproversManager<ChangeRequest> changeRequestApproversManager;
 
     @MockComponent
     private DelegateApproverManager<ChangeRequest> changeRequestDelegateApproverManager;
 
+    @MockComponent
+    private Provider<XWikiContext> contextProvider;
+
     @Test
     void copyAllButViewRights() throws AuthorizationException, ChangeRequestException, XWikiException
     {
         ChangeRequest sourceChangeRequest = mock(ChangeRequest.class);
         ChangeRequest targetChangeRequest = mock(ChangeRequest.class);
@@ -865,6 +880,94 @@ class DefaultChangeRequestRightsManagerTest
         assertTrue(this.rightsManager.isAuthorizedToComment(userReference, changeRequest));
 
         when(this.changeRequestApproversManager.isApprover(userReference, changeRequest, false)).thenReturn(false);
         assertFalse(this.rightsManager.isAuthorizedToComment(userReference, changeRequest));
     }
+
+    @Test
+    void isCreateWithChangeRequestAllowed() throws ChangeRequestException
+    {
+        Right crRight = ChangeRequestRight.getRight();
+        UserReference userReference = mock(UserReference.class);
+        DocumentReference userDocReference = mock(DocumentReference.class);
+        DocumentReference parentSpaceReference = mock(DocumentReference.class);
+
+        when(this.userReferenceConverter.convert(userReference)).thenReturn(userDocReference);
+        when(this.authorizationManager.hasAccess(crRight, userDocReference, parentSpaceReference)).thenReturn(true);
+        assertTrue(this.rightsManager.isCreateWithChangeRequestAllowed(userReference, parentSpaceReference));
+
+        verify(this.authorizationManager).hasAccess(crRight, userDocReference, parentSpaceReference);
+    }
+
+    @Test
+    void isEditWithChangeRequestAllowed() throws ChangeRequestException, XWikiException
+    {
+        Right crRight = ChangeRequestRight.getRight();
+        UserReference userReference = mock(UserReference.class);
+        DocumentReference userDocReference = mock(DocumentReference.class);
+        DocumentReference documentReference = mock(DocumentReference.class);
+        XWikiDocument document = mock(XWikiDocument.class);
+        XWiki wiki = mock(XWiki.class);
+        XWikiContext context = mock(XWikiContext.class);
+
+        when(this.userReferenceConverter.convert(userReference)).thenReturn(userDocReference);
+        when(this.contextProvider.get()).thenReturn(context);
+        when(context.getWiki()).thenReturn(wiki);
+        when(wiki.getDocument(documentReference, context)).thenReturn(document);
+        when(this.authorizationManager.hasAccess(crRight, userDocReference, documentReference)).thenReturn(false);
+
+        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
+
+        verify(this.authorizationManager).hasAccess(crRight, userDocReference, documentReference);
+        verifyNoInteractions(document);
+
+        when(this.authorizationManager.hasAccess(crRight, userDocReference, documentReference)).thenReturn(true);
+        when(document.isNew()).thenReturn(true);
+        assertTrue(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
+
+        verify(this.authorizationManager, times(2)).hasAccess(crRight, userDocReference, documentReference);
+
+        when(document.isNew()).thenReturn(false);
+
+        DocumentReference objReference1 = mock(DocumentReference.class);
+        DocumentReference objReference2 = mock(DocumentReference.class);
+
+        BaseObject baseObject1 = mock(BaseObject.class);
+        BaseObject baseObject2 = mock(BaseObject.class);
+        BaseObject baseObject3 = mock(BaseObject.class);
+
+        List<BaseObject> baseObjectList1 = Arrays.asList(null, baseObject1);
+        List<BaseObject> baseObjectList2 = Arrays.asList(null, baseObject2, baseObject3);
+
+        Map<DocumentReference, List<BaseObject>> objectMap = new LinkedHashMap<>();
+        objectMap.put(objReference1, baseObjectList1);
+        objectMap.put(objReference2, baseObjectList2);
+
+        when(document.getXObjects()).thenReturn(objectMap);
+        BaseClass class1 = mock(BaseClass.class);
+        List<PropertyClass> propertyClasses = List.of(mock(PropertyClass.class), mock(PropertyClass.class),
+            mock(PasswordClass.class));
+        when(class1.getFieldList()).thenReturn(propertyClasses);
+        when(baseObject1.getXClass(context)).thenReturn(class1);
+
+        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
+        verify(baseObject1).getXClass(context);
+        verifyNoInteractions(baseObject2);
+        verifyNoInteractions(baseObject3);
+
+        when(class1.getFieldList()).thenReturn(List.of(mock(PropertyClass.class)));
+        BaseClass class2 = mock(BaseClass.class);
+        when(class2.getFieldList()).thenReturn(List.of(mock(PasswordClass.class)));
+        when(baseObject2.getXClass(context)).thenReturn(class2);
+
+        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
+        verify(baseObject1, times(2)).getXClass(context);
+        verify(baseObject2).getXClass(context);
+        verifyNoInteractions(baseObject3);
+
+        when(class2.getFieldList()).thenReturn(List.of());
+        assertTrue(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
+        verify(baseObject1, times(3)).getXClass(context);
+        verify(baseObject2, times(2)).getXClass(context);
+        verifyNoInteractions(baseObject3);
+    }
 }
diff --git a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/AddChangesChangeRequestHandlerTest.java b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/AddChangesChangeRequestHandlerTest.java
index 0228d19b..f70d8b97 100644
--- a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/AddChangesChangeRequestHandlerTest.java
+++ b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/AddChangesChangeRequestHandlerTest.java
@@ -28,10 +28,11 @@ import javax.inject.Provider;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.suigeneris.jrcs.rcs.Version;
 import org.xwiki.contrib.changerequest.ApproversManager;
 import org.xwiki.contrib.changerequest.ChangeRequest;
+import org.xwiki.contrib.changerequest.ChangeRequestException;
 import org.xwiki.contrib.changerequest.ChangeRequestManager;
 import org.xwiki.contrib.changerequest.ChangeRequestMergeManager;
 import org.xwiki.contrib.changerequest.ChangeRequestReference;
 import org.xwiki.contrib.changerequest.ChangeRequestRightsManager;
 import org.xwiki.contrib.changerequest.FileChange;
@@ -57,13 +58,16 @@ import com.xpn.xwiki.doc.XWikiDocumentArchive;
 import com.xpn.xwiki.store.XWikiVersioningStoreInterface;
 import com.xpn.xwiki.web.EditForm;
 import com.xpn.xwiki.web.XWikiRequest;
 import com.xpn.xwiki.web.XWikiResponse;
 
+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.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
  * Tests for {@link AddChangesChangeRequestHandler}.
@@ -183,10 +187,12 @@ class AddChangesChangeRequestHandlerTest
         DocumentReference changeRequestDocReference = mock(DocumentReference.class);
         when(this.changeRequestDocumentReferenceResolver.resolve(changeRequest)).thenReturn(changeRequestDocReference);
         String url = "some url";
         when(wiki.getURL(changeRequestDocReference, "view", context)).thenReturn(url);
 
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(true);
         this.handler.handle(changeRequestReference);
         verify(this.requestParameterConverter).convert(request, response);
         verify(document).clone();
         verify(document).readFromForm(any(EditForm.class), eq(context));
         verify(changeRequest).addFileChange(expectedFileChange);
@@ -208,11 +214,11 @@ class AddChangesChangeRequestHandlerTest
         XWikiResponse response = mock(XWikiResponse.class);
         when(context.getResponse()).thenReturn(response);
         when(this.requestParameterConverter.convert(request, response)).thenReturn(Optional.of(request));
         String docReference = "XWiki.Doc.Reference";
         when(request.getParameter("docReference")).thenReturn(docReference);
-        DocumentReference documentReference = mock(DocumentReference.class);
+        DocumentReference documentReference = mock(DocumentReference.class, "editedDoc");
         when(this.documentReferenceResolver.resolve(docReference)).thenReturn(documentReference);
         XWikiDocument document = mock(XWikiDocument.class);
         when(wiki.getDocument(documentReference, context)).thenReturn(document);
         when(document.clone()).thenReturn(document);
         when(document.getDocumentReferenceWithLocale()).thenReturn(documentReference);
@@ -220,11 +226,11 @@ class AddChangesChangeRequestHandlerTest
         String changeRequestId = "some id";
         when(changeRequestReference.getId()).thenReturn(changeRequestId);
         ChangeRequest changeRequest = mock(ChangeRequest.class);
         when(this.storageManager.load(changeRequestId)).thenReturn(Optional.of(changeRequest));
         when(changeRequest.getId()).thenReturn(changeRequestId);
-        UserReference userReference = mock(UserReference.class);
+        UserReference userReference = mock(UserReference.class, "currentUser");
         when(this.userReferenceResolver.resolve(CurrentUserReference.INSTANCE)).thenReturn(userReference);
         when(request.getParameter(AddChangesChangeRequestHandler.PREVIOUS_VERSION_PARAMETER)).thenReturn("2.1");
 
         XWikiDocumentArchive documentArchive = mock(XWikiDocumentArchive.class);
         when(versioningStore.getXWikiDocumentArchive(document, context)).thenReturn(documentArchive);
@@ -259,13 +265,22 @@ class AddChangesChangeRequestHandlerTest
         DocumentReference changeRequestDocReference = mock(DocumentReference.class);
         when(this.changeRequestDocumentReferenceResolver.resolve(changeRequest)).thenReturn(changeRequestDocReference);
         String url = "some url";
         when(wiki.getURL(changeRequestDocReference, "view", context)).thenReturn(url);
 
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(false);
+        ChangeRequestException changeRequestException =
+            assertThrows(ChangeRequestException.class, () -> this.handler.handle(changeRequestReference));
+        assertEquals("User [currentUser] is not allowed to edit the document [editedDoc] through a change request.",
+            changeRequestException.getMessage());
+
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(true);
         this.handler.handle(changeRequestReference);
 
-        verify(this.requestParameterConverter).convert(request, response);
+        verify(this.requestParameterConverter, times(2)).convert(request, response);
         verify(document).clone();
         verify(document).readFromForm(any(EditForm.class), eq(context));
         verify(changeRequest).addFileChange(expectedFileChange);
         verify(this.storageManager).save(changeRequest);
         verify(this.changeRequestApproversManager).getAllApprovers(changeRequest, false);
diff --git a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/CreateChangeRequestHandlerTest.java b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/CreateChangeRequestHandlerTest.java
index 6a3d96c9..ca9deff0 100644
--- a/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/CreateChangeRequestHandlerTest.java
+++ b/application-changerequest-default/src/test/java/org/xwiki/contrib/changerequest/internal/handlers/CreateChangeRequestHandlerTest.java
@@ -28,10 +28,12 @@ import javax.inject.Provider;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.suigeneris.jrcs.rcs.Version;
 import org.xwiki.contrib.changerequest.ChangeRequest;
+import org.xwiki.contrib.changerequest.ChangeRequestException;
+import org.xwiki.contrib.changerequest.ChangeRequestRightsManager;
 import org.xwiki.contrib.changerequest.ChangeRequestStatus;
 import org.xwiki.contrib.changerequest.FileChange;
 import org.xwiki.contrib.changerequest.events.ChangeRequestCreatedEvent;
 import org.xwiki.contrib.changerequest.storage.ChangeRequestStorageManager;
 import org.xwiki.model.reference.DocumentReference;
@@ -52,10 +54,11 @@ import com.xpn.xwiki.doc.XWikiDocumentArchive;
 import com.xpn.xwiki.store.XWikiVersioningStoreInterface;
 import com.xpn.xwiki.web.XWikiRequest;
 import com.xpn.xwiki.web.XWikiResponse;
 
 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.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -93,10 +96,13 @@ class CreateChangeRequestHandlerTest
     private RequestParameterConverter requestParameterConverter;
 
     @MockComponent
     private UserReferenceResolver<CurrentUserReference> userReferenceResolver;
 
+    @MockComponent
+    private ChangeRequestRightsManager changeRequestRightsManager;
+
     private XWikiContext context;
     private XWikiRequest httpServletRequest;
     private XWikiResponse httpServletResponse;
     private XWiki xWiki;
     private XWikiVersioningStoreInterface versioningStore;
@@ -124,11 +130,11 @@ class CreateChangeRequestHandlerTest
 
         when(this.requestParameterConverter.convert(this.httpServletRequest, this.httpServletResponse))
             .thenReturn(Optional.of(this.httpServletRequest));
         String serializedReference = "XWiki.SomeReference";
         when(this.httpServletRequest.getParameter("docReference")).thenReturn(serializedReference);
-        DocumentReference documentReference = mock(DocumentReference.class);
+        DocumentReference documentReference = mock(DocumentReference.class, "editedDoc");
         when(this.documentReferenceResolver.resolve(serializedReference)).thenReturn(documentReference);
         XWikiDocument modifiedDocument = mock(XWikiDocument.class);
         when(this.xWiki.getDocument(documentReference, this.context)).thenReturn(modifiedDocument);
         when(modifiedDocument.clone()).thenReturn(modifiedDocument);
         DocumentReference documentReferenceWithLocale = mock(DocumentReference.class);
@@ -137,11 +143,11 @@ class CreateChangeRequestHandlerTest
         String title = "some title";
         String description = "some description";
         when(this.httpServletRequest.getParameter("crTitle")).thenReturn(title);
         when(this.httpServletRequest.getParameter("crDescription")).thenReturn(description);
 
-        UserReference userReference = mock(UserReference.class);
+        UserReference userReference = mock(UserReference.class, "currentUser");
         when(this.userReferenceResolver.resolve(CurrentUserReference.INSTANCE)).thenReturn(userReference);
         String previousVersion = "3.2";
         when(this.httpServletRequest.getParameter("previousVersion")).thenReturn(previousVersion);
         XWikiDocumentArchive documentArchive = mock(XWikiDocumentArchive.class);
         when(versioningStore.getXWikiDocumentArchive(modifiedDocument, context)).thenReturn(documentArchive);
@@ -182,11 +188,23 @@ class CreateChangeRequestHandlerTest
         DocumentReference crDocReference = mock(DocumentReference.class);
         when(this.changeRequestDocumentReferenceResolver.resolve(expectedChangeRequest)).thenReturn(crDocReference);
         String expectedURL = "/mycr";
         when(this.xWiki.getURL(crDocReference, "view", this.context)).thenReturn(expectedURL);
 
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(false);
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(false);
+        ChangeRequestException changeRequestException =
+            assertThrows(ChangeRequestException.class, () -> this.handler.handle(null));
+        assertEquals("User [currentUser] is not allowed to edit the document [editedDoc] through a change request.",
+            changeRequestException.getMessage());
+
+        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
+            .thenReturn(true);
         this.handler.handle(null);
+
         verify(this.storageManager).save(expectedChangeRequest);
         verify(this.observationManager)
             .notify(any(ChangeRequestCreatedEvent.class), eq(crId), eq(expectedChangeRequest));
         verify(this.httpServletResponse).sendRedirect(expectedURL);
     }
diff --git a/application-changerequest-ui/src/main/resources/ChangeRequest/Code/EditPageUIX.xml b/application-changerequest-ui/src/main/resources/ChangeRequest/Code/EditPageUIX.xml
index f917c721..5cd2f23f 100644
--- a/application-changerequest-ui/src/main/resources/ChangeRequest/Code/EditPageUIX.xml
+++ b/application-changerequest-ui/src/main/resources/ChangeRequest/Code/EditPageUIX.xml
@@ -291,11 +291,12 @@
     <property>
       <content>{{velocity}}
 {{html clean='false'}}
 #template("xwikivars.vm")
 #set ($crEditName = "changerequest")
-#set ($hasCREdit = $services.security.authorization.isRightRegistered($crEditName) &amp;&amp; $services.security.authorization.hasAccess($crEditName, $tdoc))
+#set ($hasCREdit = $services.changerequest.authorization.isEditWithChangeRequestAllowed($doc.getDocumentReference()))
+#set ($hasCRCreate = $services.changerequest.authorization.isCreateWithChangeRequestAllowed($doc.getDocumentReference()))
 #set ($hasEdit = $services.security.authorization.hasAccess('edit', $tdoc))
 #set ($hasCreateButton = ($hasCreateSpace || $hasCreatePage))
 #set ($editURL = $doc.getURL('editcr'))
 #set ($createURL = $doc.getURL('createcr'))
 #if (!$isReadOnly &amp;&amp; $hasCREdit)
@@ -304,10 +305,12 @@
   &lt;a href="$editURL" role="button" title="$services.localization.render('changerequest.edit.button.title')" class="btn btn-default"&gt;
     $services.icon.renderHTML('pencil') $services.localization.render('changerequest.edit.button.value')
   &lt;/a&gt;
 &lt;/div&gt;
 #end
+#end
+#if (!$isReadOnly &amp;&amp; $hasCRCreate)
 #if (!$hasCreateButton)
 &lt;div class="btn-group" id="crCreate"&gt;
   &lt;a href="$createURL" role="button" title="$services.localization.render('changerequest.create.button.title')" class="btn btn-default"&gt;
     $services.icon.renderHTML('add') $services.localization.render('changerequest.create.button.value')
   &lt;/a&gt;
diff --git a/application-changerequest-ui/src/main/resources/ChangeRequest/Code/SaveButtonsUIX.xml b/application-changerequest-ui/src/main/resources/ChangeRequest/Code/SaveButtonsUIX.xml
index ea5de517..5a27a986 100644
--- a/application-changerequest-ui/src/main/resources/ChangeRequest/Code/SaveButtonsUIX.xml
+++ b/application-changerequest-ui/src/main/resources/ChangeRequest/Code/SaveButtonsUIX.xml
@@ -368,11 +368,11 @@
       <async_enabled>0</async_enabled>
     </property>
     <property>
       <content>{{velocity}}
 #set ($crEditName = "changerequest")
-#set ($hasCREdit = $services.security.authorization.isRightRegistered($crEditName) &amp;&amp; $services.security.authorization.hasAccess($crEditName, $tdoc))
+#set ($hasCREdit = $services.changerequest.authorization.isEditWithChangeRequestAllowed($doc.getDocumentReference()))
 ## we need to also inject this in view action, because of the inplace editing mode.
 #set ($isEditOrViewAction = ($xcontext.action == 'edit' || $xcontext.action == 'view'))
 #if ($hasCREdit &amp;&amp; $isEditOrViewAction)
   #set ($hasEditRight = $services.security.authorization.hasAccess('edit', $doc))
   #set ($createURL = $services.changerequest.getChangeRequestURL('create', ''))

Vulnerable code:         DocumentReference documentReference = mock(DocumentReference.class);
        UserReference userReference = mock(UserReference.class);
        verify(this.requestParameterConverter).convert(request, response);
        DocumentReference documentReference = mock(DocumentReference.class);
        UserReference userReference = mock(UserReference.class);
#set ($hasCREdit = $services.security.authorization.isRightRegistered($crEditName) &amp;&amp; $services.security.authorization.hasAccess($crEditName, $tdoc))
#set ($hasCREdit = $services.security.authorization.isRightRegistered($crEditName) &amp;&amp; $services.security.authorization.hasAccess($crEditName, $tdoc))
Fixed code: 
    /**
     * Check if the given user is allowed to use change request to edit the given document reference.
     * @param userReference the user for whom to perform the check
     * @param documentReference the reference for which to check authorization
     * @return {@code true} if the given user is allowed to perform the edition with change request
     * @throws ChangeRequestException in case of problem when performing the check
     * @since 1.10
     */
    default boolean isEditWithChangeRequestAllowed(UserReference userReference, DocumentReference documentReference)
        throws ChangeRequestException
    {
        return false;
    }

    /**
     * Check if the given user is allowed to use change request to create a document having the given reference for
     * parent.
     * @param userReference the user for whom to perform the check
     * @param parentSpaceReference the parent space where the new document would be created
     * @return {@code true} if the given user is allowed to create a document with change request at the given place
     * @throws ChangeRequestException in case of problem when performing the check
     * @since 1.10
     */
    default boolean isCreateWithChangeRequestAllowed(UserReference userReference,
        DocumentReference parentSpaceReference) throws ChangeRequestException
    {
        return false;
    }
  <suppress checks="CyclomaticComplexity" files="AbstractChangeRequestActionHandler.java"/>
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Provider;
import org.xwiki.contrib.changerequest.rights.ChangeRequestRight;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.classes.PasswordClass;
import com.xpn.xwiki.objects.classes.PropertyClass;
    @Inject
    private Provider<XWikiContext> contextProvider;


    @Override
    public boolean isEditWithChangeRequestAllowed(UserReference userReference, DocumentReference documentReference)
        throws ChangeRequestException
    {
        DocumentReference userDocReference = this.userReferenceConverter.convert(userReference);
        boolean hasCREdit = this.authorizationManager.hasAccess(ChangeRequestRight.getRight(), userDocReference,
            documentReference);
        boolean result = false;
        // if the user doesn't have CR edit right, then it's already solved.
        if (hasCREdit) {
            XWikiContext context = this.contextProvider.get();
            try {
                XWikiDocument document = context.getWiki().getDocument(documentReference, context);
                // if it's a new document, we don't care
                if (document.isNew()) {
                    result = true;
                } else {
                    Map<DocumentReference, List<BaseObject>> objects = document.getXObjects();
                    boolean foundOneObjectWithPassword = false;
                    for (Map.Entry<DocumentReference, List<BaseObject>> objectsEntry : objects.entrySet()) {
                        Optional<BaseObject> baseObjectOpt =
                            objectsEntry.getValue().stream().filter(Objects::nonNull).findFirst();
                        if (baseObjectOpt.isPresent() && this.isObjectContainingPassword(baseObjectOpt.get())) {
                            foundOneObjectWithPassword = true;
                            break;
                        }
                    }
                    if (!foundOneObjectWithPassword) {
                        result = true;
                    }
                }
            } catch (XWikiException e) {
                throw new ChangeRequestException(String.format("Cannot load document [%s] to check if CR right should "
                    + "be granted for user [%s]", documentReference, userDocReference), e);
            }
        }
        return result;
    }

    @Override
    public boolean isCreateWithChangeRequestAllowed(UserReference userReference, DocumentReference parentSpaceReference)
        throws ChangeRequestException
    {
        DocumentReference userDocReference = this.userReferenceConverter.convert(userReference);
        return this.authorizationManager.hasAccess(ChangeRequestRight.getRight(), userDocReference,
            parentSpaceReference);
    }

    private boolean isObjectContainingPassword(BaseObject baseObject)
    {
        XWikiContext context = contextProvider.get();
        Collection<PropertyClass> propertyClassCollection = baseObject.getXClass(context).getFieldList();
        for (PropertyClass propertyClass : propertyClassCollection) {
            if (propertyClass instanceof PasswordClass) {
                return true;
            }
        }
        return false;
    }
        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
        if (!this.changeRequestRightsManager.isEditWithChangeRequestAllowed(currentUserReference, documentReference)) {
            throw new ChangeRequestException(
                String.format("User [%s] is not allowed to edit the document [%s] through a change request.",
                    currentUserReference, documentReference));
        }

    /**
     * Check if the current user is allowed to use change request to edit the given document reference.
     * @param documentReference the reference for which to check authorization
     * @return {@code true} if current user is allowed to perform the edition with change request
     * @throws ChangeRequestException in case of problem when performing the checks
     * @since 1.10
     */
    public boolean isEditWithChangeRequestAllowed(DocumentReference documentReference) throws ChangeRequestException
    {
        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
        return this.changeRequestRightsManager.isEditWithChangeRequestAllowed(currentUserReference, documentReference);
    }

    /**
     * Check if the current user is allowed to use change request to create a document having the given reference for
     * parent.
     * @param parentSpaceReference the parent space where the new document would be created
     * @return {@code true} if current user is allowed to create a document with change request at the given place
     * @throws ChangeRequestException in case of problem when performing the checks
     * @since 1.10
     */
    public boolean isCreateWithChangeRequestAllowed(DocumentReference parentSpaceReference)
        throws ChangeRequestException
    {
        UserReference currentUserReference = this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE);
        return this.changeRequestRightsManager.isCreateWithChangeRequestAllowed(currentUserReference,
            parentSpaceReference);
    }
import java.util.LinkedHashMap;
import java.util.Map;
import javax.inject.Provider;
import org.xwiki.contrib.changerequest.rights.ChangeRequestRight;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.objects.classes.PasswordClass;
import com.xpn.xwiki.objects.classes.PropertyClass;
import static org.mockito.Mockito.times;
    @MockComponent
    private Provider<XWikiContext> contextProvider;


    @Test
    void isCreateWithChangeRequestAllowed() throws ChangeRequestException
    {
        Right crRight = ChangeRequestRight.getRight();
        UserReference userReference = mock(UserReference.class);
        DocumentReference userDocReference = mock(DocumentReference.class);
        DocumentReference parentSpaceReference = mock(DocumentReference.class);

        when(this.userReferenceConverter.convert(userReference)).thenReturn(userDocReference);
        when(this.authorizationManager.hasAccess(crRight, userDocReference, parentSpaceReference)).thenReturn(true);
        assertTrue(this.rightsManager.isCreateWithChangeRequestAllowed(userReference, parentSpaceReference));

        verify(this.authorizationManager).hasAccess(crRight, userDocReference, parentSpaceReference);
    }

    @Test
    void isEditWithChangeRequestAllowed() throws ChangeRequestException, XWikiException
    {
        Right crRight = ChangeRequestRight.getRight();
        UserReference userReference = mock(UserReference.class);
        DocumentReference userDocReference = mock(DocumentReference.class);
        DocumentReference documentReference = mock(DocumentReference.class);
        XWikiDocument document = mock(XWikiDocument.class);
        XWiki wiki = mock(XWiki.class);
        XWikiContext context = mock(XWikiContext.class);

        when(this.userReferenceConverter.convert(userReference)).thenReturn(userDocReference);
        when(this.contextProvider.get()).thenReturn(context);
        when(context.getWiki()).thenReturn(wiki);
        when(wiki.getDocument(documentReference, context)).thenReturn(document);
        when(this.authorizationManager.hasAccess(crRight, userDocReference, documentReference)).thenReturn(false);

        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));

        verify(this.authorizationManager).hasAccess(crRight, userDocReference, documentReference);
        verifyNoInteractions(document);

        when(this.authorizationManager.hasAccess(crRight, userDocReference, documentReference)).thenReturn(true);
        when(document.isNew()).thenReturn(true);
        assertTrue(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));

        verify(this.authorizationManager, times(2)).hasAccess(crRight, userDocReference, documentReference);

        when(document.isNew()).thenReturn(false);

        DocumentReference objReference1 = mock(DocumentReference.class);
        DocumentReference objReference2 = mock(DocumentReference.class);

        BaseObject baseObject1 = mock(BaseObject.class);
        BaseObject baseObject2 = mock(BaseObject.class);
        BaseObject baseObject3 = mock(BaseObject.class);

        List<BaseObject> baseObjectList1 = Arrays.asList(null, baseObject1);
        List<BaseObject> baseObjectList2 = Arrays.asList(null, baseObject2, baseObject3);

        Map<DocumentReference, List<BaseObject>> objectMap = new LinkedHashMap<>();
        objectMap.put(objReference1, baseObjectList1);
        objectMap.put(objReference2, baseObjectList2);

        when(document.getXObjects()).thenReturn(objectMap);
        BaseClass class1 = mock(BaseClass.class);
        List<PropertyClass> propertyClasses = List.of(mock(PropertyClass.class), mock(PropertyClass.class),
            mock(PasswordClass.class));
        when(class1.getFieldList()).thenReturn(propertyClasses);
        when(baseObject1.getXClass(context)).thenReturn(class1);

        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
        verify(baseObject1).getXClass(context);
        verifyNoInteractions(baseObject2);
        verifyNoInteractions(baseObject3);

        when(class1.getFieldList()).thenReturn(List.of(mock(PropertyClass.class)));
        BaseClass class2 = mock(BaseClass.class);
        when(class2.getFieldList()).thenReturn(List.of(mock(PasswordClass.class)));
        when(baseObject2.getXClass(context)).thenReturn(class2);

        assertFalse(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
        verify(baseObject1, times(2)).getXClass(context);
        verify(baseObject2).getXClass(context);
        verifyNoInteractions(baseObject3);

        when(class2.getFieldList()).thenReturn(List.of());
        assertTrue(this.rightsManager.isEditWithChangeRequestAllowed(userReference, documentReference));
        verify(baseObject1, times(3)).getXClass(context);
        verify(baseObject2, times(2)).getXClass(context);
        verifyNoInteractions(baseObject3);
    }
import org.xwiki.contrib.changerequest.ChangeRequestException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.times;
        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(true);
        DocumentReference documentReference = mock(DocumentReference.class, "editedDoc");
        UserReference userReference = mock(UserReference.class, "currentUser");
        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(false);
        ChangeRequestException changeRequestException =
            assertThrows(ChangeRequestException.class, () -> this.handler.handle(changeRequestReference));
        assertEquals("User [currentUser] is not allowed to edit the document [editedDoc] through a change request.",
            changeRequestException.getMessage());

        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(true);
        verify(this.requestParameterConverter, times(2)).convert(request, response);
import org.xwiki.contrib.changerequest.ChangeRequestException;
import org.xwiki.contrib.changerequest.ChangeRequestRightsManager;
import static org.junit.jupiter.api.Assertions.assertThrows;
    @MockComponent
    private ChangeRequestRightsManager changeRequestRightsManager;

        DocumentReference documentReference = mock(DocumentReference.class, "editedDoc");
        UserReference userReference = mock(UserReference.class, "currentUser");
        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(false);
        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(false);
        ChangeRequestException changeRequestException =
            assertThrows(ChangeRequestException.class, () -> this.handler.handle(null));
        assertEquals("User [currentUser] is not allowed to edit the document [editedDoc] through a change request.",
            changeRequestException.getMessage());

        when(this.changeRequestRightsManager.isEditWithChangeRequestAllowed(userReference, documentReference))
            .thenReturn(true);

#set ($hasCREdit = $services.changerequest.authorization.isEditWithChangeRequestAllowed($doc.getDocumentReference()))
#set ($hasCRCreate = $services.changerequest.authorization.isCreateWithChangeRequestAllowed($doc.getDocumentReference()))
#end
#if (!$isReadOnly &amp;&amp; $hasCRCreate)
#set ($hasCREdit = $services.changerequest.authorization.isEditWithChangeRequestAllowed($doc.getDocumentReference()))