CVE-2024-31983: Insufficient Information (NVD-CWE-noinfo)
value: XWiki Platform is a generic wiki platform. In multilingual wikis, translations can be edited by any user who has edit right, circumventing the rights that are normally required for authoring translations (script right for user-scope translations, wiki admin for translations on the wiki). Starting in version 4.3-milestone-2 and prior to versions 4.10.20, 15.5.4, and 15.10-rc-1, this can be exploited for remote code execution if the translation value is not properly escaped where it is used. This has been patched in XWiki 14.10.20, 15.5.4 and 15.10RC1. As a workaround, one may restrict edit rights on documents that contain translations.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-10T20:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/AbstractDocumentTranslationBundle.java, xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundle.java, xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/DocumentTranslationBundleFactory.java, xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/test/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundleTest.java
Commit date: 2023-11-16 16:06:02 +0100
Commit message: XWIKI-21411: Improve check of translation document author rights

* Check both author and content author and improve JavaDoc

Diff: diff --git a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/AbstractDocumentTranslationBundle.java b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/AbstractDocumentTranslationBundle.java
index 8549a4de0c17d..d5028991f1d3c 100644
--- a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/AbstractDocumentTranslationBundle.java
+++ b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/AbstractDocumentTranslationBundle.java
@@ -136,43 +136,61 @@ protected void setReference(DocumentReference reference)
         this.documentReference = reference;
 
         setId(this.idPrefix + this.serializer.serialize(reference));
     }
 
-    protected LocalizedTranslationBundle loadDocumentLocaleBundle(Locale locale) throws Exception
+    /**
+     * Gets the document that defines the translation bundle for a given locale.
+     *
+     * @param locale the requested locale
+     * @return the document defining the translation bundle, or null if it could not be fetched yet and requires a retry
+     */
+    protected XWikiDocument getDocumentLocaleBundle(Locale locale) throws Exception
     {
         XWikiContext context = this.contextProvider.get();
 
         if (context == null) {
-            // No context for some reason, lets try later
+            // No context for some reason, let's try later.
             return null;
         }
 
         XWiki xwiki = context.getWiki();
 
         if (xwiki == null) {
-            // No XWiki instance ready, lets try later
+            // No XWiki instance ready, let's try later.
             return null;
         }
 
         XWikiDocument document = xwiki.getDocument(this.documentReference, context);
 
         if (locale != null && !locale.equals(Locale.ROOT) && !locale.equals(document.getDefaultLocale())) {
             document = xwiki.getDocument(new DocumentReference(document.getDocumentReference(), locale), context);
+        }
 
-            if (document.isNew()) {
-                // No document found for this locale
-                return LocalizedTranslationBundle.EMPTY;
-            }
+        return document;
+    }
+
+    protected LocalizedTranslationBundle loadDocumentLocaleBundle(Locale locale) throws Exception
+    {
+        XWikiDocument document = getDocumentLocaleBundle(locale);
+
+        if (document == null) {
+            // Either no context or XWiki instance not ready, let's try later.
+            return null;
+        }
+
+        if (document.isNew()) {
+            // No document found for this locale.
+            return LocalizedTranslationBundle.EMPTY;
         }
 
         String content = document.getContent();
 
         Properties properties = new Properties();
         properties.load(new StringReader(content));
 
-        // Convert to LocalBundle
+        // Convert to LocalBundle.
         DefaultLocalizedTranslationBundle localeBundle = new DefaultLocalizedTranslationBundle(this, locale);
 
         TranslationMessageParser parser = getTranslationMessageParser();
 
         for (Map.Entry<Object, Object> entry : properties.entrySet()) {
diff --git a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundle.java b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundle.java
index c4fd0923d6f83..65b3951b7823a 100644
--- a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundle.java
+++ b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundle.java
@@ -17,47 +17,93 @@
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
 package org.xwiki.localization.wiki.internal;
 
+import java.util.Locale;
+
 import org.xwiki.bridge.event.WikiDeletedEvent;
 import org.xwiki.component.descriptor.ComponentDescriptor;
 import org.xwiki.component.manager.ComponentLookupException;
 import org.xwiki.component.manager.ComponentManager;
 import org.xwiki.localization.TranslationBundle;
 import org.xwiki.localization.message.TranslationMessageParser;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.observation.event.Event;
+import org.xwiki.security.authorization.AccessDeniedException;
+
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.doc.XWikiDocument;
 
 /**
  * Component wiki document based implementation of Bundle.
  * 
  * @see AbstractDocumentTranslationBundle
  * @version $Id$
  * @since 6.2RC1
  */
 public class ComponentDocumentTranslationBundle extends AbstractDocumentTranslationBundle
 {
+    private DocumentTranslationBundleFactory factory;
+
     private ComponentDescriptor<TranslationBundle> descriptor;
 
     /**
      * @param idPrefix the prefix to use when generating the bundle unique identifier
      * @param documentReference the document reference
      * @param componentManager used to lookup components needed to manipulate wiki documents
      * @param translationMessageParser the parser to use for each message
      * @param descriptor the component descriptor used to unregister the bundle
+     * @param factory the factory
      * @throws ComponentLookupException failed to lookup some required components
      */
     public ComponentDocumentTranslationBundle(String idPrefix, DocumentReference documentReference,
         ComponentManager componentManager, TranslationMessageParser translationMessageParser,
-        ComponentDescriptor<TranslationBundle> descriptor) throws ComponentLookupException
+        ComponentDescriptor<TranslationBundle> descriptor, DocumentTranslationBundleFactory factory)
+        throws ComponentLookupException
     {
         super(idPrefix, documentReference, componentManager, translationMessageParser);
 
+        this.factory = factory;
         this.descriptor = descriptor;
     }
 
+    /**
+     * {@inheritDoc}
+     * This overrides the default implementation to first check the author rights for the document.
+     *
+     * @param locale the requested locale
+     * @return the document defining the translation bundle if its author has the necessary rights, the default locale
+     *     otherwise, and fallback on the original implementation if the document doesn't exist or could not be fetched
+     */
+    @Override
+    protected XWikiDocument getDocumentLocaleBundle(Locale locale) throws Exception
+    {
+        XWikiDocument document = super.getDocumentLocaleBundle(locale);
+
+        if (document != null && !document.isNew()) {
+            XWikiContext context = this.contextProvider.get();
+            XWiki xwiki = context.getWiki();
+            XWikiDocument defaultLocaleDocument = xwiki.getDocument(this.documentReference, context);
+
+            if (defaultLocaleDocument != document) {
+                // We only need to check rights for non-default locales.
+                try {
+                    this.factory.checkRegistrationAuthorizationForDocumentLocaleBundle(document, defaultLocaleDocument);
+                } catch (AccessDeniedException e) {
+                    this.logger.warn("Failed to load and register the translation for locale [{}] from document [{}]. "
+                        + "Falling back to default locale.", locale, document.getDocumentReference());
+                    // We return the default translation bundle if the requested one has permission issues.
+                    return defaultLocaleDocument;
+                }
+            }
+        }
+
+        return document;
+    }
+
     @Override
     public void onEvent(Event event, Object source, Object data)
     {
         super.onEvent(event, source, data);
 
diff --git a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/DocumentTranslationBundleFactory.java b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/DocumentTranslationBundleFactory.java
index 233aefc5e0ead..f045772aae297 100644
--- a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/DocumentTranslationBundleFactory.java
+++ b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/main/java/org/xwiki/localization/wiki/internal/DocumentTranslationBundleFactory.java
@@ -54,10 +54,11 @@
 import org.xwiki.localization.TranslationBundleFactory;
 import org.xwiki.localization.message.TranslationMessageParser;
 import org.xwiki.localization.wiki.internal.TranslationDocumentModel.Scope;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.model.reference.DocumentReferenceResolver;
+import org.xwiki.model.reference.EntityReference;
 import org.xwiki.model.reference.EntityReferenceSerializer;
 import org.xwiki.model.reference.WikiReference;
 import org.xwiki.observation.EventListener;
 import org.xwiki.observation.ObservationManager;
 import org.xwiki.observation.event.Event;
@@ -322,11 +323,11 @@ private ComponentDocumentTranslationBundle createComponentDocumentBundle(XWikiDo
     {
         ComponentDocumentTranslationBundle documentBundle;
         try {
             documentBundle =
                 new ComponentDocumentTranslationBundle(ID_PREFIX, document.getDocumentReference(),
-                    this.componentManagerProvider.get(), this.translationParser, descriptor);
+                    this.componentManagerProvider.get(), this.translationParser, descriptor, this);
         } catch (ComponentLookupException e) {
             throw new TranslationBundleDoesNotExistsException("Failed to create document bundle", e);
         }
 
         return documentBundle;
@@ -432,30 +433,53 @@ private void registerTranslationBundle(XWikiDocument document) throws Translatio
 
             getComponentManager(document, scope, true).registerComponent(descriptor, bundle);
         }
     }
 
+    /**
+     * Checks that the author of a document defining a translation bundle has the necessary rights to make it
+     * available, based on the scope of the default locale translation bundle.
+     *
+     * @param document the document defining the translation bundle to check
+     * @param defaultLocaleDocument the document containing the default locale translation bundle
+     * @throws AccessDeniedException when the document author does not have enough rights for the defined scope
+     */
+    protected void checkRegistrationAuthorizationForDocumentLocaleBundle(XWikiDocument document,
+        XWikiDocument defaultLocaleDocument) throws AccessDeniedException
+    {
+        Scope scope = getScope(defaultLocaleDocument);
+        if (scope != null && scope != Scope.ON_DEMAND) {
+            checkRegistrationAuthorization(document, scope);
+        }
+    }
+
     /**
      * @param document the translation document
      * @param scope the scope
      * @throws AccessDeniedException thrown when the document author does not have enough right for the provided
      *             {@link Scope}
      */
     private void checkRegistrationAuthorization(XWikiDocument document, Scope scope) throws AccessDeniedException
     {
+        EntityReference entityReference;
         switch (scope) {
             case GLOBAL:
                 this.authorizationManager.checkAccess(Right.PROGRAM, document.getAuthorReference(), null);
+                this.authorizationManager.checkAccess(Right.PROGRAM, document.getContentAuthorReference(), null);
                 break;
             case WIKI:
-                this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), document
-                    .getDocumentReference().getWikiReference());
+                entityReference = document.getDocumentReference().getWikiReference();
+                this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), entityReference);
+                this.authorizationManager.checkAccess(Right.ADMIN, document.getContentAuthorReference(),
+                    entityReference);
                 break;
             case USER:
                 if (this.configuration.isRestrictUserTranslations()) {
-                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getAuthorReference(),
-                        document.getDocumentReference());
+                    entityReference = document.getDocumentReference();
+                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getAuthorReference(), entityReference);
+                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getContentAuthorReference(),
+                        entityReference);
                 }
                 break;
             default:
                 break;
         }
diff --git a/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/test/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundleTest.java b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/test/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundleTest.java
new file mode 100644
index 0000000000000..a33079da91c80
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-localization/xwiki-platform-localization-sources/xwiki-platform-localization-source-wiki/src/test/java/org/xwiki/localization/wiki/internal/ComponentDocumentTranslationBundleTest.java
@@ -0,0 +1,208 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.xwiki.localization.wiki.internal;
+
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.Mock;
+import org.xwiki.cache.Cache;
+import org.xwiki.cache.CacheManager;
+import org.xwiki.cache.config.CacheConfiguration;
+import org.xwiki.component.internal.multi.ComponentManagerManager;
+import org.xwiki.job.event.status.JobProgressManager;
+import org.xwiki.localization.LocalizationManager;
+import org.xwiki.localization.Translation;
+import org.xwiki.localization.TranslationBundleFactory;
+import org.xwiki.localization.internal.DefaultLocalizationManager;
+import org.xwiki.localization.internal.DefaultTranslationBundleContext;
+import org.xwiki.localization.messagetool.internal.MessageToolTranslationMessageParser;
+import org.xwiki.model.internal.DefaultModelContext;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.observation.internal.DefaultObservationManager;
+import org.xwiki.query.Query;
+import org.xwiki.query.QueryManager;
+import org.xwiki.rendering.internal.parser.plain.PlainTextBlockParser;
+import org.xwiki.rendering.internal.renderer.plain.PlainTextBlockRenderer;
+import org.xwiki.rendering.internal.renderer.plain.PlainTextRendererFactory;
+import org.xwiki.rendering.syntax.Syntax;
+import org.xwiki.security.authorization.AccessDeniedException;
+import org.xwiki.security.authorization.Right;
+import org.xwiki.test.LogLevel;
+import org.xwiki.test.annotation.BeforeComponent;
+import org.xwiki.test.annotation.ComponentList;
+import org.xwiki.test.junit5.LogCaptureExtension;
+import org.xwiki.test.junit5.mockito.MockComponent;
+
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.test.MockitoOldcore;
+import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
+import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
+import com.xpn.xwiki.test.reference.ReferenceComponentList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link ComponentDocumentTranslationBundle}.
+ *
+ * @version $Id$
+ */
+@OldcoreTest
+@ComponentList({
+    DocumentTranslationBundleFactory.class,
+    DefaultLocalizationManager.class,
+    DefaultTranslationBundleContext.class,
+    TranslationDocumentClassInitializer.class,
+    DefaultModelContext.class,
+    PlainTextBlockRenderer.class,
+    PlainTextRendererFactory.class,
+    DefaultObservationManager.class,
+    MessageToolTranslationMessageParser.class,
+    PlainTextBlockParser.class
+})
+@ReferenceComponentList
+class ComponentDocumentTranslationBundleTest
+{
+    private static final DocumentReference TRANSLATION_ROOT_REFERENCE = new DocumentReference("xwiki", "space",
+        "Translations");
+
+    private static final DocumentReference ADMIN_USER_REFERENCE = new DocumentReference("xwiki", "XWiki", "XWikiAdmin");
+
+    @InjectMockitoOldcore
+    private MockitoOldcore oldcore;
+
+    @MockComponent
+    private WikiTranslationConfiguration translationConfiguration;
+
+    @MockComponent
+    private QueryManager mockQueryManager;
+
+    @Mock
+    private Query mockQuery;
+
+    @MockComponent
+    private ComponentManagerManager componentManagerManager;
+
+    @MockComponent
+    private JobProgressManager jobProgressManager;
+
+    @Inject
+    private LocalizationManager localization;
+
+    private XWikiDocument translationFrDocument;
+
+    private DocumentReference adminUserReference;
+
+    /**
+     * Capture logs.
+     */
+    @RegisterExtension
+    private LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);
+
+    @MockComponent
+    private CacheManager cacheManager;
+
+    @BeforeComponent
+    void before() throws Exception
+    {
+        Cache<Object> cache = mock(Cache.class);
+        when(this.cacheManager.createNewCache(any(CacheConfiguration.class))).thenReturn(cache);
+    }
+
+    @BeforeEach
+    void setUp() throws Exception
+    {
+        this.oldcore.notifyDocumentCreatedEvent(true);
+
+        when(this.mockQueryManager.createQuery(anyString(), anyString())).thenReturn(this.mockQuery);
+        when(this.mockQuery.execute()).thenReturn(Collections.emptyList());
+
+        when(this.componentManagerManager.getComponentManager("wiki:xwiki", true)).thenReturn(this.oldcore.getMocker());
+        this.oldcore.getMocker().getInstance(TranslationBundleFactory.class, DocumentTranslationBundleFactory.ID);
+
+        XWikiDocument translationRootDocument = this.oldcore.getSpyXWiki().getDocument(TRANSLATION_ROOT_REFERENCE,
+            this.oldcore.getXWikiContext());
+
+        BaseObject translationObject = translationRootDocument.newXObject(
+            new DocumentReference("xwiki", "XWiki", "TranslationDocumentClass"),
+            this.oldcore.getXWikiContext());
+        translationObject.setStringValue(TranslationDocumentModel.TRANSLATIONCLASS_PROP_SCOPE,
+            TranslationDocumentModel.Scope.WIKI.toString());
+
+        translationRootDocument.setSyntax(Syntax.PLAIN_1_0);
+        translationRootDocument.setContent("xwiki.translation=root");
+        translationRootDocument.setAuthorReference(ADMIN_USER_REFERENCE);
+        this.oldcore.getSpyXWiki().saveDocument(translationRootDocument, this.oldcore.getXWikiContext());
+
+        this.translationFrDocument = translationRootDocument.getTranslatedDocument(Locale.FRENCH,
+            this.oldcore.getXWikiContext());
+        if (this.translationFrDocument == translationRootDocument) {
+            this.translationFrDocument =
+                new XWikiDocument(this.translationFrDocument.getDocumentReference(), Locale.FRENCH);
+            this.translationFrDocument.setDefaultLocale(this.translationFrDocument.getDefaultLocale());
+        }
+        this.translationFrDocument.setSyntax(Syntax.PLAIN_1_0);
+        this.translationFrDocument.setContent("xwiki.translation=fr");
+        this.oldcore.getSpyXWiki().saveDocument(this.translationFrDocument, this.oldcore.getXWikiContext());
+
+        doThrow(new AccessDeniedException(Right.SCRIPT, null, translationRootDocument.getDocumentReference()))
+            .when(this.oldcore.getMockAuthorizationManager()).checkAccess(Right.ADMIN, null,
+                TRANSLATION_ROOT_REFERENCE.getWikiReference());
+    }
+
+    @Test
+    void checkTranslationWithExpectedRights() throws Exception
+    {
+        this.translationFrDocument.setAuthorReference(ADMIN_USER_REFERENCE);
+        this.translationFrDocument.setContentAuthorReference(ADMIN_USER_REFERENCE);
+        this.oldcore.getSpyXWiki().saveDocument(this.translationFrDocument, this.oldcore.getXWikiContext());
+        Translation frTranslation = this.localization.getTranslation("xwiki.translation", Locale.FRENCH);
+        assertEquals("fr", frTranslation.getRawSource());
+        // Authorizations are checked twice because the mocked behavior is not actual locale bundle registration.
+        verify(this.oldcore.getMockAuthorizationManager(), times(4)).checkAccess(Right.ADMIN, ADMIN_USER_REFERENCE,
+            TRANSLATION_ROOT_REFERENCE.getWikiReference());
+    }
+
+    @Test
+    void checkTranslationWithoutExpectedRights() throws Exception
+    {
+        Translation frTranslation = this.localization.getTranslation("xwiki.translation", Locale.FRENCH);
+        assertEquals(
+            "Failed to load and register the translation for locale [fr] from document [xwiki:space.Translations]. "
+                + "Falling back to default locale.",
+            this.logCapture.getMessage(0));
+        assertEquals("root", frTranslation.getRawSource());
+        verify(this.oldcore.getMockAuthorizationManager()).checkAccess(Right.ADMIN, null,
+            TRANSLATION_ROOT_REFERENCE.getWikiReference());
+    }
+}

Vulnerable code:     protected LocalizedTranslationBundle loadDocumentLocaleBundle(Locale locale) throws Exception
            // No context for some reason, lets try later
            // No XWiki instance ready, lets try later
            if (document.isNew()) {
                // No document found for this locale
                return LocalizedTranslationBundle.EMPTY;
            }
        // Convert to LocalBundle
        ComponentDescriptor<TranslationBundle> descriptor) throws ComponentLookupException
                    this.componentManagerProvider.get(), this.translationParser, descriptor);
                this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), document
                    .getDocumentReference().getWikiReference());
                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getAuthorReference(),
                        document.getDocumentReference());
Fixed code:     /**
     * Gets the document that defines the translation bundle for a given locale.
     *
     * @param locale the requested locale
     * @return the document defining the translation bundle, or null if it could not be fetched yet and requires a retry
     */
    protected XWikiDocument getDocumentLocaleBundle(Locale locale) throws Exception
            // No context for some reason, let's try later.
            // No XWiki instance ready, let's try later.
        }
        return document;
    }

    protected LocalizedTranslationBundle loadDocumentLocaleBundle(Locale locale) throws Exception
    {
        XWikiDocument document = getDocumentLocaleBundle(locale);

        if (document == null) {
            // Either no context or XWiki instance not ready, let's try later.
            return null;
        }

        if (document.isNew()) {
            // No document found for this locale.
            return LocalizedTranslationBundle.EMPTY;
        // Convert to LocalBundle.
import java.util.Locale;

import org.xwiki.security.authorization.AccessDeniedException;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.doc.XWikiDocument;
    private DocumentTranslationBundleFactory factory;

     * @param factory the factory
        ComponentDescriptor<TranslationBundle> descriptor, DocumentTranslationBundleFactory factory)
        throws ComponentLookupException
        this.factory = factory;
    /**
     * {@inheritDoc}
     * This overrides the default implementation to first check the author rights for the document.
     *
     * @param locale the requested locale
     * @return the document defining the translation bundle if its author has the necessary rights, the default locale
     *     otherwise, and fallback on the original implementation if the document doesn't exist or could not be fetched
     */
    @Override
    protected XWikiDocument getDocumentLocaleBundle(Locale locale) throws Exception
    {
        XWikiDocument document = super.getDocumentLocaleBundle(locale);

        if (document != null && !document.isNew()) {
            XWikiContext context = this.contextProvider.get();
            XWiki xwiki = context.getWiki();
            XWikiDocument defaultLocaleDocument = xwiki.getDocument(this.documentReference, context);

            if (defaultLocaleDocument != document) {
                // We only need to check rights for non-default locales.
                try {
                    this.factory.checkRegistrationAuthorizationForDocumentLocaleBundle(document, defaultLocaleDocument);
                } catch (AccessDeniedException e) {
                    this.logger.warn("Failed to load and register the translation for locale [{}] from document [{}]. "
                        + "Falling back to default locale.", locale, document.getDocumentReference());
                    // We return the default translation bundle if the requested one has permission issues.
                    return defaultLocaleDocument;
                }
            }
        }

        return document;
    }

import org.xwiki.model.reference.EntityReference;
                    this.componentManagerProvider.get(), this.translationParser, descriptor, this);
    /**
     * Checks that the author of a document defining a translation bundle has the necessary rights to make it
     * available, based on the scope of the default locale translation bundle.
     *
     * @param document the document defining the translation bundle to check
     * @param defaultLocaleDocument the document containing the default locale translation bundle
     * @throws AccessDeniedException when the document author does not have enough rights for the defined scope
     */
    protected void checkRegistrationAuthorizationForDocumentLocaleBundle(XWikiDocument document,
        XWikiDocument defaultLocaleDocument) throws AccessDeniedException
    {
        Scope scope = getScope(defaultLocaleDocument);
        if (scope != null && scope != Scope.ON_DEMAND) {
            checkRegistrationAuthorization(document, scope);
        }
    }

        EntityReference entityReference;
                this.authorizationManager.checkAccess(Right.PROGRAM, document.getContentAuthorReference(), null);
                entityReference = document.getDocumentReference().getWikiReference();
                this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), entityReference);
                this.authorizationManager.checkAccess(Right.ADMIN, document.getContentAuthorReference(),
                    entityReference);
                    entityReference = document.getDocumentReference();
                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getAuthorReference(), entityReference);
                    this.authorizationManager.checkAccess(Right.SCRIPT, document.getContentAuthorReference(),
                        entityReference);
/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.localization.wiki.internal;

import java.util.Collections;
import java.util.Locale;

import javax.inject.Inject;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.xwiki.cache.Cache;
import org.xwiki.cache.CacheManager;
import org.xwiki.cache.config.CacheConfiguration;
import org.xwiki.component.internal.multi.ComponentManagerManager;
import org.xwiki.job.event.status.JobProgressManager;
import org.xwiki.localization.LocalizationManager;
import org.xwiki.localization.Translation;
import org.xwiki.localization.TranslationBundleFactory;
import org.xwiki.localization.internal.DefaultLocalizationManager;
import org.xwiki.localization.internal.DefaultTranslationBundleContext;
import org.xwiki.localization.messagetool.internal.MessageToolTranslationMessageParser;
import org.xwiki.model.internal.DefaultModelContext;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.observation.internal.DefaultObservationManager;
import org.xwiki.query.Query;
import org.xwiki.query.QueryManager;
import org.xwiki.rendering.internal.parser.plain.PlainTextBlockParser;
import org.xwiki.rendering.internal.renderer.plain.PlainTextBlockRenderer;
import org.xwiki.rendering.internal.renderer.plain.PlainTextRendererFactory;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.Right;
import org.xwiki.test.LogLevel;
import org.xwiki.test.annotation.BeforeComponent;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.junit5.LogCaptureExtension;
import org.xwiki.test.junit5.mockito.MockComponent;

import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.test.MockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
import com.xpn.xwiki.test.reference.ReferenceComponentList;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Unit tests for {@link ComponentDocumentTranslationBundle}.
 *
 * @version $Id$
 */
@OldcoreTest
@ComponentList({
    DocumentTranslationBundleFactory.class,
    DefaultLocalizationManager.class,
    DefaultTranslationBundleContext.class,
    TranslationDocumentClassInitializer.class,
    DefaultModelContext.class,
    PlainTextBlockRenderer.class,
    PlainTextRendererFactory.class,
    DefaultObservationManager.class,
    MessageToolTranslationMessageParser.class,
    PlainTextBlockParser.class
})
@ReferenceComponentList
class ComponentDocumentTranslationBundleTest
{
    private static final DocumentReference TRANSLATION_ROOT_REFERENCE = new DocumentReference("xwiki", "space",
        "Translations");

    private static final DocumentReference ADMIN_USER_REFERENCE = new DocumentReference("xwiki", "XWiki", "XWikiAdmin");

    @InjectMockitoOldcore
    private MockitoOldcore oldcore;

    @MockComponent
    private WikiTranslationConfiguration translationConfiguration;

    @MockComponent
    private QueryManager mockQueryManager;

    @Mock
    private Query mockQuery;

    @MockComponent
    private ComponentManagerManager componentManagerManager;

    @MockComponent
    private JobProgressManager jobProgressManager;

    @Inject
    private LocalizationManager localization;

    private XWikiDocument translationFrDocument;

    private DocumentReference adminUserReference;

    /**
     * Capture logs.
     */
    @RegisterExtension
    private LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);

    @MockComponent
    private CacheManager cacheManager;

    @BeforeComponent
    void before() throws Exception
    {
        Cache<Object> cache = mock(Cache.class);
        when(this.cacheManager.createNewCache(any(CacheConfiguration.class))).thenReturn(cache);
    }

    @BeforeEach
    void setUp() throws Exception
    {
        this.oldcore.notifyDocumentCreatedEvent(true);

        when(this.mockQueryManager.createQuery(anyString(), anyString())).thenReturn(this.mockQuery);
        when(this.mockQuery.execute()).thenReturn(Collections.emptyList());

        when(this.componentManagerManager.getComponentManager("wiki:xwiki", true)).thenReturn(this.oldcore.getMocker());
        this.oldcore.getMocker().getInstance(TranslationBundleFactory.class, DocumentTranslationBundleFactory.ID);

        XWikiDocument translationRootDocument = this.oldcore.getSpyXWiki().getDocument(TRANSLATION_ROOT_REFERENCE,
            this.oldcore.getXWikiContext());

        BaseObject translationObject = translationRootDocument.newXObject(
            new DocumentReference("xwiki", "XWiki", "TranslationDocumentClass"),
            this.oldcore.getXWikiContext());
        translationObject.setStringValue(TranslationDocumentModel.TRANSLATIONCLASS_PROP_SCOPE,
            TranslationDocumentModel.Scope.WIKI.toString());

        translationRootDocument.setSyntax(Syntax.PLAIN_1_0);
        translationRootDocument.setContent("xwiki.translation=root");
        translationRootDocument.setAuthorReference(ADMIN_USER_REFERENCE);
        this.oldcore.getSpyXWiki().saveDocument(translationRootDocument, this.oldcore.getXWikiContext());

        this.translationFrDocument = translationRootDocument.getTranslatedDocument(Locale.FRENCH,
            this.oldcore.getXWikiContext());
        if (this.translationFrDocument == translationRootDocument) {
            this.translationFrDocument =
                new XWikiDocument(this.translationFrDocument.getDocumentReference(), Locale.FRENCH);
            this.translationFrDocument.setDefaultLocale(this.translationFrDocument.getDefaultLocale());
        }
        this.translationFrDocument.setSyntax(Syntax.PLAIN_1_0);
        this.translationFrDocument.setContent("xwiki.translation=fr");
        this.oldcore.getSpyXWiki().saveDocument(this.translationFrDocument, this.oldcore.getXWikiContext());

        doThrow(new AccessDeniedException(Right.SCRIPT, null, translationRootDocument.getDocumentReference()))
            .when(this.oldcore.getMockAuthorizationManager()).checkAccess(Right.ADMIN, null,
                TRANSLATION_ROOT_REFERENCE.getWikiReference());
    }

    @Test
    void checkTranslationWithExpectedRights() throws Exception
    {
        this.translationFrDocument.setAuthorReference(ADMIN_USER_REFERENCE);
        this.translationFrDocument.setContentAuthorReference(ADMIN_USER_REFERENCE);
        this.oldcore.getSpyXWiki().saveDocument(this.translationFrDocument, this.oldcore.getXWikiContext());
        Translation frTranslation = this.localization.getTranslation("xwiki.translation", Locale.FRENCH);
        assertEquals("fr", frTranslation.getRawSource());
        // Authorizations are checked twice because the mocked behavior is not actual locale bundle registration.
        verify(this.oldcore.getMockAuthorizationManager(), times(4)).checkAccess(Right.ADMIN, ADMIN_USER_REFERENCE,
            TRANSLATION_ROOT_REFERENCE.getWikiReference());
    }

    @Test
    void checkTranslationWithoutExpectedRights() throws Exception
    {
        Translation frTranslation = this.localization.getTranslation("xwiki.translation", Locale.FRENCH);
        assertEquals(
            "Failed to load and register the translation for locale [fr] from document [xwiki:space.Translations]. "
                + "Falling back to default locale.",
            this.logCapture.getMessage(0));
        assertEquals("root", frTranslation.getRawSource());
        verify(this.oldcore.getMockAuthorizationManager()).checkAccess(Right.ADMIN, null,
            TRANSLATION_ROOT_REFERENCE.getWikiReference());
    }
}