diff --git a/src/main/java/run/halo/app/controller/admin/api/AdminController.java b/src/main/java/run/halo/app/controller/admin/api/AdminController.java index c9d1f48c30..f3ea575f2f 100644 --- a/src/main/java/run/halo/app/controller/admin/api/AdminController.java +++ b/src/main/java/run/halo/app/controller/admin/api/AdminController.java @@ -20,6 +20,7 @@ import run.halo.app.model.enums.MFAType; import run.halo.app.model.params.LoginParam; import run.halo.app.model.params.ResetPasswordParam; +import run.halo.app.model.params.ResetPasswordSendCodeParam; import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.support.BaseResponse; import run.halo.app.security.token.AuthToken; @@ -80,7 +81,7 @@ public void logout() { @ApiOperation("Sends reset password verify code") @CacheLock(autoDelete = false) @DisableOnCondition - public void sendResetCode(@RequestBody @Valid ResetPasswordParam param) { + public void sendResetCode(@RequestBody @Valid ResetPasswordSendCodeParam param) { adminService.sendResetPasswordCode(param); } diff --git a/src/main/java/run/halo/app/model/params/ResetPasswordParam.java b/src/main/java/run/halo/app/model/params/ResetPasswordParam.java index 688ceb9d24..786e6e4d1a 100644 --- a/src/main/java/run/halo/app/model/params/ResetPasswordParam.java +++ b/src/main/java/run/halo/app/model/params/ResetPasswordParam.java @@ -1,7 +1,9 @@ package run.halo.app.model.params; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Reset password params. @@ -10,15 +12,13 @@ * @date 2019-09-05 */ @Data -public class ResetPasswordParam { - - @NotBlank(message = "用户名不能为空") - private String username; - - @NotBlank(message = "邮箱不能为空") - private String email; +@EqualsAndHashCode(callSuper = true) +public class ResetPasswordParam extends ResetPasswordSendCodeParam { + @NotBlank(message = "验证码不能为空") private String code; + @NotBlank(message = "密码不能为空") + @Size(min = 8, max = 100, message = "密码的字符长度必须在 {min} - {max} 之间") private String password; } diff --git a/src/main/java/run/halo/app/model/params/ResetPasswordSendCodeParam.java b/src/main/java/run/halo/app/model/params/ResetPasswordSendCodeParam.java new file mode 100644 index 0000000000..74981a69f9 --- /dev/null +++ b/src/main/java/run/halo/app/model/params/ResetPasswordSendCodeParam.java @@ -0,0 +1,20 @@ +package run.halo.app.model.params; + +import javax.validation.constraints.NotBlank; +import lombok.Data; + +/** + * Parameters required to send the reset password verification code. + * + * @author ryanwang + * @date 2022-01-24 + */ +@Data +public class ResetPasswordSendCodeParam { + + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "邮箱不能为空") + private String email; +} diff --git a/src/main/java/run/halo/app/service/AdminService.java b/src/main/java/run/halo/app/service/AdminService.java index d888b184f5..4f0f27e46a 100644 --- a/src/main/java/run/halo/app/service/AdminService.java +++ b/src/main/java/run/halo/app/service/AdminService.java @@ -6,6 +6,7 @@ import run.halo.app.model.entity.User; import run.halo.app.model.params.LoginParam; import run.halo.app.model.params.ResetPasswordParam; +import run.halo.app.model.params.ResetPasswordSendCodeParam; import run.halo.app.security.token.AuthToken; /** @@ -54,7 +55,7 @@ public interface AdminService { * * @param param param must not be null */ - void sendResetPasswordCode(@NonNull ResetPasswordParam param); + void sendResetPasswordCode(@NonNull ResetPasswordSendCodeParam param); /** * Reset password by code. diff --git a/src/main/java/run/halo/app/service/impl/AdminServiceImpl.java b/src/main/java/run/halo/app/service/impl/AdminServiceImpl.java index 115c81db45..01058b76b7 100644 --- a/src/main/java/run/halo/app/service/impl/AdminServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/AdminServiceImpl.java @@ -32,6 +32,7 @@ import run.halo.app.model.enums.MFAType; import run.halo.app.model.params.LoginParam; import run.halo.app.model.params.ResetPasswordParam; +import run.halo.app.model.params.ResetPasswordSendCodeParam; import run.halo.app.model.properties.EmailProperties; import run.halo.app.model.support.HaloConst; import run.halo.app.security.authentication.Authentication; @@ -184,7 +185,7 @@ public void clearToken() { } @Override - public void sendResetPasswordCode(ResetPasswordParam param) { + public void sendResetPasswordCode(ResetPasswordSendCodeParam param) { cacheStore.getAny("code", String.class).ifPresent(code -> { throw new ServiceException("已经获取过验证码,不能重复获取"); }); diff --git a/src/test/java/run/halo/app/controller/AdminControllerTest.java b/src/test/java/run/halo/app/controller/AdminControllerTest.java new file mode 100644 index 0000000000..fe6bec2ff8 --- /dev/null +++ b/src/test/java/run/halo/app/controller/AdminControllerTest.java @@ -0,0 +1,203 @@ +package run.halo.app.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.nio.charset.StandardCharsets; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import run.halo.app.controller.admin.api.AdminController; +import run.halo.app.core.ControllerExceptionHandler; +import run.halo.app.model.dto.EnvironmentDTO; +import run.halo.app.model.dto.LoginPreCheckDTO; +import run.halo.app.model.entity.User; +import run.halo.app.model.params.LoginParam; +import run.halo.app.model.params.ResetPasswordParam; +import run.halo.app.model.params.ResetPasswordSendCodeParam; +import run.halo.app.security.token.AuthToken; +import run.halo.app.service.AdminService; +import run.halo.app.utils.JsonUtils; + +/** + * Admin controller test. + * + * @author guqing + * @date 2022-02-11 + */ +@ExtendWith(MockitoExtension.class) +public class AdminControllerTest { + + private static final String FIELD_ERROR_MESSAGE = "字段验证错误,请完善后重试!"; + + private MockMvc mockMvc; + + @BeforeEach + public void setUp() { + AdminController adminController = + new AdminController(new MockAdminService(), null); + mockMvc = MockMvcBuilders.standaloneSetup(adminController) + .setControllerAdvice(ControllerExceptionHandler.class) + .addFilter((request, response, chain) -> { + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + chain.doFilter(request, response); + }) + .build(); + } + + @Test + public void sendResetPasswordCodeShouldOk() throws Exception { + ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam(); + param.setEmail("example@example.com"); + param.setUsername("admin"); + sendResetPasswordCodePerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + public void sendResetPasswordCodeShould4xxError() throws Exception { + ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam(); + param.setEmail("example@example.com"); + sendResetPasswordCodePerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE)); + } + + @Test + public void sendResetPasswordCodeShould4xxErrorToo() throws Exception { + ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam(); + param.setUsername("admin"); + sendResetPasswordCodePerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE)); + } + + @Test + public void resetPasswordShouldOk() throws Exception { + ResetPasswordParam param = new ResetPasswordParam(); + param.setUsername("admin"); + param.setEmail("example@example.com"); + param.setPassword("a password"); + param.setCode("a code"); + param.setPassword("12345678"); + resetPasswordPerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + public void resetPasswordAndEmailFieldAbsentShouldReturn4xxError() throws Exception { + ResetPasswordParam param = new ResetPasswordParam(); + // The email field value in the parent class is missing,verification will also be triggered + param.setUsername("admin"); + param.setCode("a code"); + param.setPassword("a password"); + resetPasswordPerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE)); + } + + @Test + public void resetPasswordAndCodeFieldAbsentShouldReturn4xxError() throws Exception { + ResetPasswordParam param = new ResetPasswordParam(); + // The code field value in the param class is missing,verification will also be triggered + param.setUsername("admin"); + param.setEmail("example@example.com"); + param.setPassword("a password"); + resetPasswordPerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE)); + } + + @Test + public void resetPasswordAndInsufficientPasswordLengthShouldReturn4xxError() throws Exception { + ResetPasswordParam param = new ResetPasswordParam(); + // The code field value in the param class is missing,verification will also be triggered + param.setUsername("admin"); + param.setEmail("example@example.com"); + param.setCode("a code"); + param.setPassword("123"); + resetPasswordPerform(JsonUtils.objectToJson(param)) + .andDo(print()) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE)); + } + + @NotNull + private ResultActions resetPasswordPerform(String param) + throws Exception { + return this.mockMvc.perform(put("/api/admin/password/reset") + .content(param) + .contentType(MediaType.APPLICATION_JSON)); + } + + @NotNull + private ResultActions sendResetPasswordCodePerform(String param) + throws Exception { + return this.mockMvc.perform(post("/api/admin/password/code") + .content(param) + .contentType(MediaType.APPLICATION_JSON)); + } + + public static class MockAdminService implements AdminService { + + @Override + public User authenticate(LoginParam loginParam) { + return null; + } + + @Override + public AuthToken authCodeCheck(LoginParam loginParam) { + return null; + } + + @Override + public void clearToken() { + + } + + @Override + public void sendResetPasswordCode(ResetPasswordSendCodeParam param) { + + } + + @Override + public void resetPasswordByCode(ResetPasswordParam param) { + + } + + @Override + public EnvironmentDTO getEnvironments() { + return null; + } + + @Override + public AuthToken refreshToken(String refreshToken) { + return null; + } + + @Override + public String getLogFiles(Long lines) { + return null; + } + + @Override + public LoginPreCheckDTO getUserEnv(String username) { + return null; + } + } +}