Skip to content

Commit

Permalink
refactor: setup validation
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Oct 8, 2024
1 parent b7fcbca commit df79eca
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -73,25 +77,82 @@ public class SystemSetupEndpoint {

@Bean
RouterFunction<ServerResponse> 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<ServerResponse> 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<ServerResponse> handleSetupSuccessfully(ServerRequest request) {
if (isHtmlRequest(request)) {
return redirectToConsole();
}
return ServerResponse.noContent().build();
}

private Mono<ServerResponse> 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<ServerResponse> redirectToConsole() {
return ServerResponse.temporaryRedirect(URI.create("/console")).build();
}

private Mono<Void> doInitialization(SetupRequest body) {
Expand Down Expand Up @@ -146,7 +207,7 @@ private Mono<ServerResponse> 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();
Expand All @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ void setClock(Clock clock) {
public Mono<Void> installPresetPlugins() {
return getPresetJars()
.flatMap(path -> this.install(path)
.onErrorResume(PluginAlreadyExistsException.class, e -> Mono.empty())
.flatMap(plugin -> FileUtils.deleteFileSilently(path)
.thenReturn(plugin)
)
Expand Down Expand Up @@ -519,15 +520,6 @@ private Flux<Path> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions application/src/main/resources/templates/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ <h1 class="form-title" th:text="#{title}"></h1>
<div class="form-item">
<label for="password" th:text="#{form.password.label}"></label>
<th:block
th:replace="~{gateway_modules/input_fragments :: password(id = 'password', name = 'password', required = 'true', minlength = 6, enableToggle = true)}"
th:replace="~{gateway_modules/input_fragments :: password(id = 'password', name = 'password', required = 'true', minlength = 5, enableToggle = true)}"
></th:block>
</div>

<div class="form-item">
<label for="confirmPassword" th:text="#{form.confirmPassword.label}"></label>
<th:block
th:replace="~{gateway_modules/input_fragments :: password(id = 'confirmPassword', name = null, required = 'true', minlength = 6, enableToggle = true)}"
th:replace="~{gateway_modules/input_fragments :: password(id = 'confirmPassword', name = null, required = 'true', minlength = 5, enableToggle = true)}"
></th:block>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
18 changes: 7 additions & 11 deletions e2e/testsuite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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": "[email protected]",
"password_confirm": "123456"
}
siteTitle=testing&username={{.param.userName}}&password=123456&[email protected]
expect:
statusCode: 307
statusCode: 204
- name: createPost
request:
api: /api.console.halo.run/v1alpha1/posts
Expand Down

0 comments on commit df79eca

Please sign in to comment.