diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RuleService.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RuleService.java index 5c759f26e819..1993b8700222 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RuleService.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RuleService.java @@ -28,10 +28,9 @@ import org.apache.shenyu.admin.model.result.ConfigImportResult; import org.apache.shenyu.admin.model.vo.RuleVO; import org.apache.shenyu.admin.service.configs.ConfigsImportContext; +import org.apache.shenyu.admin.validation.validator.UriConditionValidator; import org.apache.shenyu.common.dto.RuleData; -import org.apache.shenyu.common.enums.OperatorEnum; import org.apache.shenyu.common.enums.ParamTypeEnum; -import org.springframework.web.util.pattern.PathPatternParser; import java.util.List; @@ -55,17 +54,13 @@ public interface RuleService extends PageService { * @return rows int */ default int createOrUpdate(final RuleDTO ruleDTO) { - - // now, only check rule uri condition in pathPattern mode - // todo check uri in other modes - try { final List ruleConditions = ruleDTO.getRuleConditions(); ruleConditions.stream() .filter(conditionData -> ParamTypeEnum.URI.getName().equals(conditionData.getParamType())) - .filter(conditionData -> OperatorEnum.PATH_PATTERN.getAlias().equals(conditionData.getOperator())) - .map(RuleConditionDTO::getParamValue) - .forEach(PathPatternParser.defaultInstance::parse); + .forEach(conditionData -> { + UriConditionValidator.validate(conditionData.getOperator(), conditionData.getParamValue()); + }); } catch (Exception e) { throw new ShenyuAdminException("uri validation of Condition failed, please check.", e); } @@ -91,7 +86,7 @@ default int createOrUpdate(final RuleDTO ruleDTO) { /** * delete rules by ids and namespaceId. * - * @param ids primary key. + * @param ids primary key. * @param namespaceId namespaceId. * @return rows int */ @@ -171,7 +166,7 @@ default int createOrUpdate(final RuleDTO ruleDTO) { * Find by selector id and name rule do. * * @param selectorId selector id - * @param name rule name + * @param name rule name * @return {@link RuleDO} */ RuleDO findBySelectorIdAndName(String selectorId, String name); @@ -188,8 +183,8 @@ default int createOrUpdate(final RuleDTO ruleDTO) { * Import data. * * @param namespace namespace - * @param ruleList rule list - * @param context import context + * @param ruleList rule list + * @param context import context * @return config import result */ ConfigImportResult importData(String namespace, List ruleList, ConfigsImportContext context); @@ -197,8 +192,8 @@ default int createOrUpdate(final RuleDTO ruleDTO) { /** * Enabled string by ids and namespaceId. * - * @param ids the ids - * @param enabled the enabled + * @param ids the ids + * @param enabled the enabled * @param namespaceId the namespaceId. * @return the result */ diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/validation/validator/UriConditionValidator.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/validation/validator/UriConditionValidator.java new file mode 100644 index 000000000000..4798bc3afedc --- /dev/null +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/validation/validator/UriConditionValidator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.admin.validation.validator; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shenyu.common.enums.OperatorEnum; +import org.springframework.web.util.pattern.PathPatternParser; + +public class UriConditionValidator { + + private static final Map> VALIDATOR_MAP = new HashMap<>(); + + static { + VALIDATOR_MAP.put(OperatorEnum.PATH_PATTERN.getAlias(), + PathPatternParser.defaultInstance::parse); + VALIDATOR_MAP.put(OperatorEnum.REGEX.getAlias(), Pattern::compile); + + Consumer commonPathValidator = value -> { + if (!value.startsWith("/")) { + throw new IllegalArgumentException("The URI must start with '/'"); + } + if (StringUtils.containsAny(value, " ", "\t", "\n")) { + throw new IllegalArgumentException( + "The URI cannot contain whitespaces. Current value: " + value); + } + }; + Consumer blankPathValidator = value -> { + if (StringUtils.isNotBlank(value)) { + throw new IllegalArgumentException("The URI must be blank"); + } + }; + VALIDATOR_MAP.put(OperatorEnum.EQ.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.STARTS_WITH.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.ENDS_WITH.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.MATCH.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.EXCLUDE.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.CONTAINS.getAlias(), commonPathValidator); + VALIDATOR_MAP.put(OperatorEnum.IS_BLANK.getAlias(), blankPathValidator); + } + + public static void validate(final String operator, final String value) { + if (!OperatorEnum.IS_BLANK.getAlias().equals(operator) && StringUtils.isBlank(value)) { + throw new IllegalArgumentException("The URI condition value cannot be empty."); + } + Consumer validator = VALIDATOR_MAP.get(operator); + if (Objects.nonNull(validator)) { + validator.accept(value); + } + } + +} diff --git a/shenyu-admin/src/test/java/org/apache/shenyu/admin/validation/validator/UriConditionValidatorTest.java b/shenyu-admin/src/test/java/org/apache/shenyu/admin/validation/validator/UriConditionValidatorTest.java new file mode 100644 index 000000000000..dcc756b7daea --- /dev/null +++ b/shenyu-admin/src/test/java/org/apache/shenyu/admin/validation/validator/UriConditionValidatorTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.admin.validation.validator; + +import org.apache.shenyu.common.enums.OperatorEnum; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.web.util.pattern.PatternParseException; + +import java.util.regex.PatternSyntaxException; + +/** + * Test cases for {@link UriConditionValidator}. + */ +public class UriConditionValidatorTest { + + @Test + public void testValidateErrorRegex() { + String pattern = "[abc"; + Assertions.assertThrows(PatternSyntaxException.class, + () -> UriConditionValidator.validate(OperatorEnum.REGEX.getAlias(), pattern)); + } + + @Test + public void testValidateHappyRegex() { + String pattern = "^\\/[a-zA-Z0-9\\-_\\/]+$"; + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.REGEX.getAlias(), pattern)); + } + + @Test + public void testValidateHappyPathPattern() { + String pattern = "/http/**"; + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.PATH_PATTERN.getAlias(), pattern)); + } + + @Test + public void testValidateErrorPathPattern() { + String pattern = "/http/{abc"; + Assertions.assertThrows(PatternParseException.class, + () -> UriConditionValidator.validate(OperatorEnum.PATH_PATTERN.getAlias(), pattern)); + } + + @Test + public void testValidateHappyIsBlank() { + String pattern = ""; + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.IS_BLANK.getAlias(), pattern)); + } + + @Test + public void testValidateErrorIsBlank() { + String pattern = "/http"; + Assertions.assertThrows(IllegalArgumentException.class, + () -> UriConditionValidator.validate(OperatorEnum.IS_BLANK.getAlias(), pattern)); + } + + @Test + public void testValidateEmptyValueForNormalOperator() { + String pattern = " "; + Assertions.assertThrows(IllegalArgumentException.class, + () -> UriConditionValidator.validate(OperatorEnum.EQ.getAlias(), pattern)); + } + + @Test + public void testValidateHappyOtherCondition() { + String pattern = "/http/test"; + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.STARTS_WITH.getAlias(), pattern)); + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.ENDS_WITH.getAlias(), pattern)); + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.MATCH.getAlias(), pattern)); + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.EQ.getAlias(), pattern)); + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.EXCLUDE.getAlias(), pattern)); + Assertions.assertDoesNotThrow(() -> UriConditionValidator.validate(OperatorEnum.CONTAINS.getAlias(), pattern)); + } + + @Test + public void testValidateErrorOtherCondition() { + String pattern = "http/test"; + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.STARTS_WITH.getAlias(), pattern)); + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.ENDS_WITH.getAlias(), pattern)); + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.MATCH.getAlias(), pattern)); + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.EQ.getAlias(), pattern)); + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.EXCLUDE.getAlias(), pattern)); + Assertions.assertThrows(IllegalArgumentException.class, () -> UriConditionValidator.validate(OperatorEnum.CONTAINS.getAlias(), pattern)); + } +} \ No newline at end of file