Commit 4dc4035a authored by kezhenxu94's avatar kezhenxu94 Committed by Jason Song

Enhancement: validates http parameters using javax.validation api (#1858)

* enhancement: validates http parameters using javax.validation api

* improve code quality according Codacity

* update as requested

* update according to Codacy
parent 601e3665
...@@ -7,7 +7,6 @@ import com.ctrip.framework.apollo.common.entity.App; ...@@ -7,7 +7,6 @@ import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
...@@ -19,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody; ...@@ -19,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
...@@ -34,10 +34,7 @@ public class AppController { ...@@ -34,10 +34,7 @@ public class AppController {
} }
@PostMapping("/apps") @PostMapping("/apps")
public AppDTO create(@RequestBody AppDTO dto) { public AppDTO create(@Valid @RequestBody AppDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getAppId())) {
throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
App entity = BeanUtils.transform(App.class, dto); App entity = BeanUtils.transform(App.class, dto);
App managedEntity = appService.findOne(entity.getAppId()); App managedEntity = appService.findOne(entity.getAppId());
if (managedEntity != null) { if (managedEntity != null) {
...@@ -46,8 +43,7 @@ public class AppController { ...@@ -46,8 +43,7 @@ public class AppController {
entity = adminService.createNewApp(entity); entity = adminService.createNewApp(entity);
dto = BeanUtils.transform(AppDTO.class, entity); return BeanUtils.transform(AppDTO.class, entity);
return dto;
} }
@DeleteMapping("/apps/{appId:.+}") @DeleteMapping("/apps/{appId:.+}")
......
...@@ -6,7 +6,6 @@ import com.ctrip.framework.apollo.common.dto.ClusterDTO; ...@@ -6,7 +6,6 @@ import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
...@@ -16,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody; ...@@ -16,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List; import java.util.List;
@RestController @RestController
...@@ -30,11 +30,7 @@ public class ClusterController { ...@@ -30,11 +30,7 @@ public class ClusterController {
@PostMapping("/apps/{appId}/clusters") @PostMapping("/apps/{appId}/clusters")
public ClusterDTO create(@PathVariable("appId") String appId, public ClusterDTO create(@PathVariable("appId") String appId,
@RequestParam(value = "autoCreatePrivateNamespace", defaultValue = "true") boolean autoCreatePrivateNamespace, @RequestParam(value = "autoCreatePrivateNamespace", defaultValue = "true") boolean autoCreatePrivateNamespace,
@RequestBody ClusterDTO dto) { @Valid @RequestBody ClusterDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getName())) {
throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
Cluster entity = BeanUtils.transform(Cluster.class, dto); Cluster entity = BeanUtils.transform(Cluster.class, dto);
Cluster managedEntity = clusterService.findOne(appId, entity.getName()); Cluster managedEntity = clusterService.findOne(appId, entity.getName());
if (managedEntity != null) { if (managedEntity != null) {
...@@ -47,8 +43,7 @@ public class ClusterController { ...@@ -47,8 +43,7 @@ public class ClusterController {
entity = clusterService.saveWithoutInstanceOfAppNamespaces(entity); entity = clusterService.saveWithoutInstanceOfAppNamespaces(entity);
} }
dto = BeanUtils.transform(ClusterDTO.class, entity); return BeanUtils.transform(ClusterDTO.class, entity);
return dto;
} }
@DeleteMapping("/apps/{appId}/clusters/{clusterName:.+}") @DeleteMapping("/apps/{appId}/clusters/{clusterName:.+}")
......
...@@ -6,7 +6,6 @@ import com.ctrip.framework.apollo.common.dto.NamespaceDTO; ...@@ -6,7 +6,6 @@ import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
...@@ -15,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody; ...@@ -15,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -29,10 +29,8 @@ public class NamespaceController { ...@@ -29,10 +29,8 @@ public class NamespaceController {
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces") @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces")
public NamespaceDTO create(@PathVariable("appId") String appId, public NamespaceDTO create(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @RequestBody NamespaceDTO dto) { @PathVariable("clusterName") String clusterName,
if (!InputValidator.isValidClusterNamespace(dto.getNamespaceName())) { @Valid @RequestBody NamespaceDTO dto) {
throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
Namespace entity = BeanUtils.transform(Namespace.class, dto); Namespace entity = BeanUtils.transform(Namespace.class, dto);
Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName()); Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName());
if (managedEntity != null) { if (managedEntity != null) {
...@@ -41,8 +39,7 @@ public class NamespaceController { ...@@ -41,8 +39,7 @@ public class NamespaceController {
entity = namespaceService.save(entity); entity = namespaceService.save(entity);
dto = BeanUtils.transform(NamespaceDTO.class, entity); return BeanUtils.transform(NamespaceDTO.class, entity);
return dto;
} }
@DeleteMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}") @DeleteMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}")
......
...@@ -21,7 +21,7 @@ public abstract class AbstractControllerTest { ...@@ -21,7 +21,7 @@ public abstract class AbstractControllerTest {
@Autowired @Autowired
private HttpMessageConverters httpMessageConverters; private HttpMessageConverters httpMessageConverters;
protected RestTemplate restTemplate = (new TestRestTemplate()).getRestTemplate(); protected RestTemplate restTemplate = (new TestRestTemplate()).getRestTemplate();
@PostConstruct @PostConstruct
...@@ -32,4 +32,8 @@ public abstract class AbstractControllerTest { ...@@ -32,4 +32,8 @@ public abstract class AbstractControllerTest {
@Value("${local.server.port}") @Value("${local.server.port}")
int port; int port;
protected String url(String path) {
return "http://localhost:" + port + path;
}
} }
...@@ -4,7 +4,7 @@ import com.ctrip.framework.apollo.biz.repository.AppRepository; ...@@ -4,7 +4,7 @@ import com.ctrip.framework.apollo.biz.repository.AppRepository;
import com.ctrip.framework.apollo.common.dto.AppDTO; import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.entity.App; import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity; ...@@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import static org.hamcrest.Matchers.containsString;
public class AppControllerTest extends AbstractControllerTest { public class AppControllerTest extends AbstractControllerTest {
...@@ -112,6 +113,20 @@ public class AppControllerTest extends AbstractControllerTest { ...@@ -112,6 +113,20 @@ public class AppControllerTest extends AbstractControllerTest {
Assert.assertNull(deletedApp); Assert.assertNull(deletedApp);
} }
@Test
@Sql(scripts = "/controller/cleanup.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public void shouldFailedWhenAppIdIsInvalid() {
AppDTO dto = generateSampleDTOData();
dto.setAppId("invalid app id");
try {
restTemplate.postForEntity(getBaseAppUrl(), dto, String.class);
Assert.fail("Should throw");
} catch (HttpClientErrorException e) {
Assert.assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode());
Assert.assertThat(new String(e.getResponseBodyAsByteArray()), containsString(InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
}
private AppDTO generateSampleDTOData() { private AppDTO generateSampleDTOData() {
AppDTO dto = new AppDTO(); AppDTO dto = new AppDTO();
dto.setAppId("someAppId"); dto.setAppId("someAppId");
......
...@@ -2,17 +2,23 @@ package com.ctrip.framework.apollo.adminservice.controller; ...@@ -2,17 +2,23 @@ package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Cluster; import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.service.ClusterService; import com.ctrip.framework.apollo.biz.service.ClusterService;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
public class ClusterControllerTest { public class ClusterControllerTest extends AbstractControllerTest {
private ClusterController clusterController; private ClusterController clusterController;
@Mock @Mock
...@@ -40,4 +46,29 @@ public class ClusterControllerTest { ...@@ -40,4 +46,29 @@ public class ClusterControllerTest {
clusterController.delete("1", "2", "d"); clusterController.delete("1", "2", "d");
verify(clusterService, times(1)).findOne("1", "2"); verify(clusterService, times(1)).findOne("1", "2");
} }
@Test
public void shouldFailWhenRequestBodyInvalid() {
ClusterDTO cluster = new ClusterDTO();
cluster.setAppId("valid");
cluster.setName("notBlank");
ResponseEntity<ClusterDTO> response =
restTemplate.postForEntity(baseUrl() + "/apps/{appId}/clusters", cluster, ClusterDTO.class, cluster.getAppId());
ClusterDTO createdCluster = response.getBody();
Assert.assertNotNull(createdCluster);
Assert.assertEquals(cluster.getAppId(), createdCluster.getAppId());
Assert.assertEquals(cluster.getName(), createdCluster.getName());
cluster.setName("invalid app name");
try {
restTemplate.postForEntity(baseUrl() + "/apps/{appId}/clusters", cluster, ClusterDTO.class, cluster.getAppId());
Assert.fail("Should throw");
} catch (HttpClientErrorException e) {
Assert.assertThat(new String(e.getResponseBodyAsByteArray()), containsString(InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
}
private String baseUrl() {
return "http://localhost:" + port;
}
} }
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.web.client.HttpClientErrorException;
import static org.hamcrest.Matchers.containsString;
/**
* Created by kezhenxu at 2019/1/8 16:27.
*
* @author kezhenxu (kezhenxu94@163.com)
*/
public class NamespaceControllerTest extends AbstractControllerTest {
@Test
public void create() {
try {
NamespaceDTO namespaceDTO = new NamespaceDTO();
namespaceDTO.setClusterName("cluster");
namespaceDTO.setNamespaceName("invalid name");
namespaceDTO.setAppId("whatever");
restTemplate.postForEntity(
url("/apps/{appId}/clusters/{clusterName}/namespaces"),
namespaceDTO, NamespaceDTO.class, namespaceDTO.getAppId(), namespaceDTO.getClusterName());
Assert.fail("Should throw");
} catch (HttpClientErrorException e) {
Assert.assertThat(new String(e.getResponseBodyAsByteArray()), containsString(InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
}
}
...@@ -190,7 +190,7 @@ public class RemoteConfigRepositoryTest { ...@@ -190,7 +190,7 @@ public class RemoteConfigRepositoryTest {
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification)); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification));
when(someResponse.getBody()).thenReturn(newApolloConfig); when(someResponse.getBody()).thenReturn(newApolloConfig);
longPollFinished.get(500, TimeUnit.MILLISECONDS); longPollFinished.get(5000, TimeUnit.MILLISECONDS);
remoteConfigLongPollService.stopLongPollingRefresh(); remoteConfigLongPollService.stopLongPollingRefresh();
......
package com.ctrip.framework.apollo.common.controller; package com.ctrip.framework.apollo.common.controller;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.exception.AbstractApolloHttpException; import com.ctrip.framework.apollo.common.exception.AbstractApolloHttpException;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.tracer.Tracer;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.event.Level; import org.slf4j.event.Level;
...@@ -13,22 +20,15 @@ import org.springframework.http.HttpHeaders; ...@@ -13,22 +20,15 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeException; import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
import static org.slf4j.event.Level.ERROR;
import java.lang.reflect.Type; import static org.slf4j.event.Level.WARN;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import static org.slf4j.event.Level.*;
import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
...@@ -72,6 +72,18 @@ public class GlobalDefaultExceptionHandler { ...@@ -72,6 +72,18 @@ public class GlobalDefaultExceptionHandler {
return handleError(request, ex.getHttpStatus(), ex); return handleError(request, ex.getHttpStatus(), ex);
} }
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValidException(
HttpServletRequest request, MethodArgumentNotValidException ex
) {
final Optional<ObjectError> firstError = ex.getBindingResult().getAllErrors().stream().findFirst();
if (firstError.isPresent()) {
final String firstErrorMessage = firstError.get().getDefaultMessage();
return handleError(request, BAD_REQUEST, new BadRequestException(firstErrorMessage));
}
return handleError(request, BAD_REQUEST, ex);
}
private ResponseEntity<Map<String, Object>> handleError(HttpServletRequest request, private ResponseEntity<Map<String, Object>> handleError(HttpServletRequest request,
HttpStatus status, Throwable ex) { HttpStatus status, Throwable ex) {
return handleError(request, status, ex, ERROR); return handleError(request, status, ex, ERROR);
......
package com.ctrip.framework.apollo.common.dto; package com.ctrip.framework.apollo.common.dto;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import javax.validation.constraints.Pattern;
public class AppDTO extends BaseDTO{ public class AppDTO extends BaseDTO{
private long id; private long id;
private String name; private String name;
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = "AppId格式错误: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
private String appId; private String appId;
private String orgId; private String orgId;
......
package com.ctrip.framework.apollo.common.dto; package com.ctrip.framework.apollo.common.dto;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class ClusterDTO extends BaseDTO{ public class ClusterDTO extends BaseDTO{
private long id; private long id;
@NotBlank(message = "cluster name cannot be blank")
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = "Cluster格式错误: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
private String name; private String name;
@NotBlank(message = "appId cannot be blank")
private String appId; private String appId;
private long parentClusterId; private long parentClusterId;
......
package com.ctrip.framework.apollo.common.dto; package com.ctrip.framework.apollo.common.dto;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import javax.validation.constraints.Pattern;
public class NamespaceDTO extends BaseDTO{ public class NamespaceDTO extends BaseDTO{
private long id; private long id;
private String appId; private String appId;
private String clusterName; private String clusterName;
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = "Namespace格式错误: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
private String namespaceName; private String namespaceName;
public long getId() { public long getId() {
......
package com.ctrip.framework.apollo.common.entity; package com.ctrip.framework.apollo.common.entity;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
...@@ -13,9 +16,15 @@ import javax.persistence.Table; ...@@ -13,9 +16,15 @@ import javax.persistence.Table;
@Where(clause = "isDeleted = 0") @Where(clause = "isDeleted = 0")
public class App extends BaseEntity { public class App extends BaseEntity {
@NotBlank(message = "Name cannot be blank")
@Column(name = "Name", nullable = false) @Column(name = "Name", nullable = false)
private String name; private String name;
@NotBlank(message = "AppId cannot be blank")
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
@Column(name = "AppId", nullable = false) @Column(name = "AppId", nullable = false)
private String appId; private String appId;
...@@ -25,9 +34,11 @@ public class App extends BaseEntity { ...@@ -25,9 +34,11 @@ public class App extends BaseEntity {
@Column(name = "OrgName", nullable = false) @Column(name = "OrgName", nullable = false)
private String orgName; private String orgName;
@NotBlank(message = "OwnerName cannot be blank")
@Column(name = "OwnerName", nullable = false) @Column(name = "OwnerName", nullable = false)
private String ownerName; private String ownerName;
@NotBlank(message = "OwnerEmail cannot be blank")
@Column(name = "OwnerEmail", nullable = false) @Column(name = "OwnerEmail", nullable = false)
private String ownerEmail; private String ownerEmail;
......
package com.ctrip.framework.apollo.common.entity; package com.ctrip.framework.apollo.common.entity;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
...@@ -16,9 +19,15 @@ import javax.persistence.Table; ...@@ -16,9 +19,15 @@ import javax.persistence.Table;
@Where(clause = "isDeleted = 0") @Where(clause = "isDeleted = 0")
public class AppNamespace extends BaseEntity { public class AppNamespace extends BaseEntity {
@NotBlank(message = "App Name cannot be blank")
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = "Namespace格式错误: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & " + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE
)
@Column(name = "Name", nullable = false) @Column(name = "Name", nullable = false)
private String name; private String name;
@NotBlank(message = "AppId cannot be blank")
@Column(name = "AppId", nullable = false) @Column(name = "AppId", nullable = false)
private String appId; private String appId;
......
...@@ -2,7 +2,6 @@ package com.ctrip.framework.apollo.common.utils; ...@@ -2,7 +2,6 @@ package com.ctrip.framework.apollo.common.utils;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
...@@ -12,18 +11,12 @@ public class InputValidator { ...@@ -12,18 +11,12 @@ public class InputValidator {
public static final String INVALID_CLUSTER_NAMESPACE_MESSAGE = "只允许输入数字,字母和符号 - _ ."; public static final String INVALID_CLUSTER_NAMESPACE_MESSAGE = "只允许输入数字,字母和符号 - _ .";
public static final String INVALID_NAMESPACE_NAMESPACE_MESSAGE = "不允许以.json, .yml, .yaml, .xml, .properties结尾"; public static final String INVALID_NAMESPACE_NAMESPACE_MESSAGE = "不允许以.json, .yml, .yaml, .xml, .properties结尾";
public static final String CLUSTER_NAMESPACE_VALIDATOR = "[0-9a-zA-Z_.-]+"; public static final String CLUSTER_NAMESPACE_VALIDATOR = "[0-9a-zA-Z_.-]+";
public static final String APP_NAMESPACE_VALIDATOR = "[a-zA-Z0-9._-]+(?<!\\.(json|yml|yaml|xml|properties))$"; private static final String APP_NAMESPACE_VALIDATOR = "[a-zA-Z0-9._-]+(?<!\\.(json|yml|yaml|xml|properties))$";
private static final Pattern CLUSTER_NAMESPACE_PATTERN = private static final Pattern CLUSTER_NAMESPACE_PATTERN =
Pattern.compile(CLUSTER_NAMESPACE_VALIDATOR); Pattern.compile(CLUSTER_NAMESPACE_VALIDATOR);
private static final Pattern APP_NAMESPACE_PATTERN = private static final Pattern APP_NAMESPACE_PATTERN =
Pattern.compile(APP_NAMESPACE_VALIDATOR); Pattern.compile(APP_NAMESPACE_VALIDATOR);
public static boolean isValidClusterNamespace(String input) {
Matcher matcher = CLUSTER_NAMESPACE_PATTERN.matcher(input);
return matcher.matches();
}
public static boolean isValidAppNamespace(String name){ public static boolean isValidAppNamespace(String name){
if (StringUtils.isEmpty(name)){ if (StringUtils.isEmpty(name)){
return false; return false;
......
package com.ctrip.framework.apollo.common.utils;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class InputValidatorTest {
@Test
public void testIsValidClusterNamespaceWithCorrectInput() throws Exception {
String someValidInput = "a1-b2_c3.d4";
assertTrue(InputValidator.isValidClusterNamespace(someValidInput));
}
@Test
public void testIsValidClusterNamespaceWithInCorrectInput() throws Exception {
String someInvalidInput = "中文123";
assertFalse(InputValidator.isValidClusterNamespace(someInvalidInput));
String anotherInvalidInput = "123@#{}";
assertFalse(InputValidator.isValidClusterNamespace(anotherInvalidInput));
}
}
package com.ctrip.framework.apollo.openapi.dto; package com.ctrip.framework.apollo.openapi.dto;
public class OpenAppNamespaceDTO extends BaseDTO { public class OpenAppNamespaceDTO extends BaseDTO {
private String name; private String name;
private String appId; private String appId;
......
...@@ -56,8 +56,9 @@ public class NamespaceController { ...@@ -56,8 +56,9 @@ public class NamespaceController {
@PreAuthorize(value = "@consumerPermissionValidator.hasCreateNamespacePermission(#request, #appId)") @PreAuthorize(value = "@consumerPermissionValidator.hasCreateNamespacePermission(#request, #appId)")
@PostMapping(value = "/openapi/v1/apps/{appId}/appnamespaces") @PostMapping(value = "/openapi/v1/apps/{appId}/appnamespaces")
public OpenAppNamespaceDTO createNamespace(@PathVariable String appId, @RequestBody OpenAppNamespaceDTO appNamespaceDTO, public OpenAppNamespaceDTO createNamespace(@PathVariable String appId,
HttpServletRequest request) { @RequestBody OpenAppNamespaceDTO appNamespaceDTO,
HttpServletRequest request) {
if (!Objects.equals(appId, appNamespaceDTO.getAppId())) { if (!Objects.equals(appId, appNamespaceDTO.getAppId())) {
throw new BadRequestException(String.format("AppId not equal. AppId in path = %s, AppId in payload = %s", appId, throw new BadRequestException(String.format("AppId not equal. AppId in path = %s, AppId in payload = %s", appId,
......
...@@ -5,8 +5,6 @@ import com.ctrip.framework.apollo.common.entity.App; ...@@ -5,8 +5,6 @@ import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.http.MultiResponseEntity; import com.ctrip.framework.apollo.common.http.MultiResponseEntity;
import com.ctrip.framework.apollo.common.http.RichResponseEntity; import com.ctrip.framework.apollo.common.http.RichResponseEntity;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.component.PortalSettings; import com.ctrip.framework.apollo.portal.component.PortalSettings;
...@@ -40,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestParam; ...@@ -40,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
...@@ -99,7 +98,7 @@ public class AppController { ...@@ -99,7 +98,7 @@ public class AppController {
} }
@PostMapping @PostMapping
public App create(@RequestBody AppModel appModel) { public App create(@Valid @RequestBody AppModel appModel) {
App app = transformToApp(appModel); App app = transformToApp(appModel);
...@@ -119,7 +118,7 @@ public class AppController { ...@@ -119,7 +118,7 @@ public class AppController {
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)") @PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@PutMapping("/{appId:.+}") @PutMapping("/{appId:.+}")
public void update(@PathVariable String appId, @RequestBody AppModel appModel) { public void update(@PathVariable String appId, @Valid @RequestBody AppModel appModel) {
if (!Objects.equals(appId, appModel.getAppId())) { if (!Objects.equals(appId, appModel.getAppId())) {
throw new BadRequestException("The App Id of path variable and request body is different"); throw new BadRequestException("The App Id of path variable and request body is different");
} }
...@@ -149,15 +148,7 @@ public class AppController { ...@@ -149,15 +148,7 @@ public class AppController {
} }
@PostMapping(value = "/envs/{env}", consumes = {"application/json"}) @PostMapping(value = "/envs/{env}", consumes = {"application/json"})
public ResponseEntity<Void> create(@PathVariable String env, @RequestBody App app) { public ResponseEntity<Void> create(@PathVariable String env, @Valid @RequestBody App app) {
RequestPrecondition.checkArgumentsNotEmpty(app.getName(), app.getAppId(), app.getOwnerEmail(),
app.getOwnerName(),
app.getOrgId(), app.getOrgName());
if (!InputValidator.isValidClusterNamespace(app.getAppId())) {
throw new BadRequestException(InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE);
}
appService.createAppInRemote(Env.valueOf(env), app); appService.createAppInRemote(Env.valueOf(env), app);
roleInitializationService.initNamespaceSpecificEnvRoles(app.getAppId(), ConfigConsts.NAMESPACE_APPLICATION, env, userInfoHolder.getUser().getUserId()); roleInitializationService.initNamespaceSpecificEnvRoles(app.getAppId(), ConfigConsts.NAMESPACE_APPLICATION, env, userInfoHolder.getUser().getUserId());
...@@ -211,13 +202,6 @@ public class AppController { ...@@ -211,13 +202,6 @@ public class AppController {
String orgId = appModel.getOrgId(); String orgId = appModel.getOrgId();
String orgName = appModel.getOrgName(); String orgName = appModel.getOrgName();
RequestPrecondition.checkArgumentsNotEmpty(appId, appName, ownerName, orgId, orgName);
if (!InputValidator.isValidClusterNamespace(appModel.getAppId())) {
throw new BadRequestException(
String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
return App.builder() return App.builder()
.appId(appId) .appId(appId)
.name(appName) .name(appName)
......
package com.ctrip.framework.apollo.portal.controller; package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.ClusterDTO; import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.service.ClusterService; import com.ctrip.framework.apollo.portal.service.ClusterService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
...@@ -16,6 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping; ...@@ -16,6 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Objects; import java.util.Objects;
import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
...@@ -34,14 +32,9 @@ public class ClusterController { ...@@ -34,14 +32,9 @@ public class ClusterController {
@PreAuthorize(value = "@permissionValidator.hasCreateClusterPermission(#appId)") @PreAuthorize(value = "@permissionValidator.hasCreateClusterPermission(#appId)")
@PostMapping(value = "apps/{appId}/envs/{env}/clusters") @PostMapping(value = "apps/{appId}/envs/{env}/clusters")
public ClusterDTO createCluster(@PathVariable String appId, @PathVariable String env, public ClusterDTO createCluster(@PathVariable String appId, @PathVariable String env,
@RequestBody ClusterDTO cluster) { @Valid @RequestBody ClusterDTO cluster) {
checkModel(Objects.nonNull(cluster)); checkModel(Objects.nonNull(cluster));
RequestPrecondition.checkArgumentsNotEmpty(cluster.getAppId(), cluster.getName());
if (!InputValidator.isValidClusterNamespace(cluster.getName())) {
throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
}
String operator = userInfoHolder.getUser().getUserId(); String operator = userInfoHolder.getUser().getUserId();
cluster.setDataChangeLastModifiedBy(operator); cluster.setDataChangeLastModifiedBy(operator);
......
...@@ -7,7 +7,6 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException; ...@@ -7,7 +7,6 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.http.MultiResponseEntity; import com.ctrip.framework.apollo.common.http.MultiResponseEntity;
import com.ctrip.framework.apollo.common.http.RichResponseEntity; import com.ctrip.framework.apollo.common.http.RichResponseEntity;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition; import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
...@@ -37,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestBody; ...@@ -37,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -190,15 +190,7 @@ public class NamespaceController { ...@@ -190,15 +190,7 @@ public class NamespaceController {
@PostMapping("/apps/{appId}/appnamespaces") @PostMapping("/apps/{appId}/appnamespaces")
public AppNamespace createAppNamespace(@PathVariable String appId, public AppNamespace createAppNamespace(@PathVariable String appId,
@RequestParam(defaultValue = "true") boolean appendNamespacePrefix, @RequestParam(defaultValue = "true") boolean appendNamespacePrefix,
@RequestBody AppNamespace appNamespace) { @Valid @RequestBody AppNamespace appNamespace) {
RequestPrecondition.checkArgumentsNotEmpty(appNamespace.getAppId(), appNamespace.getName());
if (!InputValidator.isValidAppNamespace(appNamespace.getName())) {
throw new BadRequestException(String.format("Namespace格式错误: %s",
InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
+ InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE));
}
AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace, appendNamespacePrefix); AppNamespace createdAppNamespace = appNamespaceService.createAppNamespaceInLocal(appNamespace, appendNamespacePrefix);
if (portalConfig.canAppAdminCreatePrivateNamespace() || createdAppNamespace.isPublic()) { if (portalConfig.canAppAdminCreatePrivateNamespace() || createdAppNamespace.isPublic()) {
......
package com.ctrip.framework.apollo.portal.entity.model; package com.ctrip.framework.apollo.portal.entity.model;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import java.util.Set; import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class AppModel { public class AppModel {
@NotBlank(message = "name cannot be blank")
private String name; private String name;
@NotBlank(message = "appId cannot be blank")
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = "AppId格式错误: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
private String appId; private String appId;
@NotBlank(message = "orgId cannot be blank")
private String orgId; private String orgId;
@NotBlank(message = "orgName cannot be blank")
private String orgName; private String orgName;
@NotBlank(message = "ownerName cannot be blank")
private String ownerName; private String ownerName;
private Set<String> admins; private Set<String> admins;
......
package com.ctrip.framework.apollo;
import com.ctrip.framework.apollo.openapi.auth.ConsumerPermissionValidator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Created by kezhenxu at 2019/1/8 20:19.
*
* @author kezhenxu (kezhenxu94@163.com)
*/
@Profile("skipAuthorization")
@Configuration
public class SkipAuthorizationConfiguration {
@Primary
@Bean
@Qualifier("consumerPermissionValidator")
public ConsumerPermissionValidator consumerPermissionValidator() {
ConsumerPermissionValidator mock = mock(ConsumerPermissionValidator.class);
when(mock.hasCreateNamespacePermission(any(), any())).thenReturn(true);
return mock;
}
}
package com.ctrip.framework.apollo.openapi.v1.controller;
import javax.annotation.PostConstruct;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* Created by kezhenxu at 2019/1/8 18:19.
*
* @author kezhenxu (kezhenxu94@163.com)
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AbstractControllerTest {
@Autowired
private HttpMessageConverters httpMessageConverters;
protected RestTemplate restTemplate = (new TestRestTemplate()).getRestTemplate();
@PostConstruct
protected void postConstruct() {
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
restTemplate.setMessageConverters(httpMessageConverters.getConverters());
}
@Value("${local.server.port}")
protected int port;
protected String url(String path) {
return "http://localhost:" + port + path;
}
}
package com.ctrip.framework.apollo.openapi.v1.controller;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.openapi.auth.ConsumerPermissionValidator;
import com.ctrip.framework.apollo.openapi.dto.OpenAppNamespaceDTO;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.HttpClientErrorException;
import static org.hamcrest.Matchers.containsString;
/**
* Created by kezhenxu at 2019/1/8 18:17.
*
* @author kezhenxu (kezhenxu94@163.com)
*/
@ActiveProfiles("skipAuthorization")
public class NamespaceControllerTest extends AbstractControllerTest {
@Autowired
private ConsumerPermissionValidator consumerPermissionValidator;
@Ignore
@Test
public void shouldFailWhenAppNamespaceNameIsInvalid() {
Assert.assertTrue(consumerPermissionValidator.hasCreateNamespacePermission(null, null));
OpenAppNamespaceDTO dto = new OpenAppNamespaceDTO();
dto.setAppId("appId");
dto.setName("invalid name");
try {
restTemplate.postForEntity(
url("/openapi/v1/apps/{appId}/appnamespaces"),
dto, OpenAppNamespaceDTO.class, dto.getAppId()
);
Assert.fail("should throw");
} catch (HttpClientErrorException e) {
Assert.assertThat(
new String(e.getResponseBodyAsByteArray()),
containsString(InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & "
+ InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE)
);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment