Skip to content
Permalink
Browse files Browse the repository at this point in the history
XWIKI-19749: Require script right for translations with user scope
* Add a new configuration option.
* Migrate tests to JUnit 5 and add a new test.
  • Loading branch information
michitux committed Dec 19, 2022
1 parent 5f8d9a2 commit d06ff8a
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 45 deletions.
@@ -0,0 +1,51 @@
/*
* 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 javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.configuration.ConfigurationSource;

/**
* Default implementation of the {@link WikiTranslationConfiguration}.
*
* @since 14.10.2
* @since 15.0RC1
* @version $Id$
*/
@Singleton
@Component
public class DefaultWikiTranslationConfiguration implements WikiTranslationConfiguration
{
private static final String PREFIX = "localization.wiki.";

@Inject
@Named("xwikiproperties")
private ConfigurationSource configuration;

@Override
public boolean isRestrictUserTranslations()
{
return this.configuration.getProperty(PREFIX + "restrictUserTranslations", true);
}
}
Expand Up @@ -143,6 +143,9 @@ public class DocumentTranslationBundleFactory implements TranslationBundleFactor
@Inject
private AuthorizationManager authorizationManager;

@Inject
private WikiTranslationConfiguration configuration;

/**
* Used to cache on demand document bundles (those that are not registered as components).
*/
Expand Down Expand Up @@ -447,6 +450,12 @@ private void checkRegistrationAuthorization(XWikiDocument document, Scope scope)
this.authorizationManager.checkAccess(Right.ADMIN, document.getAuthorReference(), document
.getDocumentReference().getWikiReference());
break;
case USER:
if (this.configuration.isRestrictUserTranslations()) {
this.authorizationManager.checkAccess(Right.SCRIPT, document.getAuthorReference(),
document.getDocumentReference());
}
break;
default:
break;
}
Expand Down
@@ -0,0 +1,38 @@
/*
* 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 org.xwiki.component.annotation.Role;

/**
* Configuration of the wiki translations.
*
* @since 14.10.2
* @since 15.0RC1
* @version $Id$
*/
@Role
public interface WikiTranslationConfiguration
{
/**
* @return if translations with user scope shall be restricted to users with script right
*/
boolean isRestrictUserTranslations();
}
@@ -1,3 +1,4 @@
org.xwiki.localization.wiki.internal.DefaultWikiTranslationConfiguration
org.xwiki.localization.wiki.internal.DocumentTranslationBundleFactory
org.xwiki.localization.wiki.internal.DocumentTranslationBundleInitializer
org.xwiki.localization.wiki.internal.TranslationDocumentClassInitializer
Expand Up @@ -22,95 +22,144 @@
import java.util.Collections;
import java.util.Locale;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
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.mockito.Mockito;
import org.xwiki.cache.infinispan.internal.InfinispanCacheFactory;
import org.xwiki.cache.internal.DefaultCacheManager;
import org.xwiki.cache.internal.DefaultCacheManagerConfiguration;
import org.xwiki.component.internal.multi.ComponentManagerManager;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.localization.LocalizationManager;
import org.xwiki.localization.Translation;
import org.xwiki.localization.TranslationBundleDoesNotExistsException;
import org.xwiki.localization.TranslationBundleFactory;
import org.xwiki.localization.TranslationBundleFactoryDoesNotExistsException;
import org.xwiki.localization.internal.DefaultLocalizationManager;
import org.xwiki.localization.internal.DefaultTranslationBundleContext;
import org.xwiki.localization.messagetool.internal.MessageToolTranslationMessageParser;
import org.xwiki.localization.wiki.internal.TranslationDocumentModel.Scope;
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.test.annotation.AfterComponent;
import org.xwiki.test.annotation.AllComponents;
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.Right;
import org.xwiki.test.LogLevel;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.junit5.LogCaptureExtension;
import org.xwiki.test.junit5.mockito.MockComponent;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;

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.test.MockitoOldcoreRule;

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.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@AllComponents
public class DocumentTranslationBundleFactoryTest
/**
* Unit tests for {@link DocumentTranslationBundleFactory}.
*
* @version $Id$
*/
@OldcoreTest
@ComponentList({
DocumentTranslationBundleFactory.class,
DefaultLocalizationManager.class,
DefaultTranslationBundleContext.class,
DefaultModelContext.class,
PlainTextBlockRenderer.class,
PlainTextRendererFactory.class,
DefaultObservationManager.class,
DefaultCacheManager.class,
DefaultCacheManagerConfiguration.class,
MessageToolTranslationMessageParser.class,
PlainTextBlockParser.class,
InfinispanCacheFactory.class })
@ReferenceComponentList
class DocumentTranslationBundleFactoryTest
{
@Rule
public MockitoOldcoreRule oldcore = new MockitoOldcoreRule();
@InjectMockitoOldcore
private MockitoOldcore oldcore;

@MockComponent
private WikiTranslationConfiguration translationConfiguration;

@MockComponent
private QueryManager mockQueryManager;

@Mock
private Query mockQuery;

@MockComponent
private WikiDescriptorManager mockWikiDescriptorManager;

@MockComponent
private ComponentManagerManager componentManagerManager;

@Inject
private LocalizationManager localization;

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

@BeforeEach
public void before() throws Exception
{
this.oldcore.notifyDocumentCreatedEvent(true);
this.oldcore.notifyDocumentUpdatedEvent(true);
this.oldcore.notifyDocumentDeletedEvent(true);
}

@Before
public void before() throws Exception
{
this.oldcore.getXWikiContext().setMainXWiki("xwiki");
this.oldcore.getXWikiContext().setWikiId("xwiki");

doReturn("plain/1.0").when(this.oldcore.getSpyXWiki()).getCurrentContentSyntaxId(Mockito.any(String.class),
Mockito.any(XWikiContext.class));

this.mockQuery = mock(Query.class);

when(this.mockQueryManager.createQuery(Mockito.any(String.class), Mockito.any(String.class)))
.thenReturn(this.mockQuery);
when(this.mockQuery.execute()).thenReturn(Collections.EMPTY_LIST);
when(this.mockQuery.execute()).thenReturn(Collections.emptyList());

when(this.mockWikiDescriptorManager.getMainWikiId()).thenReturn(this.oldcore.getXWikiContext().getMainXWiki());
when(this.mockWikiDescriptorManager.getCurrentWikiId()).thenReturn(this.oldcore.getXWikiContext().getWikiId());

// Return the "context" component manager for the current wiki and the current user but not for another wiki.
when(this.componentManagerManager.getComponentManager("wiki:xwiki", true)).thenReturn(this.oldcore.getMocker());
when(this.componentManagerManager.getComponentManager("wiki:otherwiki", true))
.thenReturn(mock(ComponentManager.class));
when(this.componentManagerManager.getComponentManager("user:null", true)).thenReturn(this.oldcore.getMocker());

// Initialize document bundle factory
this.oldcore.getMocker().getInstance(TranslationBundleFactory.class, DocumentTranslationBundleFactory.ID);

this.localization = this.oldcore.getMocker().getInstance(LocalizationManager.class);

this.oldcore.getMocker().registerMockComponent(ConfigurationSource.class);

// We want to be notified about new components registrations
this.oldcore.notifyComponentDescriptorEvent();
}

@AfterComponent
public void registerComponents() throws Exception
{
this.mockQueryManager = this.oldcore.getMocker().registerMockComponent(QueryManager.class);
this.mockWikiDescriptorManager = this.oldcore.getMocker().registerMockComponent(WikiDescriptorManager.class);
}

private void addTranslation(String key, String message, DocumentReference reference, Locale locale, Scope scope)
throws XWikiException
{
Expand Down Expand Up @@ -142,14 +191,12 @@ private void addTranslation(String key, String message, DocumentReference refere

document.setSyntax(Syntax.PLAIN_1_0);

StringBuilder builder = new StringBuilder(document.getContent());
String content = document.getContent() + '\n'
+ key
+ '='
+ message;

builder.append('\n');
builder.append(key);
builder.append('=');
builder.append(message);

document.setContent(builder.toString());
document.setContent(content);

this.oldcore.getSpyXWiki().saveDocument(document, "", this.oldcore.getXWikiContext());
}
Expand All @@ -159,10 +206,10 @@ private void assertTranslation(String key, String message, Locale locale)
Translation translation = this.localization.getTranslation(key, locale);

if (message != null) {
Assert.assertNotNull("No translation could be found for key [" + key + "]", translation);
Assert.assertEquals(message, translation.getRawSource());
assertNotNull(translation, "No translation could be found for key [" + key + "]");
assertEquals(message, translation.getRawSource());
} else {
Assert.assertNull(translation);
assertNull(translation);
}
}

Expand All @@ -174,7 +221,7 @@ private void resetContext() throws ComponentLookupException
// tests

@Test
public void getTranslationScopeWiki() throws XWikiException, ComponentLookupException
void getTranslationScopeWiki() throws XWikiException, ComponentLookupException
{
assertTranslation("wiki.translation", null, Locale.ROOT);

Expand All @@ -191,7 +238,7 @@ public void getTranslationScopeWiki() throws XWikiException, ComponentLookupExce
}

@Test
public void getTranslationScopeWikiFromOtherWiki() throws XWikiException, ComponentLookupException
void getTranslationScopeWikiFromOtherWiki() throws XWikiException, ComponentLookupException
{
assertTranslation("wiki.translation", null, Locale.ROOT);

Expand All @@ -207,7 +254,7 @@ public void getTranslationScopeWikiFromOtherWiki() throws XWikiException, Compon
}

@Test
public void getTranslationScopeONDemand() throws XWikiException, TranslationBundleDoesNotExistsException,
void getTranslationScopeONDemand() throws XWikiException, TranslationBundleDoesNotExistsException,
TranslationBundleFactoryDoesNotExistsException, ComponentLookupException
{
assertTranslation("wiki.translation", null, Locale.ROOT);
Expand All @@ -224,4 +271,27 @@ public void getTranslationScopeONDemand() throws XWikiException, TranslationBund

assertTranslation("wiki.translation", "Wiki translation", Locale.ROOT);
}

@Test
void restrictUserTranslations() throws XWikiException, AccessDeniedException
{
DocumentReference translationDocument =
new DocumentReference(this.oldcore.getXWikiContext().getWikiId(), "space", "translation");

when(this.translationConfiguration.isRestrictUserTranslations()).thenReturn(true);

addTranslation("user.translation", "User translation", translationDocument, Locale.ROOT, Scope.USER);

assertTranslation("user.translation", "User translation", Locale.ROOT);

doThrow(new AccessDeniedException(Right.SCRIPT, null, translationDocument))
.when(this.oldcore.getMockAuthorizationManager()).checkAccess(Right.SCRIPT, null, translationDocument);

addTranslation("user.translation2", "User translation", translationDocument, Locale.ROOT, Scope.USER);

assertEquals("Failed to register translation bundle from document [xwiki:space.translation]",
this.logCapture.getMessage(0));

assertTranslation("user.translation", null, Locale.ROOT);
}
}
Expand Up @@ -1391,4 +1391,14 @@ edit.defaultEditor.org.xwiki.rendering.block.XDOM#wysiwyg=$xwikiPropertiesDefaul
#-# The default value is:
# skinx.jsStrictModeEnabled = false

#-------------------------------------------------------------------------------------
# Localization
#-------------------------------------------------------------------------------------

#-# [Since 14.10.2, 15.0RC1]
#-# Indicate whether translations with user scope that are only applied for the user who created them shall be
#-# restricted to users with script right. The default value is true. Disable this option only when you absolutely
#-# trust all users on the wiki.
# localization.wiki.restrictUserTranslations = true

$!xwikiPropertiesAdditionalProperties

0 comments on commit d06ff8a

Please sign in to comment.