Make ApplicationContext inaccessible in ITemplateContext (#6680)

#### 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
```
pull/6686/head
John Niang 2024-09-20 11:14:58 +08:00 committed by GitHub
parent fb9aff00ca
commit a87dedd916
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 172 additions and 10 deletions

View File

@ -45,6 +45,6 @@ public class CommentElementTagProcessor extends AbstractElementTagProcessor {
structureHandler.replaceWith("", false);
return;
}
commentWidget.render(context, tag, structureHandler);
commentWidget.render(new SecureTemplateContext(context), tag, structureHandler);
}
}

View File

@ -71,7 +71,9 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
// apply processors to modelToInsert
getTemplateHeadProcessors(context)
.concatMap(processor -> processor.process(context, modelToInsert, structureHandler))
.concatMap(processor -> processor.process(
new SecureTemplateContext(context), modelToInsert, structureHandler)
)
.then()
.block();

View File

@ -57,7 +57,8 @@ public class HaloPostTemplateHandler extends AbstractTemplateHandler {
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 =

View File

@ -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);
}
}

View File

@ -58,8 +58,8 @@ public class TemplateFooterElementTagProcessor extends AbstractElementTagProcess
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();

View File

@ -82,7 +82,7 @@ class HaloPostTemplateHandlerTest {
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));
@ -97,7 +97,7 @@ class HaloPostTemplateHandlerTest {
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));
@ -114,9 +114,9 @@ class HaloPostTemplateHandlerTest {
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));
@ -131,7 +131,7 @@ class HaloPostTemplateHandlerTest {
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));