From df79ecaaa220ec7999d31f785ccbe7446457f212 Mon Sep 17 00:00:00 2001 From: guqing Date: Tue, 8 Oct 2024 12:40:14 +0800 Subject: [PATCH] refactor: setup validation --- .../core/endpoint/SystemSetupEndpoint.java | 85 ++++++++++++++++--- .../{ => console}/SystemConfigEndpoint.java | 2 +- .../halo/app/plugin/PluginServiceImpl.java | 10 +-- .../run/halo/app/security/CsrfConfigurer.java | 2 +- .../src/main/resources/templates/setup.html | 4 +- .../app/plugin/PluginServiceImplTest.java | 27 ------ e2e/testsuite.yaml | 18 ++-- 7 files changed, 85 insertions(+), 63 deletions(-) rename application/src/main/java/run/halo/app/core/endpoint/{ => console}/SystemConfigEndpoint.java (99%) diff --git a/application/src/main/java/run/halo/app/core/endpoint/SystemSetupEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/SystemSetupEndpoint.java index f379d92a1d..f88c2075ae 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/SystemSetupEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/SystemSetupEndpoint.java @@ -1,6 +1,8 @@ package run.halo.app.core.endpoint; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; import static org.springframework.web.reactive.function.server.RequestPredicates.contentType; import static org.springframework.web.reactive.function.server.RequestPredicates.path; @@ -17,6 +19,8 @@ import java.util.Map; import java.util.Properties; import lombok.RequiredArgsConstructor; +import org.springdoc.core.fn.builders.content.Builder; +import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; @@ -33,7 +37,6 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.Validator; import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Flux; @@ -47,6 +50,7 @@ import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemSetting; import run.halo.app.infra.SystemState; +import run.halo.app.infra.exception.RequestBodyValidationException; import run.halo.app.infra.utils.JsonUtils; import run.halo.app.infra.utils.YamlUnstructuredLoader; import run.halo.app.plugin.PluginService; @@ -73,25 +77,82 @@ public class SystemSetupEndpoint { @Bean RouterFunction setupPageRouter() { - return RouterFunctions.route() - .GET(path("/system/setup").and(accept(MediaType.TEXT_HTML)), this::setupPage) - .POST("/system/setup", contentType(MediaType.APPLICATION_FORM_URLENCODED), this::setup) + final var tag = "System"; + return SpringdocRouteBuilder.route() + .GET(path("/system/setup").and(accept(MediaType.TEXT_HTML)), this::setupPage, + builder -> builder.operationId("JumpToSetupPage") + .description("Jump to setup page") + .tag(tag) + .response(responseBuilder() + .content(Builder.contentBuilder() + .mediaType(MediaType.TEXT_HTML_VALUE)) + .implementation(String.class) + ) + ) + .POST("/system/setup", contentType(MediaType.APPLICATION_FORM_URLENCODED), this::setup, + builder -> builder + .operationId("SetupSystem") + .description("Setup system") + .tag(tag) + .requestBody(requestBodyBuilder() + .implementation(SetupRequest.class) + .content(Builder.contentBuilder() + .mediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + ) + ) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.NO_CONTENT.value())) + .implementation(Void.class) + ) + ) .build(); } private Mono setup(ServerRequest request) { return request.formData() .map(SetupRequest::new) + .filterWhen(body -> initializationStateGetter.userInitialized() + .map(initialized -> !initialized) + ) .flatMap(body -> { var bindingResult = body.toBindingResult(); validator.validate(body, bindingResult); if (bindingResult.hasErrors()) { - return ServerResponse.status(HttpStatus.BAD_REQUEST) - .render(SETUP_TEMPLATE, bindingResult.getModel()); + return handleValidationErrors(bindingResult, request); } - return doInitialization(body) - .then(ServerResponse.temporaryRedirect(URI.create("/console")).build()); - }); + return doInitialization(body); + }) + .then(Mono.defer(() -> handleSetupSuccessfully(request))); + } + + private static Mono handleSetupSuccessfully(ServerRequest request) { + if (isHtmlRequest(request)) { + return redirectToConsole(); + } + return ServerResponse.noContent().build(); + } + + private Mono handleValidationErrors(BindingResult bindingResult, + ServerRequest request) { + if (isHtmlRequest(request)) { + return ServerResponse.status(HttpStatus.BAD_REQUEST) + .render(SETUP_TEMPLATE, bindingResult.getModel()); + } + return Mono.error(new RequestBodyValidationException(bindingResult)); + } + + private static boolean isHtmlRequest(ServerRequest request) { + return request.headers().accept().contains(MediaType.TEXT_HTML) + && !isAjaxRequest(request); + } + + private static boolean isAjaxRequest(ServerRequest request) { + return request.headers().firstHeader("X-Requested-With") != null + && "XMLHttpRequest".equals(request.headers().firstHeader("X-Requested-With")); + } + + private static Mono redirectToConsole() { + return ServerResponse.temporaryRedirect(URI.create("/console")).build(); } private Mono doInitialization(SetupRequest body) { @@ -146,7 +207,7 @@ private Mono setupPage(ServerRequest request) { return initializationStateGetter.userInitialized() .flatMap(initialized -> { if (initialized) { - return ServerResponse.temporaryRedirect(URI.create("/console")).build(); + return redirectToConsole(); } var body = new SetupRequest(new LinkedMultiValueMap<>()); var bindingResult = body.toBindingResult(); @@ -163,9 +224,9 @@ public String getUsername() { return formData.getFirst("username"); } - @Schema(requiredMode = REQUIRED, minLength = 3, maxLength = 200) + @Schema(requiredMode = REQUIRED, minLength = 5, maxLength = 200) @NotBlank - @Size(min = 3, max = 200) + @Size(min = 5, max = 200) public String getPassword() { return formData.getFirst("password"); } diff --git a/application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java similarity index 99% rename from application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java rename to application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java index d6ceb690f1..de6bd4fb07 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java @@ -1,4 +1,4 @@ -package run.halo.app.core.endpoint; +package run.halo.app.core.endpoint.console; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; diff --git a/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java b/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java index 463901d9b0..540dc853b4 100644 --- a/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java +++ b/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java @@ -115,6 +115,7 @@ void setClock(Clock clock) { public Mono installPresetPlugins() { return getPresetJars() .flatMap(path -> this.install(path) + .onErrorResume(PluginAlreadyExistsException.class, e -> Mono.empty()) .flatMap(plugin -> FileUtils.deleteFileSilently(path) .thenReturn(plugin) ) @@ -519,15 +520,6 @@ private Flux getPresetJars() { } } - - private Path toPath(Resource resource) { - try { - return Path.of(resource.getURI()); - } catch (IOException e) { - throw Exceptions.propagate(e); - } - } - private static void updatePlugin(Plugin oldPlugin, Plugin newPlugin) { var oldMetadata = oldPlugin.getMetadata(); var newMetadata = newPlugin.getMetadata(); diff --git a/application/src/main/java/run/halo/app/security/CsrfConfigurer.java b/application/src/main/java/run/halo/app/security/CsrfConfigurer.java index 0dc3b25e3b..bac91e04bc 100644 --- a/application/src/main/java/run/halo/app/security/CsrfConfigurer.java +++ b/application/src/main/java/run/halo/app/security/CsrfConfigurer.java @@ -18,7 +18,7 @@ public class CsrfConfigurer implements SecurityConfigurer { public void configure(ServerHttpSecurity http) { var csrfMatcher = new AndServerWebExchangeMatcher( CsrfWebFilter.DEFAULT_CSRF_MATCHER, - new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**") + new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**", "/system/setup") )); http.csrf(csrfSpec -> csrfSpec .csrfTokenRepository(withHttpOnlyFalse()) diff --git a/application/src/main/resources/templates/setup.html b/application/src/main/resources/templates/setup.html index 098905e212..a8cec96a25 100644 --- a/application/src/main/resources/templates/setup.html +++ b/application/src/main/resources/templates/setup.html @@ -71,14 +71,14 @@

diff --git a/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java b/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java index bb38bd0fd9..94734dea6b 100644 --- a/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java +++ b/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java @@ -77,33 +77,6 @@ class PluginServiceImplTest { @InjectMocks PluginServiceImpl pluginService; - @Test - void getPresetsTest() { - var presets = pluginService.getPresets(); - StepVerifier.create(presets) - .assertNext(plugin -> { - assertEquals("fake-plugin", plugin.getMetadata().getName()); - assertEquals("0.0.2", plugin.getSpec().getVersion()); - assertEquals(Plugin.Phase.PENDING, plugin.getStatus().getPhase()); - }) - .verifyComplete(); - } - - @Test - void getPresetIfNotFound() { - var plugin = pluginService.getPreset("not-found-plugin"); - StepVerifier.create(plugin) - .verifyComplete(); - } - - @Test - void getPresetIfFound() { - var plugin = pluginService.getPreset("fake-plugin"); - StepVerifier.create(plugin) - .expectNextCount(1) - .verifyComplete(); - } - @Nested class InstallUpdateReloadTest { diff --git a/e2e/testsuite.yaml b/e2e/testsuite.yaml index 773264f481..05c17f9c34 100644 --- a/e2e/testsuite.yaml +++ b/e2e/testsuite.yaml @@ -8,22 +8,18 @@ param: notificationName: "{{randAlpha 6}}" auth: "Basic YWRtaW46MTIzNDU2" items: -- name: init +- name: setup request: - api: /system/setup + api: | + {{default "http://halo:8090" (env "SERVER")}}/system/setup method: POST header: - Content-Type: application/www-form-urlencoded + Content-Type: application/x-www-form-urlencoded + Accept: application/json body: | - { - "siteTitle": "testing", - "username": "admin", - "password": "123456", - "email": "testing@halo.com", - "password_confirm": "123456" - } + siteTitle=testing&username={{.param.userName}}&password=123456&email=testing@halo.run expect: - statusCode: 307 + statusCode: 204 - name: createPost request: api: /api.console.halo.run/v1alpha1/posts