Make ApplicationContext inaccessible in ServerWebExchange (#6679)

#### What type of PR is this?

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

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

Plugins can implement their own RouterFunctions and ControllerMappings, but those might expose root ApplicationContext for plugins, which is not expected.

So this PR fixes the insecure access to root ApplicationContext.

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

```release-note
None
```
pull/6686/head
John Niang 2024-09-20 11:16:59 +08:00 committed by GitHub
parent a87dedd916
commit df195b12f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 2 deletions

View File

@ -12,6 +12,7 @@ import java.util.List;
import java.util.Objects;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxRegistrations;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -33,6 +34,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.resource.EncodedResourceResolver;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import reactor.core.publisher.Mono;
@ -41,6 +43,7 @@ import run.halo.app.console.WebSocketRequestPredicate;
import run.halo.app.core.endpoint.WebSocketHandlerMapping;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
import run.halo.app.infra.SecureRequestMappingHandlerAdapter;
import run.halo.app.infra.properties.AttachmentProperties;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
@ -67,6 +70,19 @@ public class WebFluxConfig implements WebFluxConfigurer {
this.applicationContext = applicationContext;
}
@Bean
WebFluxRegistrations webFluxRegistrations() {
return new WebFluxRegistrations() {
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
// Because we have no chance to customize ServerWebExchangeMethodArgumentResolver,
// we have to use SecureRequestMappingHandlerAdapter to replace a secure
// ServerWebExchange.
return new SecureRequestMappingHandlerAdapter();
}
};
}
@Bean
ServerResponse.Context context(CodecConfigurer codec,
ViewResolutionResultHandler resultHandler) {

View File

@ -0,0 +1,26 @@
package run.halo.app.infra;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Secure request mapping handler adapter.
*
* @author johnniang
* @since 2.20.0
*/
public class SecureRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
@NonNull
public Mono<HandlerResult> handle(
@NonNull ServerWebExchange exchange,
@NonNull Object handler
) {
return super.handle(new SecureServerWebExchange(exchange), handler);
}
}

View File

@ -0,0 +1,31 @@
package run.halo.app.infra;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.support.ServerRequestWrapper;
import org.springframework.web.server.ServerWebExchange;
/**
* Secure server request without application context available.
*
* @author johnniang
* @since 2.20.0
*/
public class SecureServerRequest extends ServerRequestWrapper {
/**
* Create a new {@code ServerRequestWrapper} that wraps the given request.
*
* @param delegate the request to wrap
*/
public SecureServerRequest(ServerRequest delegate) {
super(delegate);
}
@Override
@NonNull
public ServerWebExchange exchange() {
return new SecureServerWebExchange(super.exchange());
}
}

View File

@ -0,0 +1,25 @@
package run.halo.app.infra;
import org.springframework.context.ApplicationContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
/**
* Secure server web exchange without application context available.
*
* @author johnniang
* @since 2.20.0
*/
public class SecureServerWebExchange extends ServerWebExchangeDecorator {
public SecureServerWebExchange(ServerWebExchange delegate) {
super(delegate);
}
@Override
public ApplicationContext getApplicationContext() {
// Always return null to prevent access to application context
return null;
}
}

View File

@ -9,6 +9,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
import run.halo.app.infra.SecureServerRequest;
/**
* Aggregated router function built from all custom endpoints.
@ -28,7 +29,7 @@ public class AggregatedRouterFunction implements RouterFunction<ServerResponse>
@Override
public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) {
return aggregated.route(request);
return aggregated.route(new SecureServerRequest(request));
}
@Override

View File

@ -11,6 +11,7 @@ import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.infra.SecureServerRequest;
/**
* A composite {@link RouterFunction} implementation for plugin.
@ -31,8 +32,9 @@ public class DefaultPluginRouterFunctionRegistry
@Override
@NonNull
public Mono<HandlerFunction<ServerResponse>> route(@NonNull ServerRequest request) {
var secureRequest = new SecureServerRequest(request);
return Flux.fromIterable(this.routerFunctions)
.concatMap(routerFunction -> routerFunction.route(request))
.concatMap(routerFunction -> routerFunction.route(secureRequest))
.next();
}