CVE-2023-29206 (Java, published 2023-04-15T16:15Z)
Description: value: XWiki Commons are technical libraries common to several other top level XWiki projects. There was no check in the author of a JavaScript xobject or StyleSheet xobject added in a XWiki document, so until now it was possible for a user having only Edit Right to create such object and to craft a script allowing to perform some operations when executing by a user with appropriate rights. This has been patched in XWiki 14.9-rc-1 by only executing the script if the author of it has Script rights.
Severity: nan (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx/pom.xml
xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx/src/main/java/com/xpn/xwiki/plugin/skinx/AbstractDocumentSkinExtensionPlugin.java
xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx/src/main/java/com/xpn/xwiki/plugin/skinx/AbstractSkinExtensionPlugin.java
xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx/src/test/java/com/xpn/xwiki/plugin/skinx/CssSkinExtensionPluginTest.java
xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx/src/test/java/com/xpn/xwiki/plugin/skinx/JsSkinExtensionPluginTest.java
Commit message: XWIKI-9119: Refactoring of SkinExtensionPlugin

  * Provide some tests


Vulnerable code:
    <xwiki.jacoco.instructionRatio>0.18</xwiki.jacoco.instructionRatio>
        EntityReferenceSerializer<String> serializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
                        if (Utils.getComponent(AuthorizationManager.class).hasAccess(Right.PROGRAM,
        if (doc != null) {
            List<BaseObject> objects = doc.getObjects(getExtensionClassName());
            if (objects != null) {
                for (BaseObject obj : objects) {
                    if (obj == null) {
                        continue;
                    }
                    if (obj.getStringValue(USE_FIELDNAME).equals("currentPage")) {
                        return true;
                    }
    public void use(String resource, XWikiContext context)
        super.use(canonicalResource, context);
    @Override
    public void use(String resource, Map<String, Object> parameters, XWikiContext context)
        String canonicalResource = getCanonicalDocumentName(resource);
        super.use(canonicalResource, parameters, context);
                if (Utils.getComponent(AuthorizationManager.class).hasAccess(Right.PROGRAM,
        @SuppressWarnings("unchecked")
        EntityReferenceResolver<String> resolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "current");
        @SuppressWarnings("unchecked")
        EntityReferenceSerializer<String> serializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
        useResource(resource, context);

        // In case a previous call added some parameters, remove them, since the last call for a resource always
        // discards previous ones.
        getParametersMap(context).remove(resource);

        // Register the use of the resource in case the current execution is an asynchronous renderer
        getSkinExtensionAsync().use(getName(), resource, null);
        // Associate parameters to the resource
        getParametersMap(context).put(resource, parameters);
public class CssSkinExtensionPluginTest
    private DocumentReferenceResolver<String> documentReferenceResolver;
        when(this.documentReferenceResolver.resolve(CssSkinExtensionPlugin.SSX_CLASS_NAME)).thenReturn(

Fixed code:
    <xwiki.jacoco.instructionRatio>0.25</xwiki.jacoco.instructionRatio>
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.xwiki.model.reference.DocumentReferenceResolver;
    private AuthorizationManager authorizationManager;
    private DocumentReferenceResolver<String> stringDocumentReferenceResolver;
    private EntityReferenceResolver<String> currentEntityReferenceResolver;

        EntityReferenceSerializer<String> serializer = getDefaultEntityReferenceSerializer();
                        if (getAuthorizationManager().hasAccess(Right.PROGRAM,
        boolean result = false;
        if (doc != null && this.hasCurrentPageExtensionObjects(doc)) {
            if (getAuthorizationManager().hasAccess(Right.SCRIPT, doc.getAuthorReference(),
                doc.getDocumentReference())) {
                result = true;
            } else {
                displayScriptRightLog(doc.getDocumentReference());
            }
        }
        return result;
    }

    private void displayScriptRightLog(Object documentReference)
    {
        LOGGER.warn("Extensions present in [{}] ignored because of lack of script right from the author.",
            documentReference);
    }

    private boolean hasCurrentPageExtensionObjects(XWikiDocument doc)
    {
        List<BaseObject> objects = doc.getObjects(getExtensionClassName());
        if (objects != null) {
            for (BaseObject obj : objects) {
                if (obj == null) {
                    continue;
                }
                if (StringUtils.equals(obj.getStringValue(USE_FIELDNAME), "currentPage")) {
                    return true;
    public void use(String resource, Map<String, Object> parameters, XWikiContext context)
        if (this.canResourceBeUsed(canonicalResource, context)) {
            super.use(canonicalResource, parameters, context);
        } else {
            displayScriptRightLog(canonicalResource);
        }
    private DocumentReferenceResolver<String> getDocumentReferenceResolver()
        if (this.stringDocumentReferenceResolver == null) {
            this.stringDocumentReferenceResolver = Utils.getComponent(DocumentReferenceResolver.TYPE_STRING);
        }
        return this.stringDocumentReferenceResolver;
    }
    private AuthorizationManager getAuthorizationManager()
    {
        if (this.authorizationManager == null) {
            this.authorizationManager = Utils.getComponent(AuthorizationManager.class);
        }
        return this.authorizationManager;
    }

    private boolean canResourceBeUsed(String resource, XWikiContext context)
    {
        DocumentReferenceResolver<String> documentReferenceResolver = getDocumentReferenceResolver();
        DocumentReference documentReference = documentReferenceResolver.resolve(resource);

        try {
            XWikiDocument document = context.getWiki().getDocument(documentReference, context);
            DocumentReference authorReference = document.getAuthorReference();
            return getAuthorizationManager().hasAccess(Right.SCRIPT, authorReference, documentReference);
        } catch (XWikiException e) {
            LOGGER.error("Error while loading [{}] for checking script right: [{}]", documentReference,
                ExceptionUtils.getRootCauseMessage(e));
            LOGGER.debug("Original error stack trace: ", e);
            return false;
        }
                if (getAuthorizationManager().hasAccess(Right.PROGRAM,
    private EntityReferenceResolver<String> getCurrentEntityReferenceResolver()
    {
        if (this.currentEntityReferenceResolver == null) {
            this.currentEntityReferenceResolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "current");
        }
        return this.currentEntityReferenceResolver;
    }

        EntityReferenceResolver<String> resolver = getCurrentEntityReferenceResolver();
        EntityReferenceSerializer<String> serializer = getDefaultEntityReferenceSerializer();
        use(resource, null, context);
        // In case a previous call added some parameters, remove them, since the last call for a resource always
        // discards previous ones.
        if (parameters == null) {
            getParametersMap(context).remove(resource);
        } else {
            // Associate parameters to the resource
            getParametersMap(context).put(resource, parameters);
        }
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.skinx.internal.async.SkinExtensionAsync;
import org.xwiki.test.LogLevel;
import org.xwiki.test.junit5.LogCaptureExtension;
import com.xpn.xwiki.objects.BaseObject;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
class CssSkinExtensionPluginTest
    private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver;
    @MockComponent
    private DocumentReferenceResolver<String> documentReferenceResolver;

    @MockComponent
    @Named("current")
    private EntityReferenceResolver<String> currentEntityReferenceResolver;

    @MockComponent
    private SkinExtensionAsync skinExtensionAsync;

    @RegisterExtension
    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);

        when(this.currentMixedDocumentReferenceResolver.resolve(CssSkinExtensionPlugin.SSX_CLASS_NAME)).thenReturn(

    @Test
    void use() throws XWikiException
    {
        String resource = "MySpace.MySSXPage";
        Map<String, Object> parameters = Collections.singletonMap("fooParam", "barValue");

        DocumentReference documentReference = new DocumentReference("xwiki", "MySpace", "MySSXPage");
        when(this.currentEntityReferenceResolver.resolve(resource, EntityType.DOCUMENT)).thenReturn(documentReference);
        when(this.entityReferenceSerializer.serialize(documentReference)).thenReturn(resource);

        when(this.documentReferenceResolver.resolve(resource)).thenReturn(documentReference);
        XWikiDocument document = mock(XWikiDocument.class);
        when(this.mockitoOldcore.getSpyXWiki().getDocument(documentReference, context)).thenReturn(document);

        DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
        when(document.getAuthorReference()).thenReturn(userReference);
        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(true);

        this.skinExtensionPlugin.use(resource, parameters, context);
        String className = CssSkinExtensionPlugin.class.getCanonicalName();
        Set<String> resources = (Set<String>) context.get(className);

        assertEquals(Collections.singleton(resource), resources);

        Map<String, Map<String, Object>> parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        Map<String, Map<String, Object>> expectedParameters = new HashMap<>();
        expectedParameters.put(resource, parameters);

        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference, documentReference);
        verify(this.skinExtensionAsync).use("ssx", resource, parameters);

        String resource2 = "MySpace.MyOtherSSXPage";
        Map<String, Object> parameters2 = null;
        DocumentReference documentReference2 = new DocumentReference("xwiki", "MySpace", "MyOtherSSXPage");

        when(this.currentEntityReferenceResolver.resolve(resource2, EntityType.DOCUMENT))
            .thenReturn(documentReference2);
        when(this.entityReferenceSerializer.serialize(documentReference2)).thenReturn(resource2);

        when(this.documentReferenceResolver.resolve(resource2)).thenReturn(documentReference2);
        XWikiDocument document2 = mock(XWikiDocument.class);
        when(this.mockitoOldcore.getSpyXWiki().getDocument(documentReference2, context)).thenReturn(document2);

        DocumentReference userReference2 = new DocumentReference("xwiki", "XWiki", "Bar");
        when(document2.getAuthorReference()).thenReturn(userReference2);
        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference2, documentReference2)).thenReturn(false);

        this.skinExtensionPlugin.use(resource2, parameters2, context);
        resources = (Set<String>) context.get(className);
        assertEquals(Collections.singleton(resource), resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference2, documentReference2);
        verify(this.skinExtensionAsync, never()).use("ssx", resource2, parameters2);

        assertEquals(1, this.logCapture.size());
        assertEquals("Extensions present in [MySpace.MyOtherSSXPage] ignored because of lack of script right "
            + "from the author.", this.logCapture.getMessage(0));

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference2, documentReference2)).thenReturn(true);
        this.skinExtensionPlugin.use(resource2, parameters2, context);

        Set<String> expectedSet = new HashSet<>();
        expectedSet.add(resource);
        expectedSet.add(resource2);

        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);

        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager, times(2)).hasAccess(Right.SCRIPT, userReference2, documentReference2);
        verify(this.skinExtensionAsync).use("ssx", resource2, null);

        parameters2 = Collections.singletonMap("buzValue", 42);
        this.skinExtensionPlugin.use(resource2, parameters2, context);

        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        expectedParameters.put(resource2, parameters2);
        assertEquals(expectedParameters, parametersMap);
        verify(this.skinExtensionAsync).use("ssx", resource2, parameters2);

        this.skinExtensionPlugin.use(resource, null, context);
        expectedParameters.remove(resource);
        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        expectedParameters.put(resource2, parameters2);
        assertEquals(expectedParameters, parametersMap);
        verify(this.skinExtensionAsync).use("ssx", resource, null);
    }

    @Test
    void hasPageExtensions()
    {
        this.context.setDoc(null);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));

        XWikiDocument currentDoc = mock(XWikiDocument.class, "currentDoc");
        this.context.setDoc(currentDoc);

        String className = CssSkinExtensionPlugin.SSX_CLASS_NAME;
        when(currentDoc.getObjects(className)).thenReturn(null);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verify(currentDoc).getObjects(className);

        BaseObject baseObject = mock(BaseObject.class, "specificObj");
        when(baseObject.getStringValue("use")).thenReturn("wiki");
        Vector<BaseObject> objectList = new Vector<>();
        objectList.add(mock(BaseObject.class));
        objectList.add(null);
        objectList.add(mock(BaseObject.class));
        objectList.add(baseObject);
        objectList.add(null);
        objectList.add(mock(BaseObject.class));

        when(currentDoc.getObjects(className)).thenReturn(objectList);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verifyNoInteractions(this.authorizationManager);

        when(baseObject.getStringValue("use")).thenReturn("currentPage");
        DocumentReference documentReference = new DocumentReference("xwiki", "MySpace", "SomePage");
        DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
        when(currentDoc.getDocumentReference()).thenReturn(documentReference);
        when(currentDoc.getAuthorReference()).thenReturn(userReference);

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(false);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference, documentReference);

        assertEquals(1, this.logCapture.size());
        assertEquals("Extensions present in [xwiki:MySpace.SomePage] ignored because of lack of script right "
            + "from the author.", this.logCapture.getMessage(0));

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(true);
        assertTrue(this.skinExtensionPlugin.hasPageExtensions(context));
    }
/*
 * 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 com.xpn.xwiki.plugin.skinx;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.inject.Named;
import javax.inject.Provider;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.observation.ObservationManager;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.skinx.internal.async.SkinExtensionAsync;
import org.xwiki.test.LogLevel;
import org.xwiki.test.junit5.LogCaptureExtension;
import org.xwiki.test.junit5.mockito.MockComponent;

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.test.MockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
import com.xpn.xwiki.web.XWikiRequest;
import com.xpn.xwiki.web.XWikiURLFactory;

import static org.junit.jupiter.api.Assertions.*;
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;

/**
 * Tests for {@link JsSkinExtensionPlugin}.
 *
 * @version $Id$
 * @since 14.9RC1
 */
@OldcoreTest
class JsSkinExtensionPluginTest
{
    @InjectMockitoOldcore
    private MockitoOldcore mockitoOldcore;

    @MockComponent
    private Provider<DocumentReference> documentReferenceProvider;

    @MockComponent
    private ObservationManager observationManager;

    @MockComponent
    private EntityReferenceSerializer<String> entityReferenceSerializer;

    @MockComponent
    @Named("currentmixed")
    private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver;

    @MockComponent
    private DocumentReferenceResolver<String> documentReferenceResolver;

    @MockComponent
    @Named("current")
    private EntityReferenceResolver<String> currentEntityReferenceResolver;

    @MockComponent
    private AuthorizationManager authorizationManager;

    @MockComponent
    private SkinExtensionAsync skinExtensionAsync;

    @RegisterExtension
    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);

    private XWikiContext context;
    private BaseClass pluginClass;
    private JsSkinExtensionPlugin skinExtensionPlugin;
    private XWikiURLFactory urlFactory;

    @BeforeEach
    void setup() throws XWikiException
    {
        XWikiDocument classDoc = mock(XWikiDocument.class);
        this.pluginClass = mock(BaseClass.class);

        this.context = mockitoOldcore.getXWikiContext();
        this.urlFactory = mock(XWikiURLFactory.class);
        context.setURLFactory(urlFactory);
        XWikiRequest xWikiRequest = mock(XWikiRequest.class);
        context.setRequest(xWikiRequest);
        XWiki wiki = mockitoOldcore.getSpyXWiki();
        when(this.currentMixedDocumentReferenceResolver.resolve(JsSkinExtensionPlugin.JSX_CLASS_NAME)).thenReturn(
            new DocumentReference(JsSkinExtensionPlugin.JSX_CLASS_REFERENCE, new WikiReference("xwiki")));
        when(wiki.getDocument(JsSkinExtensionPlugin.JSX_CLASS_NAME, context)).thenReturn(classDoc);
        when(classDoc.getXClass()).thenReturn(this.pluginClass);
        this.skinExtensionPlugin = new JsSkinExtensionPlugin("plugin1", "unused", context);
        skinExtensionPlugin.init(context);
    }

    @Test
    void use() throws XWikiException
    {
        String resource = "MySpace.MyJSXPage";
        Map<String, Object> parameters = Collections.singletonMap("fooParam", "barValue");

        DocumentReference documentReference = new DocumentReference("xwiki", "MySpace", "MyJSXPage");
        when(this.currentEntityReferenceResolver.resolve(resource, EntityType.DOCUMENT)).thenReturn(documentReference);
        when(this.entityReferenceSerializer.serialize(documentReference)).thenReturn(resource);

        when(this.documentReferenceResolver.resolve(resource)).thenReturn(documentReference);
        XWikiDocument document = mock(XWikiDocument.class);
        when(this.mockitoOldcore.getSpyXWiki().getDocument(documentReference, context)).thenReturn(document);

        DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
        when(document.getAuthorReference()).thenReturn(userReference);
        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(true);

        this.skinExtensionPlugin.use(resource, parameters, context);
        String className = JsSkinExtensionPlugin.class.getCanonicalName();
        Set<String> resources = (Set<String>) context.get(className);

        assertEquals(Collections.singleton(resource), resources);

        Map<String, Map<String, Object>> parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        Map<String, Map<String, Object>> expectedParameters = new HashMap<>();
        expectedParameters.put(resource, parameters);

        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference, documentReference);
        verify(this.skinExtensionAsync).use("jsx", resource, parameters);

        String resource2 = "MySpace.MyOtherJSXPage";
        Map<String, Object> parameters2 = null;
        DocumentReference documentReference2 = new DocumentReference("xwiki", "MySpace", "MyOtherJSXPage");

        when(this.currentEntityReferenceResolver.resolve(resource2, EntityType.DOCUMENT))
            .thenReturn(documentReference2);
        when(this.entityReferenceSerializer.serialize(documentReference2)).thenReturn(resource2);

        when(this.documentReferenceResolver.resolve(resource2)).thenReturn(documentReference2);
        XWikiDocument document2 = mock(XWikiDocument.class);
        when(this.mockitoOldcore.getSpyXWiki().getDocument(documentReference2, context)).thenReturn(document2);

        DocumentReference userReference2 = new DocumentReference("xwiki", "XWiki", "Bar");
        when(document2.getAuthorReference()).thenReturn(userReference2);
        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference2, documentReference2)).thenReturn(false);

        this.skinExtensionPlugin.use(resource2, parameters2, context);
        resources = (Set<String>) context.get(className);
        assertEquals(Collections.singleton(resource), resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference2, documentReference2);
        verify(this.skinExtensionAsync, never()).use("jsx", resource2, parameters2);

        assertEquals(1, this.logCapture.size());
        assertEquals("Extensions present in [MySpace.MyOtherJSXPage] ignored because of lack of script right "
            + "from the author.", this.logCapture.getMessage(0));

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference2, documentReference2)).thenReturn(true);
        this.skinExtensionPlugin.use(resource2, parameters2, context);

        Set<String> expectedSet = new HashSet<>();
        expectedSet.add(resource);
        expectedSet.add(resource2);

        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);

        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        assertEquals(expectedParameters, parametersMap);
        verify(this.authorizationManager, times(2)).hasAccess(Right.SCRIPT, userReference2, documentReference2);
        verify(this.skinExtensionAsync).use("jsx", resource2, null);

        parameters2 = Collections.singletonMap("buzValue", 42);
        this.skinExtensionPlugin.use(resource2, parameters2, context);

        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        expectedParameters.put(resource2, parameters2);
        assertEquals(expectedParameters, parametersMap);
        verify(this.skinExtensionAsync).use("jsx", resource2, parameters2);

        this.skinExtensionPlugin.use(resource, null, context);
        expectedParameters.remove(resource);
        resources = (Set<String>) context.get(className);
        assertEquals(expectedSet, resources);
        parametersMap =
            (Map<String, Map<String, Object>>) context.get(className + "_parameters");
        expectedParameters.put(resource2, parameters2);
        assertEquals(expectedParameters, parametersMap);
        verify(this.skinExtensionAsync).use("jsx", resource, null);
    }

    @Test
    void hasPageExtensions()
    {
        this.context.setDoc(null);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));

        XWikiDocument currentDoc = mock(XWikiDocument.class, "currentDoc");
        this.context.setDoc(currentDoc);

        String className = JsSkinExtensionPlugin.JSX_CLASS_NAME;
        when(currentDoc.getObjects(className)).thenReturn(null);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verify(currentDoc).getObjects(className);

        BaseObject baseObject = mock(BaseObject.class, "specificObj");
        when(baseObject.getStringValue("use")).thenReturn("wiki");
        Vector<BaseObject> objectList = new Vector<>();
        objectList.add(mock(BaseObject.class));
        objectList.add(null);
        objectList.add(mock(BaseObject.class));
        objectList.add(baseObject);
        objectList.add(null);
        objectList.add(mock(BaseObject.class));

        when(currentDoc.getObjects(className)).thenReturn(objectList);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verifyNoInteractions(this.authorizationManager);

        when(baseObject.getStringValue("use")).thenReturn("currentPage");
        DocumentReference documentReference = new DocumentReference("xwiki", "MySpace", "SomePage");
        DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "Foo");
        when(currentDoc.getDocumentReference()).thenReturn(documentReference);
        when(currentDoc.getAuthorReference()).thenReturn(userReference);

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(false);
        assertFalse(this.skinExtensionPlugin.hasPageExtensions(context));
        verify(this.authorizationManager).hasAccess(Right.SCRIPT, userReference, documentReference);

        assertEquals(1, this.logCapture.size());
        assertEquals("Extensions present in [xwiki:MySpace.SomePage] ignored because of lack of script right "
            + "from the author.", this.logCapture.getMessage(0));

        when(this.authorizationManager.hasAccess(Right.SCRIPT, userReference, documentReference)).thenReturn(true);
        assertTrue(this.skinExtensionPlugin.hasPageExtensions(context));
    }
}