Skip to content
Permalink
Browse files Browse the repository at this point in the history
XWIKI-17794: Properly interpret velocity in gadget titles
  • Loading branch information
surli committed Jan 12, 2021
1 parent 5308f7d commit bb7068b
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 21 deletions.
Expand Up @@ -44,17 +44,16 @@
import org.xwiki.rendering.block.WordBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.executor.ContentExecutor;
import org.xwiki.rendering.executor.ContentExecutorException;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.macro.dashboard.Gadget;
import org.xwiki.rendering.macro.dashboard.GadgetSource;
import org.xwiki.rendering.parser.MissingParserException;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.MacroTransformationContext;
import org.xwiki.rendering.util.ParserUtils;
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;

Expand Down Expand Up @@ -120,6 +119,9 @@ public class DefaultGadgetSource implements GadgetSource
@Inject
private JobProgressManager progress;

@Inject
private AuthorizationManager authorizationManager;

/**
* Prepare the parser to parse the title and content of the gadget into blocks.
*/
Expand Down Expand Up @@ -194,19 +196,23 @@ private List<Gadget> prepareGadgets(List<BaseObject> objects, Syntax sourceSynta
String position = xObject.getStringValue("position");
String id = xObject.getNumber() + "";

// render title with velocity
StringWriter writer = new StringWriter();
// FIXME: the engine has an issue with $ and # as last character. To test and fix if it happens
velocityEngine.evaluate(velocityContext, writer, key, title);
String gadgetTitle = writer.toString();
String gadgetTitle;

XWikiDocument ownerDocument = xObject.getOwnerDocument();
if (this.authorizationManager.hasAccess(Right.SCRIPT, ownerDocument.getAuthorReference(), ownerDocument.getDocumentReference())) {
gadgetTitle =
this.evaluateVelocityTitle(velocityContext, velocityEngine, key, title, ownerDocument);
} else {
gadgetTitle = title;
}

// parse both the title and content in the syntax of the transformation context
List<Block> titleBlocks =
renderGadgetProperty(gadgetTitle, sourceSyntax, xObject.getDocumentReference(),
xObject.getOwnerDocument(), context);
ownerDocument, context);
List<Block> contentBlocks =
renderGadgetProperty(content, sourceSyntax, xObject.getDocumentReference(),
xObject.getOwnerDocument(), context);
ownerDocument, context);

// create a gadget will all these and add the gadget to the container of gadgets
Gadget gadget = new Gadget(id, titleBlocks, contentBlocks, position);
Expand All @@ -222,6 +228,18 @@ private List<Gadget> prepareGadgets(List<BaseObject> objects, Syntax sourceSynta
return gadgets;
}

private String evaluateVelocityTitle(VelocityContext velocityContext, VelocityEngine velocityEngine, String key,
String title, XWikiDocument ownerDocument) throws Exception
{
return this.authorExecutor.call(() -> {
// render title with velocity
StringWriter writer = new StringWriter();
// FIXME: the engine has an issue with $ and # as last character. To test and fix if it happens
velocityEngine.evaluate(velocityContext, writer, key, title);
return writer.toString();
}, ownerDocument.getAuthorReference(), ownerDocument.getDocumentReference());
}

private List<Block> renderGadgetProperty(String content, Syntax sourceSyntax, EntityReference sourceReference,
XWikiDocument ownerDocument, MacroTransformationContext context)
throws Exception
Expand Down
Expand Up @@ -41,6 +41,8 @@
import org.xwiki.rendering.transformation.MacroTransformationContext;
import org.xwiki.rendering.transformation.TransformationContext;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.test.junit5.mockito.ComponentTest;
import org.xwiki.test.junit5.mockito.InjectMockComponents;
import org.xwiki.test.junit5.mockito.MockComponent;
Expand All @@ -57,6 +59,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ComponentTest
Expand All @@ -72,6 +75,9 @@
@MockComponent
private AuthorExecutor authorExecutor;

@MockComponent
private AuthorizationManager authorizationManager;

@Mock
private DocumentReference documentReference;

Expand Down Expand Up @@ -99,6 +105,11 @@
@Mock
private MacroTransformationContext macroTransformationContext;

@Mock
private VelocityEngine velocityEngine;

private ContentExecutor<MacroTransformationContext> contentExecutor;

@BeforeEach
void setup(MockitoComponentManager componentManager) throws Exception
{
Expand All @@ -123,23 +134,14 @@ void setup(MockitoComponentManager componentManager) throws Exception
when(transformationContext.getId()).thenReturn(transformationId);

VelocityManager velocityManager = componentManager.getInstance(VelocityManager.class);
VelocityEngine velocityEngine = mock(VelocityEngine.class);
when(velocityManager.getVelocityEngine()).thenReturn(velocityEngine);
when(velocityEngine.evaluate(any(), any(), any(), any(String.class))).then((Answer<Void>) invocation -> {
Object[] args = invocation.getArguments();
StringWriter stringWriter = (StringWriter) args[1];
String title = (String) args[3];
stringWriter.append(title);
return null;
});

AuthorExecutor authorExecutor = componentManager.getInstance(AuthorExecutor.class);
when(authorExecutor.call(any(), eq(ownerAuthorReference), eq(ownerSourceReference))).then(invocationOnMock -> {
Callable callable = (Callable) invocationOnMock.getArguments()[0];
return callable.call();
});

ContentExecutor<MacroTransformationContext> contentExecutor =
this.contentExecutor =
componentManager.getInstance(ContentExecutor.TYPE_MACRO_TRANSFORMATION);
when(contentExecutor.execute(any(), any(), any(), any())).then((Answer<XDOM>) invocationOnMock -> {
String content = invocationOnMock.getArgument(0);
Expand All @@ -162,12 +164,50 @@ void getGadgets() throws Exception
when(gadgetObject1.getLargeStringValue("content")).thenReturn("Some content");
when(gadgetObject1.getStringValue("position")).thenReturn("0");
when(gadgetObject1.getNumber()).thenReturn(42);
when(this.authorizationManager.hasAccess(Right.SCRIPT, ownerAuthorReference, ownerSourceReference)).thenReturn(true);
when(this.velocityEngine.evaluate(any(), any(), any(), eq("Gadget 1"))).then((Answer<Void>) invocation -> {
Object[] args = invocation.getArguments();
StringWriter stringWriter = (StringWriter) args[1];
String title = "Evaluated velocity version of gadget 1";
stringWriter.append(title);
return null;
});

List<Gadget> gadgets = this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext);
assertEquals(1, gadgets.size());
Gadget gadget = gadgets.get(0);
assertEquals("Gadget 1", gadget.getTitle().get(0).toString());
assertEquals("Evaluated velocity version of gadget 1", gadget.getTitle().get(0).toString());
assertEquals("Some content", gadget.getContent().get(0).toString());
assertEquals("42", gadget.getId());
verify(this.contentExecutor)
.execute(eq("Evaluated velocity version of gadget 1"), any(), any(), any());
verify(this.contentExecutor)
.execute(eq("Some content"), any(), any(), any());
}

@Test
void getGadgetWithoutScriptRight() throws Exception
{
assertEquals(new ArrayList<>(), this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext));

BaseObject gadgetObject1 = mock(BaseObject.class);
when(xWikiDocument.getXObjects(gadgetClassReference)).thenReturn(Collections.singletonList(gadgetObject1));
when(gadgetObject1.getOwnerDocument()).thenReturn(ownerDocument);
when(gadgetObject1.getStringValue("title")).thenReturn("Gadget 2");
when(gadgetObject1.getLargeStringValue("content")).thenReturn("Some other content");
when(gadgetObject1.getStringValue("position")).thenReturn("2");
when(gadgetObject1.getNumber()).thenReturn(12);
when(this.authorizationManager.hasAccess(Right.SCRIPT, ownerAuthorReference, ownerSourceReference)).thenReturn(false);

List<Gadget> gadgets = this.defaultGadgetSource.getGadgets(testSource, macroTransformationContext);
assertEquals(1, gadgets.size());
Gadget gadget = gadgets.get(0);
assertEquals("Gadget 2", gadget.getTitle().get(0).toString());
assertEquals("Some other content", gadget.getContent().get(0).toString());
assertEquals("12", gadget.getId());
verify(this.contentExecutor)
.execute(eq("Gadget 2"), any(), any(), any());
verify(this.contentExecutor)
.execute(eq("Some other content"), any(), any(), any());
}
}

0 comments on commit bb7068b

Please sign in to comment.