Skip to content

Commit

Permalink
Make ApplicationContext inaccessible in ITemplateContext (#6680)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind improvement
/area core
/area plugin
/milestone 2.20.x

#### What this PR does / why we need it:

This PR disables access to ApplicationContext using ITemplateContext.

#### Does this PR introduce a user-facing change?

```release-note
None
```
  • Loading branch information
JohnNiang committed Sep 20, 2024
1 parent fb9aff0 commit a87dedd
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
structureHandler.replaceWith("", false);
return;
}
commentWidget.render(context, tag, structureHandler);
commentWidget.render(new SecureTemplateContext(context), tag, structureHandler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ protected void doProcess(ITemplateContext context, IModel model,

// apply processors to modelToInsert
getTemplateHeadProcessors(context)
.concatMap(processor -> processor.process(context, modelToInsert, structureHandler))
.concatMap(processor -> processor.process(
new SecureTemplateContext(context), modelToInsert, structureHandler)
)
.then()
.block();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ private IProcessableElementTag handleElementTag(
var context = getContext();
for (ElementTagPostProcessor elementTagPostProcessor : postProcessors) {
tagProcessorChain = tagProcessorChain.flatMap(
tag -> elementTagPostProcessor.process(context, tag).defaultIfEmpty(tag)
tag -> elementTagPostProcessor.process(new SecureTemplateContext(context), tag)
.defaultIfEmpty(tag)
);
}
processedTag =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package run.halo.app.theme.dialect;

import static org.thymeleaf.spring6.expression.ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.context.IdentifierSequences;
import org.thymeleaf.engine.TemplateData;
import org.thymeleaf.expression.IExpressionObjects;
import org.thymeleaf.inline.IInliner;
import org.thymeleaf.model.IModelFactory;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.templatemode.TemplateMode;

/**
* Secure template context.
*
* @author johnniang
* @since 2.20.0
*/
class SecureTemplateContext implements ITemplateContext {

private static final Set<String> DANGEROUS_VARIABLES =
Set.of(THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);

private final ITemplateContext delegate;

public SecureTemplateContext(ITemplateContext delegate) {
this.delegate = delegate;
}

@Override
public TemplateData getTemplateData() {
return delegate.getTemplateData();
}

@Override
public TemplateMode getTemplateMode() {
return delegate.getTemplateMode();
}

@Override
public List<TemplateData> getTemplateStack() {
return delegate.getTemplateStack();
}

@Override
public List<IProcessableElementTag> getElementStack() {
return delegate.getElementStack();
}

@Override
public Map<String, Object> getTemplateResolutionAttributes() {
return delegate.getTemplateResolutionAttributes();
}

@Override
public IModelFactory getModelFactory() {
return delegate.getModelFactory();
}

@Override
public boolean hasSelectionTarget() {
return delegate.hasSelectionTarget();
}

@Override
public Object getSelectionTarget() {
return delegate.getSelectionTarget();
}

@Override
public IInliner getInliner() {
return delegate.getInliner();
}

@Override
public String getMessage(
Class<?> origin,
String key,
Object[] messageParameters,
boolean useAbsentMessageRepresentation
) {
return delegate.getMessage(origin, key, messageParameters, useAbsentMessageRepresentation);
}

@Override
public String buildLink(String base, Map<String, Object> parameters) {
return delegate.buildLink(base, parameters);
}

@Override
public IdentifierSequences getIdentifierSequences() {
return delegate.getIdentifierSequences();
}

@Override
public IEngineConfiguration getConfiguration() {
return delegate.getConfiguration();
}

@Override
public IExpressionObjects getExpressionObjects() {
return delegate.getExpressionObjects();
}

@Override
public Locale getLocale() {
return delegate.getLocale();
}

@Override
public boolean containsVariable(String name) {
if (DANGEROUS_VARIABLES.contains(name)) {
return false;
}
return delegate.containsVariable(name);
}

@Override
public Set<String> getVariableNames() {
return delegate.getVariableNames()
.stream()
.filter(name -> !DANGEROUS_VARIABLES.contains(name))
.collect(Collectors.toSet());
}

@Override
public Object getVariable(String name) {
if (DANGEROUS_VARIABLES.contains(name)) {
return null;
}
return delegate.getVariable(name);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

SecureTemplateContext that = (SecureTemplateContext) o;
return Objects.equals(delegate, that.delegate);
}

@Override
public int hashCode() {
return Objects.hashCode(delegate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
modelToInsert.add(context.getModelFactory().createText(globalFooterText));

getTemplateFooterProcessors(context)
.concatMap(processor -> processor.process(context, tag,
structureHandler, modelToInsert)
.concatMap(processor -> processor.process(
new SecureTemplateContext(context), tag, structureHandler, modelToInsert)
)
.then()
.block();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void shouldHandleStandaloneElementIfNoElementTagProcessors(
void shouldHandleStandaloneElementIfOneElementTagProcessorProvided() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IStandaloneElementTag.class);
when(processor.process(templateContext, standaloneElementTag))
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));
Expand All @@ -97,7 +97,7 @@ void shouldHandleStandaloneElementIfOneElementTagProcessorProvided() {
void shouldHandleStandaloneElementIfTagTypeChanged() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IStandaloneElementTag.class);
when(processor.process(templateContext, standaloneElementTag))
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));
Expand All @@ -114,9 +114,9 @@ void shouldHandleStandaloneElementIfMoreElementTagProcessorsProvided() {
var processor2 = mock(ElementTagPostProcessor.class);
var newTag1 = mock(IStandaloneElementTag.class);
var newTag2 = mock(IStandaloneElementTag.class);
when(processor1.process(templateContext, standaloneElementTag))
when(processor1.process(new SecureTemplateContext(templateContext), standaloneElementTag))
.thenReturn(Mono.just(newTag1));
when(processor2.process(templateContext, newTag1))
when(processor2.process(new SecureTemplateContext(templateContext), newTag1))
.thenReturn(Mono.just(newTag2));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor1, processor2));
Expand All @@ -131,7 +131,7 @@ void shouldHandleStandaloneElementIfMoreElementTagProcessorsProvided() {
void shouldNotHandleIfProcessedTagTypeChanged() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IOpenElementTag.class);
when(processor.process(templateContext, standaloneElementTag))
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));
Expand Down

0 comments on commit a87dedd

Please sign in to comment.