Skip to content

Commit

Permalink
XWIKI-21335: Execute UI Extension parameters with the rights of their…
Browse files Browse the repository at this point in the history
… author

* Check script right for the UI extension's author
* Use AuthorExecutor to set the context author

(cherry picked from commit 171e7c7)
  • Loading branch information
michitux committed Oct 19, 2023
1 parent af32478 commit 56748e1
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 52 deletions.
Expand Up @@ -143,12 +143,9 @@ public List<WikiComponent> buildComponents(BaseObject baseObject) throws WikiCom
String.format("Failed to initialize Panel UI extension [%s]", baseObject.getReference()), e);
}

String rawParameters = baseObject.getStringValue(PARAMETERS_PROPERTY);

// It would be nice to have PER_LOOKUP components for UIX parameters but without constructor injection it's
// safer to use a POJO and pass the Component Manager to it.
WikiUIExtensionParameters parameters =
new WikiUIExtensionParameters(id, rawParameters, this.wikiComponentManager);
WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(baseObject, this.wikiComponentManager);
extension.setParameters(parameters);
extension.setScope(scope);

Expand Down
Expand Up @@ -22,9 +22,10 @@
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand All @@ -38,11 +39,20 @@
import org.xwiki.logging.LoggerConfiguration;
import org.xwiki.model.EntityType;
import org.xwiki.model.ModelContext;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.velocity.VelocityEngine;
import org.xwiki.velocity.VelocityManager;
import org.xwiki.velocity.XWikiVelocityContext;
import org.xwiki.velocity.XWikiVelocityException;

import com.xpn.xwiki.objects.BaseObject;

import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.ID_PROPERTY;
import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.PARAMETERS_PROPERTY;

/**
* Wiki UI Extension parameter manager.
*
Expand Down Expand Up @@ -97,25 +107,37 @@ public class WikiUIExtensionParameters
*/
private Execution execution;

private AuthorExecutor authorExecutor;

private final DocumentReference documentReference;

private final DocumentReference authorReference;

private final AuthorizationManager authorizationManager;

/**
* Default constructor.
*
* @param id the unique identifier of this set of parameters, mostly used to isolate parameters value execution
* @param rawParameters raw parameters, their values can contain velocity directives
* @param baseObject the object from which the parameters shall be loaded
* @param cm the XWiki component manager
* @throws WikiComponentException if some required components can't be found in the Component Manager
*/
public WikiUIExtensionParameters(String id, String rawParameters, ComponentManager cm)
public WikiUIExtensionParameters(BaseObject baseObject, ComponentManager cm)
throws WikiComponentException
{
this.id = id;
this.parameters = parseParameters(rawParameters);
this.id = baseObject.getStringValue(ID_PROPERTY);
this.parameters = parseParameters(baseObject.getStringValue(PARAMETERS_PROPERTY));

this.documentReference = baseObject.getDocumentReference();
this.authorReference = baseObject.getOwnerDocument().getAuthorReference();

try {
this.execution = cm.getInstance(Execution.class);
this.velocityManager = cm.getInstance(VelocityManager.class);
this.modelContext = cm.getInstance(ModelContext.class);
this.loggerConfiguration = cm.getInstance(LoggerConfiguration.class);
this.authorExecutor = cm.getInstance(AuthorExecutor.class);
this.authorizationManager = cm.getInstance(AuthorizationManager.class);
} catch (ComponentLookupException e) {
throw new WikiComponentException(
"Failed to get an instance for a component role required by Wiki Components.", e);
Expand Down Expand Up @@ -148,7 +170,7 @@ private Properties parseParameters(String rawParameters)
*/
public Map<String, String> get()
{
boolean isCacheValid = false;
Map<String, String> result;

// Even though the parameters are dynamic, we cache a rendered version of them in order to improve performance.
// This cache has a short lifespan, it gets discarded for each new request, or if the database has been switched
Expand All @@ -158,43 +180,50 @@ public Map<String, String> get()
if (currentContextId == this.previousContextId
&& currentWiki.equals(previousWiki) && this.evaluatedParameters != null)
{
isCacheValid = true;
}

if (!isCacheValid) {
this.evaluatedParameters = new HashMap<>();

if (this.parameters.size() > 0) {
result = this.evaluatedParameters;
} else {
result = this.parameters.stringPropertyNames().stream()
.filter(StringUtils::isNotBlank)
.collect(Collectors.toMap(Function.identity(), this.parameters::getProperty));

if (!this.parameters.isEmpty()
&& this.authorizationManager.hasAccess(Right.SCRIPT, this.authorReference, this.documentReference))
{
try {
VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
VelocityContext velocityContext = this.velocityManager.getVelocityContext();
this.authorExecutor.call(() -> {
VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
VelocityContext velocityContext = this.velocityManager.getVelocityContext();

for (String propertyKey : this.parameters.stringPropertyNames()) {
if (!StringUtils.isBlank(propertyKey)) {
String propertyValue = this.parameters.getProperty(propertyKey);
result.replaceAll((propertyKey, propertyValue) -> {
StringWriter writer = new StringWriter();
try {
String namespace = this.id + ':' + propertyKey;
velocityEngine.evaluate(
new XWikiVelocityContext(velocityContext,
this.loggerConfiguration.isDeprecatedLogEnabled()),
writer, namespace, propertyValue);
this.evaluatedParameters.put(propertyKey, writer.toString());
return writer.toString();
} catch (XWikiVelocityException e) {
LOGGER.warn(String.format(
"Failed to evaluate UI extension data value, key [%s], value [%s]. Reason: [%s]",
propertyKey, propertyValue, e.getMessage()));
}
}
}
} catch (XWikiVelocityException ex) {

return propertyValue;
});

return null;
}, this.authorReference, this.documentReference);
} catch (Exception ex) {
LOGGER.warn(String.format("Failed to get velocity engine. Reason: [%s]", ex.getMessage()));
}
this.previousContextId = currentContextId;
this.previousWiki = currentWiki;
}

this.evaluatedParameters = result;
this.previousContextId = currentContextId;
this.previousWiki = currentWiki;
}

return this.evaluatedParameters;
return result;
}
}
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;

import org.apache.velocity.VelocityContext;
import org.junit.jupiter.api.Test;
Expand All @@ -43,6 +44,9 @@
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.rendering.transformation.Transformation;
import org.xwiki.rendering.util.ErrorBlockGenerator;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.test.annotation.BeforeComponent;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.junit5.mockito.MockComponent;
Expand Down Expand Up @@ -90,6 +94,12 @@ public class WikiUIExtensionComponentBuilderTest implements WikiUIExtensionConst
@MockComponent
private LoggerConfiguration loggerConfiguration;

@MockComponent
private AuthorExecutor authorExecutor;

@MockComponent
private AuthorizationManager authorizationManager;

@InjectMockComponents
private WikiUIExtensionComponentBuilder builder;

Expand Down Expand Up @@ -125,6 +135,13 @@ public void configure(MockitoComponentManager componentManager, MockitoOldcore o
componentManager.registerMockComponent(Transformation.class, "macro");
componentManager.registerMockComponent(ContentParser.class);

when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> {
Callable<?> callable = invocation.getArgument(0);
return callable.call();
});

when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOC_REF)).thenReturn(true);

// The document holding the UI extension object.
this.componentDoc = mock(XWikiDocument.class, "xwiki:XWiki.MyUIExtension");
when(this.componentDoc.getDocumentReference()).thenReturn(DOC_REF);
Expand Down Expand Up @@ -194,6 +211,7 @@ private BaseObject createExtensionObject(String id, String extensionPointId, Str
BaseObjectReference objectReference =
new BaseObjectReference(new ObjectReference("XWiki.UIExtensionClass[0]", DOC_REF));
when(extensionObject.getReference()).thenReturn(objectReference);
when(extensionObject.getDocumentReference()).thenReturn(DOC_REF);

when(extensionObject.getOwnerDocument()).thenReturn(this.componentDoc);

Expand Down

0 comments on commit 56748e1

Please sign in to comment.