mirror of https://github.com/halo-dev/halo
feat: add basic functions of demo mode (#569)
* feat: add basic functions of demo mode * feat: add @disableApi annotation to some API that should be disabled * feat: add DisableApi test case * fix: change DisableApi annotation name to DisableOnConditation * Fix test cases * Add more strict test on failing case Co-authored-by: John Niang <johnniang@foxmail.com>pull/575/head
parent
df3601a751
commit
b78e2161e5
|
@ -6,6 +6,7 @@ import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import run.halo.app.Application;
|
import run.halo.app.Application;
|
||||||
import run.halo.app.cache.lock.CacheLock;
|
import run.halo.app.cache.lock.CacheLock;
|
||||||
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
import run.halo.app.model.dto.EnvironmentDTO;
|
import run.halo.app.model.dto.EnvironmentDTO;
|
||||||
import run.halo.app.model.dto.StatisticDTO;
|
import run.halo.app.model.dto.StatisticDTO;
|
||||||
import run.halo.app.model.params.LoginParam;
|
import run.halo.app.model.params.LoginParam;
|
||||||
|
@ -66,6 +67,7 @@ public class AdminController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("password/reset")
|
@PutMapping("password/reset")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Resets password by verify code")
|
@ApiOperation("Resets password by verify code")
|
||||||
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
|
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
|
||||||
adminService.resetPasswordByCode(param);
|
adminService.resetPasswordByCode(param);
|
||||||
|
@ -104,12 +106,14 @@ public class AdminController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("spring/application.yaml")
|
@PutMapping("spring/application.yaml")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Updates application config content")
|
@ApiOperation("Updates application config content")
|
||||||
public void updateSpringApplicationConfig(@RequestParam(name = "content") String content) {
|
public void updateSpringApplicationConfig(@RequestParam(name = "content") String content) {
|
||||||
adminService.updateApplicationConfig(content);
|
adminService.updateApplicationConfig(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = {"halo/restart", "spring/restart"})
|
@PostMapping(value = {"halo/restart", "spring/restart"})
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Restarts halo server")
|
@ApiOperation("Restarts halo server")
|
||||||
public void restartApplication() {
|
public void restartApplication() {
|
||||||
Application.restart();
|
Application.restart();
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.web.PageableDefault;
|
import org.springframework.data.web.PageableDefault;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
import run.halo.app.model.dto.OptionDTO;
|
import run.halo.app.model.dto.OptionDTO;
|
||||||
import run.halo.app.model.dto.OptionSimpleDTO;
|
import run.halo.app.model.dto.OptionSimpleDTO;
|
||||||
import run.halo.app.model.entity.Option;
|
import run.halo.app.model.entity.Option;
|
||||||
|
@ -43,6 +44,7 @@ public class OptionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("saving")
|
@PostMapping("saving")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Saves options")
|
@ApiOperation("Saves options")
|
||||||
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
|
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
|
||||||
optionService.save(optionParams);
|
optionService.save(optionParams);
|
||||||
|
@ -73,12 +75,14 @@ public class OptionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Creates option")
|
@ApiOperation("Creates option")
|
||||||
public void createBy(@RequestBody @Valid OptionParam optionParam) {
|
public void createBy(@RequestBody @Valid OptionParam optionParam) {
|
||||||
optionService.save(optionParam);
|
optionService.save(optionParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("{optionId:\\d+}")
|
@PutMapping("{optionId:\\d+}")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Updates option")
|
@ApiOperation("Updates option")
|
||||||
public void updateBy(@PathVariable("optionId") Integer optionId,
|
public void updateBy(@PathVariable("optionId") Integer optionId,
|
||||||
@RequestBody @Valid OptionParam optionParam) {
|
@RequestBody @Valid OptionParam optionParam) {
|
||||||
|
@ -86,12 +90,14 @@ public class OptionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{optionId:\\d+}")
|
@DeleteMapping("{optionId:\\d+}")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Deletes option")
|
@ApiOperation("Deletes option")
|
||||||
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
|
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
|
||||||
optionService.removePermanently(optionId);
|
optionService.removePermanently(optionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("map_view/saving")
|
@PostMapping("map_view/saving")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Saves options by option map")
|
@ApiOperation("Saves options by option map")
|
||||||
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
|
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
|
||||||
optionService.save(optionMap);
|
optionService.save(optionMap);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
import run.halo.app.model.dto.UserDTO;
|
import run.halo.app.model.dto.UserDTO;
|
||||||
import run.halo.app.model.entity.User;
|
import run.halo.app.model.entity.User;
|
||||||
import run.halo.app.model.params.PasswordParam;
|
import run.halo.app.model.params.PasswordParam;
|
||||||
|
@ -49,6 +50,7 @@ public class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("profiles/password")
|
@PutMapping("profiles/password")
|
||||||
|
@DisableOnCondition
|
||||||
@ApiOperation("Updates user's password")
|
@ApiOperation("Updates user's password")
|
||||||
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
|
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
|
||||||
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
|
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package run.halo.app.handler.aspect;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
|
import run.halo.app.exception.ForbiddenException;
|
||||||
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
|
import run.halo.app.model.enums.Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义注解DisableApi的切面
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @date 2020-02-14 14:08
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DisableOnConditionAspect {
|
||||||
|
|
||||||
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
|
public DisableOnConditionAspect(HaloProperties haloProperties) {
|
||||||
|
this.haloProperties = haloProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut("@annotation(run.halo.app.model.annotation.DisableOnCondition)")
|
||||||
|
public void pointcut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Around("pointcut() && @annotation(disableApi)")
|
||||||
|
public Object around(ProceedingJoinPoint joinPoint,
|
||||||
|
DisableOnCondition disableApi) throws Throwable {
|
||||||
|
Mode mode = disableApi.mode();
|
||||||
|
if (haloProperties.getMode().equals(mode)) {
|
||||||
|
throw new ForbiddenException("禁止访问");
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinPoint.proceed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package run.halo.app.model.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import run.halo.app.model.enums.Mode;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该注解可以限制某些条件下禁止访问api
|
||||||
|
* @author guqing
|
||||||
|
* @date 2020-02-14 13:48
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface DisableOnCondition {
|
||||||
|
@AliasFor("mode")
|
||||||
|
Mode value() default Mode.DEMO;
|
||||||
|
|
||||||
|
@AliasFor("value")
|
||||||
|
Mode mode() default Mode.DEMO;
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import org.springframework.lang.Nullable;
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
PRODUCTION,
|
PRODUCTION,
|
||||||
DEVELOPMENT,
|
DEVELOPMENT,
|
||||||
|
DEMO,
|
||||||
TEST;
|
TEST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
server:
|
||||||
|
port: 8090
|
||||||
|
forward-headers-strategy: native
|
||||||
|
compression:
|
||||||
|
enabled: false
|
||||||
|
spring:
|
||||||
|
jackson:
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
devtools:
|
||||||
|
add-properties: false
|
||||||
|
output:
|
||||||
|
ansi:
|
||||||
|
enabled: always
|
||||||
|
datasource:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
|
||||||
|
# H2 database configuration.
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
url: jdbc:h2:file:~/.halo/db/halo
|
||||||
|
username: admin
|
||||||
|
password: 123456
|
||||||
|
|
||||||
|
# MySQL database configuration.
|
||||||
|
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
# username: root
|
||||||
|
# password: 123456
|
||||||
|
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
settings:
|
||||||
|
web-allow-others: false
|
||||||
|
path: /h2-console
|
||||||
|
enabled: false
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: false
|
||||||
|
open-in-view: false
|
||||||
|
flyway:
|
||||||
|
enabled: false
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10240MB
|
||||||
|
max-request-size: 10240MB
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
base-path: /api/admin/actuator
|
||||||
|
exposure:
|
||||||
|
include: ['httptrace', 'metrics','env','logfile','health']
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
run.halo.app: INFO
|
||||||
|
file:
|
||||||
|
path: ${user.home}/.halo/logs
|
||||||
|
|
||||||
|
halo:
|
||||||
|
download-timeout: 5m
|
||||||
|
doc-disabled: false
|
||||||
|
production-env: false
|
||||||
|
auth-enabled: true
|
||||||
|
mode: demo
|
||||||
|
workDir: ${user.home}/halo-demo/
|
||||||
|
cache: level
|
|
@ -0,0 +1,29 @@
|
||||||
|
package run.halo.app.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
|
import run.halo.app.model.support.BaseResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DisableApi注解测试类
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @date 2020-02-14 17:51
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/test/disableOnCondition")
|
||||||
|
public class DisableOnConditionController {
|
||||||
|
|
||||||
|
@GetMapping("/no")
|
||||||
|
@DisableOnCondition
|
||||||
|
public BaseResponse<String> blockAccess() {
|
||||||
|
return BaseResponse.ok("测试静止访问");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/yes")
|
||||||
|
public BaseResponse<String> ableAccess() {
|
||||||
|
return BaseResponse.ok("成功");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package run.halo.app.handler.aspect;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.web.util.NestedServletException;
|
||||||
|
import run.halo.app.exception.ForbiddenException;
|
||||||
|
import run.halo.app.model.properties.PrimaryProperties;
|
||||||
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author guqing
|
||||||
|
* @date 2020-02-14 17:06
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "halo.auth-enabled=false")
|
||||||
|
@ActiveProfiles("demo")
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
class DisableOnConditionAspectTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MockMvc mvc;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
OptionService optionService;
|
||||||
|
|
||||||
|
static final String REQUEST_URI = "/api/admin/test/disableOnCondition";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
optionService.saveProperty(PrimaryProperties.IS_INSTALLED, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test()
|
||||||
|
void blockAccessTest() throws Exception {
|
||||||
|
Throwable t = null;
|
||||||
|
try {
|
||||||
|
mvc.perform(get(REQUEST_URI + "/no"))
|
||||||
|
.andDo(print())
|
||||||
|
.andReturn();
|
||||||
|
} catch (NestedServletException nse) {
|
||||||
|
t = nse;
|
||||||
|
}
|
||||||
|
Assertions.assertNotNull(t);
|
||||||
|
final Throwable rootException = t;
|
||||||
|
Assertions.assertThrows(ForbiddenException.class, () -> {
|
||||||
|
throw rootException.getCause();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ableAccessTest() throws Exception {
|
||||||
|
mvc.perform(get(REQUEST_URI + "/yes"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.status", is(HttpStatus.OK.value())));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue