fix: missing ServerWebExchange in plugin template processor extension (#6877)

#### What type of PR is this?
/kind bug
/area core
/milestone 2.20.x

#### What this PR does / why we need it:
修复由 #6680 导致的插件模板处理扩展中无法获取到请求上下文的问题

#6680 修复了插件可以在模板处理扩展中通过请求上下文获取到 Halo 的 ApplicationContext 的问题
但这也引入了新的问题就是导致模板处理扩展无法获取到请求上下文,此 PR 通过判断传递给插件的 ITemplateContext 是否为 IWebContext,如果是则包装为 SecureTemplateWebContext 传递给插件,以解决此问题

#### Which issue(s) this PR fixes:
Fixes #6875

#### Does this PR introduce a user-facing change?
```release-note
修复插件模板处理扩展中无法获取到请求上下文的问题
```
pull/6880/head
guqing 2024-10-16 16:21:28 +08:00 committed by GitHub
parent ecc7c07d08
commit c577deb6ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 74 additions and 9 deletions

View File

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

View File

@ -75,7 +75,7 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
// apply processors to modelToInsert
getTemplateHeadProcessors(context)
.concatMap(processor -> processor.process(
new SecureTemplateContext(context), modelToInsert, structureHandler)
SecureTemplateContextWrapper.wrap(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(new SecureTemplateContext(context), tag)
tag -> elementTagPostProcessor.process(
SecureTemplateContextWrapper.wrap(context), tag)
.defaultIfEmpty(tag)
);
}

View File

@ -0,0 +1,24 @@
package run.halo.app.theme.dialect;
import org.thymeleaf.context.Contexts;
import org.thymeleaf.context.ITemplateContext;
/**
* Wrap the delegate template context to a secure template context according to whether it is a
* WebContext.
*
* @author guqing
* @since 2.20.4
*/
public class SecureTemplateContextWrapper {
/**
* Wrap the delegate template context to a secure template context.
*/
static SecureTemplateContext wrap(ITemplateContext delegate) {
if (Contexts.isWebContext(delegate)) {
return new SecureTemplateWebContext(delegate);
}
return new SecureTemplateContext(delegate);
}
}

View File

@ -0,0 +1,36 @@
package run.halo.app.theme.dialect;
import org.springframework.context.ApplicationContext;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.context.IWebContext;
import org.thymeleaf.web.IWebExchange;
/**
* Secure template web context.
* <p>It's used to prevent some dangerous variables such as {@link ApplicationContext} from being
* accessed.
*
* @author guqing
* @see SecureTemplateContext
* @since 2.20.4
*/
class SecureTemplateWebContext extends SecureTemplateContext implements IWebContext {
private final IWebContext delegate;
/**
* The delegate must be an instance of IWebContext to create a SecureTemplateWebContext.
*/
public SecureTemplateWebContext(ITemplateContext delegate) {
super(delegate);
if (delegate instanceof IWebContext webContext) {
this.delegate = webContext;
} else {
throw new IllegalArgumentException("The delegate must be an instance of IWebContext");
}
}
@Override
public IWebExchange getExchange() {
return delegate.getExchange();
}
}

View File

@ -63,7 +63,7 @@ public class TemplateFooterElementTagProcessor extends AbstractElementTagProcess
getTemplateFooterProcessors(context)
.concatMap(processor -> processor.process(
new SecureTemplateContext(context), tag, structureHandler, modelToInsert)
SecureTemplateContextWrapper.wrap(context), tag, structureHandler, modelToInsert)
)
.then()
.block();

View File

@ -82,7 +82,8 @@ class HaloPostTemplateHandlerTest {
void shouldHandleStandaloneElementIfOneElementTagProcessorProvided() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IStandaloneElementTag.class);
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
when(processor.process(SecureTemplateContextWrapper.wrap(templateContext),
standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));
@ -97,7 +98,8 @@ class HaloPostTemplateHandlerTest {
void shouldHandleStandaloneElementIfTagTypeChanged() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IStandaloneElementTag.class);
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
when(processor.process(SecureTemplateContextWrapper.wrap(templateContext),
standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));
@ -114,9 +116,10 @@ class HaloPostTemplateHandlerTest {
var processor2 = mock(ElementTagPostProcessor.class);
var newTag1 = mock(IStandaloneElementTag.class);
var newTag2 = mock(IStandaloneElementTag.class);
when(processor1.process(new SecureTemplateContext(templateContext), standaloneElementTag))
when(processor1.process(SecureTemplateContextWrapper.wrap(templateContext),
standaloneElementTag))
.thenReturn(Mono.just(newTag1));
when(processor2.process(new SecureTemplateContext(templateContext), newTag1))
when(processor2.process(SecureTemplateContextWrapper.wrap(templateContext), newTag1))
.thenReturn(Mono.just(newTag2));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor1, processor2));
@ -131,7 +134,8 @@ class HaloPostTemplateHandlerTest {
void shouldNotHandleIfProcessedTagTypeChanged() {
var processor = mock(ElementTagPostProcessor.class);
var newTag = mock(IOpenElementTag.class);
when(processor.process(new SecureTemplateContext(templateContext), standaloneElementTag))
when(processor.process(SecureTemplateContextWrapper.wrap(templateContext),
standaloneElementTag))
.thenReturn(Mono.just(newTag));
when(extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.thenReturn(List.of(processor));