diff --git a/stream-core/src/main/java/org/dromara/streamquery/stream/core/bean/BeanHelper.java b/stream-core/src/main/java/org/dromara/streamquery/stream/core/bean/BeanHelper.java index 5c6679c5c658fffbc5bb04e962fe03e9326167dd..5f35f0c0a6ca99e371b3dace5af60ee8b687512c 100644 --- a/stream-core/src/main/java/org/dromara/streamquery/stream/core/bean/BeanHelper.java +++ b/stream-core/src/main/java/org/dromara/streamquery/stream/core/bean/BeanHelper.java @@ -153,8 +153,8 @@ public class BeanHelper { LambdaExecutable targetGetterLambda = LambdaHelper.resolve(targetGetter); if (!Opp.of(sourceGetterLambda.getReturnType()) - .map(Type::getTypeName) - .equals(Opp.of(targetGetterLambda.getReturnType()).map(Type::getTypeName))) { + .map(Type::getTypeName) + .equals(Opp.of(targetGetterLambda.getReturnType()).map(Type::getTypeName))) { continue; } diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/QueryCondition.java b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/QueryCondition.java index 92d07e697a767e96f20c65d98ee469a242001ea9..f3ba9061a86e1ac91e62b8415afa1c2f66dd71ef 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/QueryCondition.java +++ b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/QueryCondition.java @@ -32,10 +32,11 @@ import org.dromara.streamquery.stream.core.lambda.LambdaExecutable; import org.dromara.streamquery.stream.core.lambda.LambdaHelper; import org.dromara.streamquery.stream.core.optional.Opp; import org.dromara.streamquery.stream.core.stream.Steam; +import org.dromara.streamquery.stream.plugin.mybatisplus.engine.configuration.StreamPluginAutoConfiguration; +import org.dromara.streamquery.stream.plugin.mybatisplus.engine.utils.SqlInjectionUtilSq; import java.util.Collection; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -105,7 +106,9 @@ public class QueryCondition extends LambdaQueryWrapper { * @param column a {@link SFunction} object * @param data a {@link String} object * @return a {@link QueryCondition} object + * @deprecated because this method is superfluous */ + @Deprecated public QueryCondition eq(SFunction column, String data) { super.eq(StringUtils.isNotEmpty(data), column, data); return this; @@ -119,8 +122,8 @@ public class QueryCondition extends LambdaQueryWrapper { * @param a R class * @return a {@link QueryCondition} object */ - public > QueryCondition eq(SFunction column, R data) { - super.eq(Objects.nonNull(data), column, data); + public QueryCondition eq(SFunction column, R data) { + super.eq(column, data); return this; } @@ -130,7 +133,9 @@ public class QueryCondition extends LambdaQueryWrapper { * @param column a {@link SFunction} object * @param data a {@link String} object * @return a {@link QueryCondition} object + * @deprecated because this method is superfluous */ + @Deprecated public QueryCondition like(SFunction column, String data) { super.like(StringUtils.isNotEmpty(data), column, data); return this; @@ -144,10 +149,10 @@ public class QueryCondition extends LambdaQueryWrapper { * @param a R class * @return a {@link QueryCondition} object */ - public > QueryCondition in( + public QueryCondition in( SFunction column, Collection dataList) { this.mapping = getMapping(column); - super.in(CollectionUtils.isNotEmpty(dataList), column, dataList); + super.in(column, dataList); return this; } @@ -157,7 +162,9 @@ public class QueryCondition extends LambdaQueryWrapper { * @param column a {@link SFunction} object * @param data a {@link String} object * @return a {@link QueryCondition} object + * @deprecated because this method is superfluous */ + @Deprecated public QueryCondition activeEq(SFunction column, String data) { Opp.of(data).map(v -> super.eq(column, v)).orElseRun(() -> Database.notActive(this)); return this; @@ -170,8 +177,10 @@ public class QueryCondition extends LambdaQueryWrapper { * @param data a R object * @param a R class * @return a {@link QueryCondition} object + * @deprecated because this method is optional */ - public > QueryCondition activeEq( + @Deprecated + public QueryCondition activeEq( SFunction column, R data) { Opp.of(data).map(v -> super.eq(column, v)).orElseRun(() -> Database.notActive(this)); return this; @@ -183,7 +192,9 @@ public class QueryCondition extends LambdaQueryWrapper { * @param column a {@link SFunction} object * @param data a {@link String} object * @return a {@link QueryCondition} object + * @deprecated because this method is superfluous */ + @Deprecated public QueryCondition activeLike(SFunction column, String data) { Opp.of(data).map(v -> super.like(column, v)).orElseRun(() -> Database.notActive(this)); return this; @@ -196,14 +207,28 @@ public class QueryCondition extends LambdaQueryWrapper { * @param dataList a {@link Collection} object * @param a R class * @return a {@link QueryCondition} object + * @deprecated because this method is optional */ - public > QueryCondition activeIn( + @Deprecated + public QueryCondition activeIn( SFunction column, Collection dataList) { this.mapping = getMapping(column); Opp.ofColl(dataList).map(v -> super.in(column, v)).orElseRun(() -> Database.notActive(this)); return this; } + @Override + public QueryCondition or(Consumer> consumer) { + super.or(consumer); + return this; + } + + @Override + public QueryCondition and(Consumer> consumer) { + super.and(consumer); + return this; + } + @Override protected LambdaQueryWrapper addCondition( boolean condition, SFunction column, SqlKeyword sqlKeyword, Object val) { @@ -266,4 +291,20 @@ public class QueryCondition extends LambdaQueryWrapper { SharedString.emptyString(), SharedString.emptyString()); } + + @Override + public LambdaQueryWrapper apply(boolean condition, String applySql, Object... values) { + if (StreamPluginAutoConfiguration.isSafeModeEnabled() && SqlInjectionUtilSq.check(applySql)) { + throw new IllegalArgumentException("SQL Injection attempt detected in 'apply'"); + } + return super.apply(condition, applySql, values); + } + + @Override + public LambdaQueryWrapper having(boolean condition, String sqlHaving, Object... params) { + if (StreamPluginAutoConfiguration.isSafeModeEnabled() && SqlInjectionUtilSq.check(sqlHaving)) { + throw new IllegalArgumentException("SQL Injection attempt detected in 'having'"); + } + return super.having(condition, sqlHaving, params); + } } diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/configuration/StreamPluginAutoConfiguration.java b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/configuration/StreamPluginAutoConfiguration.java index 00e43462b7a239d61d95aa1b6582dc46dc9f3df4..525d9831dab33b881065fb3ba56925dd4279dec5 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/configuration/StreamPluginAutoConfiguration.java +++ b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/configuration/StreamPluginAutoConfiguration.java @@ -31,9 +31,11 @@ import org.dromara.streamquery.stream.plugin.mybatisplus.engine.handler.JsonPost import org.dromara.streamquery.stream.plugin.mybatisplus.engine.mapper.DynamicMapperHandler; import org.dromara.streamquery.stream.plugin.mybatisplus.engine.methods.SaveOneSql; import org.dromara.streamquery.stream.plugin.mybatisplus.engine.methods.UpdateOneSql; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; import java.util.List; @@ -47,6 +49,19 @@ public class StreamPluginAutoConfiguration { private static final String CURRENT_NAMESPACE = LambdaHelper.getPropertyName(TableInfo::getCurrentNamespace); + private static boolean safeModeEnabled; + + @Autowired + public void setEnvironment(Environment environment) { + safeModeEnabled = + Boolean.parseBoolean( + environment.getProperty("stream-query.mybatis-plus.safe-mode", "false")); + } + + public static boolean isSafeModeEnabled() { + return safeModeEnabled; + } + /** * defaultSqlInjector. * diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/interceptor/SqTenantLineInnerInterceptor.java b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/interceptor/SqTenantLineInnerInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..8faa139fcdca658768d971360f24f9fe21e6b349 --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/interceptor/SqTenantLineInnerInterceptor.java @@ -0,0 +1,59 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.engine.interceptor; + +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.insert.Insert; +import org.dromara.streamquery.stream.plugin.mybatisplus.engine.utils.SqlInjectionUtilSq; + +/** + * @author Cason + * @since 2023-06-23 21:36 + */ +public class SqTenantLineInnerInterceptor extends TenantLineInnerInterceptor { + public SqTenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) { + super(tenantLineHandler); + } + + @Override + protected void processInsert(Insert insert, int index, String sql, Object obj) { + checkTenantId(); + super.processInsert(insert, index, sql, obj); + } + + @Override + public Expression buildTableExpression(Table table, Expression where, String whereSegment) { + checkTenantId(); + return super.buildTableExpression(table, where, whereSegment); + } + + private void checkTenantId() { + Expression tenantId = this.getTenantLineHandler().getTenantId(); + if (tenantId instanceof StringValue) { + StringValue stringValue = (StringValue) tenantId; + String tenantIdStr = stringValue.getValue(); + if (SqlInjectionUtilSq.check(tenantIdStr)) { + throw new IllegalArgumentException( + "SQL Injection attempt detected in 'TenantLineInnerInterceptor'"); + } + } + } +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/utils/SqlInjectionUtilSq.java b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/utils/SqlInjectionUtilSq.java new file mode 100644 index 0000000000000000000000000000000000000000..6922dd31e01513a92a730105f844adcfd9c0959c --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/main/java/org/dromara/streamquery/stream/plugin/mybatisplus/engine/utils/SqlInjectionUtilSq.java @@ -0,0 +1,45 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.engine.utils; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * @author Cason + * @since 2023-06-29 + */ +public class SqlInjectionUtilSq { + private static final Pattern SQL_SYNTAX_PATTERN = + Pattern.compile( + "(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)", + Pattern.CASE_INSENSITIVE); + private static final Pattern SQL_COMMENT_PATTERN = + Pattern.compile("(or|union|--|#|/*|;)", Pattern.CASE_INSENSITIVE); + + public SqlInjectionUtilSq() {} + + public static boolean check(String value) { + Objects.requireNonNull(value); + return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find(); + } + + public static String removeEscapeCharacter(String text) { + Objects.nonNull(text); + return text.replaceAll("\"", "").replaceAll("'", ""); + } +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/JsonFieldHandlerTest.java b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/JsonFieldHandlerTest.java index 3071412fe62220ae30905729d8c566362d406896..3d828b568ea90da7b4b1852af650255fc336d759 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/JsonFieldHandlerTest.java +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/JsonFieldHandlerTest.java @@ -144,10 +144,9 @@ class JsonFieldHandlerTest { Database.saveFewSql(Lists.of(user1, user2, user3)); QueryCondition wrapper = - (QueryCondition) QueryCondition.query(UserInfoWithJsonName.class) - .in(UserInfoWithJsonName::getName, Lists.of(name1, name3)) - .or(i -> i.eq(UserInfoWithJsonName::getName, user2.getName())); + .in(UserInfoWithJsonName::getName, Lists.of(name1, name3)) + .or(i -> i.eq(UserInfoWithJsonName::getName, user2.getName())); val list = Database.list(wrapper); @@ -167,6 +166,42 @@ class JsonFieldHandlerTest { "Returned users should not contain the second username"); } + @Test + void andTest() { + Name name1 = new Name(); + name1.setUsername("Cason"); + name1.setNickname("JAY"); + + Name name2 = new Name(); + name2.setUsername("Alice"); + name2.setNickname("AL"); + + Name name3 = new Name(); + name3.setUsername("Bob"); + name3.setNickname("BB"); + + UserInfoWithJsonName user1 = new UserInfoWithJsonName(); + user1.setName(name1); + + UserInfoWithJsonName user2 = new UserInfoWithJsonName(); + user2.setName(name2); + + UserInfoWithJsonName user3 = new UserInfoWithJsonName(); + user3.setName(name3); + + Database.saveFewSql(Lists.of(user1, user2, user3)); + + QueryCondition wrapper = + QueryCondition.query(UserInfoWithJsonName.class) + .eq(UserInfoWithJsonName::getId, 1L) + .and(i -> i.eq(UserInfoWithJsonName::getName, user2.getName())); + + val list = Database.list(wrapper); + + assertEquals(0, list.size(), "Query should return exactly zero result"); + + } + @Test void InTest() { Name name1 = new Name(); @@ -313,13 +348,8 @@ class JsonFieldHandlerTest { } @Data - static class Name implements Serializable, Comparable { + static class Name implements Serializable { private String username; private String nickname; - - @Override - public int compareTo(Name o) { - return username.compareTo(o.username); - } } } diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductCategory.java b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..fd4fe7a32f38ff4c82494e439216920db83f3211 --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductCategory.java @@ -0,0 +1,41 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import org.dromara.streamquery.stream.plugin.mybatisplus.issue.gitee.issue17BSNV.Table; + +/** + * @author Cason + * @since 2023-06-27 + */ +@Data +@Table("product_category") +public class ProductCategory { + private static final long serialVersionUID = -7219188882388819210L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private Long productId; + + private Long categoryId; + + private String tenantId; +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductInfo.java b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..f0ab2f898179aed6ab9bf08db6153b666333f8ba --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/pojo/po/ProductInfo.java @@ -0,0 +1,44 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import org.dromara.streamquery.stream.plugin.mybatisplus.engine.mapper.IGenerateMapper; +import org.dromara.streamquery.stream.plugin.mybatisplus.issue.gitee.issue17BSNV.Table; + +import java.math.BigDecimal; + +/** + * @author Cason + * @since 2023-06-27 20:13 + */ +@Data +@Table("product_info") +public class ProductInfo implements IGenerateMapper { + private static final long serialVersionUID = -7219188882388819210L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private String productName; + + private BigDecimal productPrice; + + private String tenantId; +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionTenantTest.java b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionTenantTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3dc7ea134a86ac3a4378cc18818a00c89cd69fb5 --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionTenantTest.java @@ -0,0 +1,104 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.safe; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.test.autoconfigure.MybatisPlusTest; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import org.apache.ibatis.exceptions.PersistenceException; +import org.dromara.streamquery.stream.plugin.mybatisplus.Database; +import org.dromara.streamquery.stream.plugin.mybatisplus.MybatisPlusTestApplication; +import org.dromara.streamquery.stream.plugin.mybatisplus.QueryCondition; +import org.dromara.streamquery.stream.plugin.mybatisplus.engine.interceptor.SqTenantLineInnerInterceptor; +import org.dromara.streamquery.stream.plugin.mybatisplus.pojo.po.ProductInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Cason + * @since 2023-06-27 + */ +@MybatisPlusTest +@ContextConfiguration( + classes = {MybatisPlusTestApplication.class, SqlInjectionTenantTest.TenantPluginConfig.class}) +@OverrideAutoConfiguration(enabled = true) +class SqlInjectionTenantTest { + + static class TenantPluginConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor( + new SqTenantLineInnerInterceptor( + new TenantLineHandler() { + @Override + public Expression getTenantId() { + return new StringValue("' or 1=1 and '123'='123"); + } + + @Override + public String getTenantIdColumn() { + return "tenant_id"; + } + })); + return interceptor; + } + } + + @Test + void queryTest() { + QueryCondition wrapper = + QueryCondition.query(ProductInfo.class).eq(ProductInfo::getId, 1L); + Throwable exception = + Assertions.assertThrows(PersistenceException.class, () -> Database.list(wrapper)); + Assertions.assertTrue(exception.getCause() instanceof IllegalArgumentException); + Assertions.assertEquals( + "SQL Injection attempt detected in 'TenantLineInnerInterceptor'", + exception.getCause().getMessage()); + } + + @Test + void insertTest() { + ProductInfo product = new ProductInfo(); + product.setId(2L); + product.setProductName("Product 2"); + Throwable exception = + Assertions.assertThrows(PersistenceException.class, () -> Database.save(product)); + Assertions.assertTrue(exception.getCause() instanceof IllegalArgumentException); + Assertions.assertEquals( + "SQL Injection attempt detected in 'TenantLineInnerInterceptor'", + exception.getCause().getMessage()); + } + + @Test + void updateTest() { + ProductInfo product = new ProductInfo(); + product.setId(1L); + product.setProductName("Updated Product 1"); + Throwable exception = + Assertions.assertThrows(PersistenceException.class, () -> Database.updateById(product)); + Assertions.assertTrue(exception.getCause() instanceof IllegalArgumentException); + Assertions.assertEquals( + "SQL Injection attempt detected in 'TenantLineInnerInterceptor'", + exception.getCause().getMessage()); + } +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionWrapperTest.java b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionWrapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c320d618612d33bcd20621133e610512b3323d67 --- /dev/null +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/java/org/dromara/streamquery/stream/plugin/mybatisplus/safe/SqlInjectionWrapperTest.java @@ -0,0 +1,53 @@ +/* + * 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.dromara.streamquery.stream.plugin.mybatisplus.safe; + +import com.baomidou.mybatisplus.test.autoconfigure.MybatisPlusTest; +import org.dromara.streamquery.stream.plugin.mybatisplus.QueryCondition; +import org.dromara.streamquery.stream.plugin.mybatisplus.pojo.po.UserInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Cason + * @since 2023-06-27 + */ +@MybatisPlusTest +class SqlInjectionWrapperTest { + + @Test + void applyTest() { + String unsafeSql = "1 = 1) OR 1 = 1 --"; + Throwable exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> QueryCondition.query(UserInfo.class).apply(unsafeSql)); + + Assertions.assertEquals("SQL Injection attempt detected in 'apply'", exception.getMessage()); + } + + @Test + void havingApply() { + String unsafeSql = "1 = 1) OR 1 = 1 --"; + Throwable exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> QueryCondition.query(UserInfo.class).having(unsafeSql)); + + Assertions.assertEquals("SQL Injection attempt detected in 'having'", exception.getMessage()); + } +} diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/application.yml b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/application.yml index 6aa4181246b7033e0e1026a6507153cce97f7621..c775fb96701202bf7668b57023f813dbdbe07e8c 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/application.yml +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/application.yml @@ -6,6 +6,7 @@ logging: stream-query: mybatis-plus: + safe-mode: on dynamic-mapper: base-packages: - org.dromara.streamquery.stream.plugin.mybatisplus.pojo.po diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/data.sql b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/data.sql index b8cf986e49edab00a28a2713f7de5f3cee484987..427d0ed4f8e9ef43933e2404e7e7b2ebf036696a 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/data.sql +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/data.sql @@ -30,3 +30,26 @@ INSERT INTO role_info (id, role_name) VALUES ('1', 'admin'), ('2', 'user'), ('3', 'guest'); + +DELETE FROM product_info; + +INSERT INTO product_info (product_name, product_price, tenant_id) +VALUES ('Apple iPhone 13', 699, '1'), + ('Samsung Galaxy S21', 799, '1'), + ('OnePlus 9 Pro', 969, '1'), + ('Google Pixel 6', 599, '2'), + ('Sony Xperia 1 III', 1199, '1'); + +DELETE FROM product_category; + +INSERT INTO product_category (product_id, category_id, tenant_id) +VALUES (1, 1, '1'), + (1, 2, '1'), + (2, 1, '1'), + (2, 3, '1'), + (3, 1, '1'), + (4, 1, '2'), + (4, 2, '2'), + (5, 1, '1'), + (5, 2, '1'), + (5, 3, '1'); \ No newline at end of file diff --git a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/schema.sql b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/schema.sql index 3ab26d116191095e6a2d2bc6f520077e999928f6..58b89d7364a0c3a01623e909484e7aa7eb40d4f8 100644 --- a/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/schema.sql +++ b/stream-plugin/stream-plugin-mybatis-plus/src/test/resources/schema.sql @@ -25,4 +25,24 @@ create table if not exists role_info id VARCHAR(30) NOT NULL, role_name VARCHAR(30) DEFAULT NULL, PRIMARY KEY (id) -); \ No newline at end of file +); + +drop table if exists product_info; +create table if not exists product_info +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + product_name VARCHAR(50) DEFAULT NULL, + product_price FLOAT DEFAULT NULL, + tenant_id VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (id) + ); + +drop table if exists product_category; +create table if not exists product_category +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + product_id BIGINT DEFAULT NULL, + category_id BIGINT DEFAULT NULL, + tenant_id VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (id) + ); \ No newline at end of file