From e210b1608a996b26ffcccce23dfb08e3d8fb9819 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 14 Aug 2017 16:09:35 +0800 Subject: [PATCH] improved wallfilter. support update column check. --- .../druid/sql/ast/expr/SQLBinaryOpExpr.java | 18 +++ .../druid/sql/ast/expr/SQLIdentifierExpr.java | 4 + .../druid/sql/ast/expr/SQLPropertyExpr.java | 4 + .../sql/ast/statement/SQLUpdateSetItem.java | 10 ++ .../sql/ast/statement/SQLUpdateStatement.java | 10 +- .../druid/sql/visitor/SchemaStatVisitor.java | 7 +- .../alibaba/druid/wall/WallCheckResult.java | 11 +- .../com/alibaba/druid/wall/WallConfig.java | 55 +++++++- .../com/alibaba/druid/wall/WallContext.java | 10 ++ .../com/alibaba/druid/wall/WallFilter.java | 127 ++++++++++++++---- .../com/alibaba/druid/wall/WallProvider.java | 2 + .../druid/wall/WallUpdateCheckHandler.java | 25 ++++ .../druid/wall/WallUpdateCheckItem.java | 37 +++++ .../com/alibaba/druid/wall/WallVisitor.java | 4 + .../druid/wall/spi/DB2WallVisitor.java | 17 ++- .../druid/wall/spi/MySqlWallVisitor.java | 20 ++- .../druid/wall/spi/OracleWallVisitor.java | 17 ++- .../alibaba/druid/wall/spi/PGWallVisitor.java | 17 ++- .../druid/wall/spi/SQLServerWallVisitor.java | 17 ++- .../druid/wall/spi/WallVisitorUtils.java | 75 ++++++++++- .../druid/wall/violation/ErrorCode.java | 2 + .../bvt/filter/wall/WallUpdateCheckTest.java | 34 +++++ 22 files changed, 459 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/alibaba/druid/wall/WallUpdateCheckHandler.java create mode 100644 src/main/java/com/alibaba/druid/wall/WallUpdateCheckItem.java create mode 100644 src/test/java/com/alibaba/druid/bvt/filter/wall/WallUpdateCheckTest.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLBinaryOpExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLBinaryOpExpr.java index 774e039dab..88f0c7a404 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLBinaryOpExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLBinaryOpExpr.java @@ -490,4 +490,22 @@ public class SQLBinaryOpExpr extends SQLExprImpl implements SQLReplaceable, Seri return false; } + + public boolean conditionContainsColumn(String column) { + if (left == null || right == null) { + return false; + } + + if (left instanceof SQLIdentifierExpr) { + if (((SQLIdentifierExpr) left).nameEquals(column)) { + return true; + } + } else if (right instanceof SQLIdentifierExpr) { + if (((SQLIdentifierExpr) right).nameEquals(column)) { + return true; + } + } + + return false; + } } diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLIdentifierExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLIdentifierExpr.java index 4308fad287..e8028f8abf 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLIdentifierExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLIdentifierExpr.java @@ -178,4 +178,8 @@ public class SQLIdentifierExpr extends SQLExprImpl implements SQLName { return null; } + + public boolean nameEquals(String name) { + return SQLUtils.nameEquals(this.name, name); + } } diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLPropertyExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLPropertyExpr.java index 1871c6313c..9d04a6b016 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLPropertyExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLPropertyExpr.java @@ -199,4 +199,8 @@ public class SQLPropertyExpr extends SQLExprImpl implements SQLName { return null; } + + public boolean nameEquals(String name) { + return SQLUtils.nameEquals(this.name, name); + } } diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateSetItem.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateSetItem.java index 9751ce6390..6dba1e6a9b 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateSetItem.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateSetItem.java @@ -17,6 +17,8 @@ package com.alibaba.druid.sql.ast.statement; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLObjectImpl; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.visitor.SQLASTVisitor; public class SQLUpdateSetItem extends SQLObjectImpl { @@ -60,4 +62,12 @@ public class SQLUpdateSetItem extends SQLObjectImpl { visitor.endVisit(this); } + public boolean columnMatch(String column) { + if (this.column instanceof SQLIdentifierExpr) { + return ((SQLIdentifierExpr) this.column).nameEquals(column); + } else if (this.column instanceof SQLPropertyExpr) { + ((SQLPropertyExpr) this.column).nameEquals(column); + } + return false; + } } diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateStatement.java index 96bf774ef8..78f6cdc503 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateStatement.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLUpdateStatement.java @@ -58,8 +58,14 @@ public class SQLUpdateStatement extends SQLStatementImpl implements SQLReplaceab public SQLName getTableName() { if (tableSource instanceof SQLExprTableSource) { - SQLExprTableSource exprTableSource = (SQLExprTableSource) tableSource; - return (SQLName) exprTableSource.getExpr(); + return ((SQLExprTableSource) tableSource).getName(); + } + + if (tableSource instanceof SQLJoinTableSource) { + SQLTableSource left = ((SQLJoinTableSource) tableSource).getLeft(); + if (left instanceof SQLExprTableSource) { + return ((SQLExprTableSource) left).getName(); + } } return null; } diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java index cf1b19157e..3a29ec42dc 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java @@ -1395,8 +1395,9 @@ public class SchemaStatVisitor extends SQLASTVisitorAdapter { setMode(x, Mode.Update); - SQLName identName = x.getTableName(); - if (identName != null) { + SQLTableSource tableSource = x.getTableSource(); + if (tableSource instanceof SQLExprTableSource) { + SQLName identName = ((SQLExprTableSource) tableSource).getName(); String ident = identName.toString(); setCurrentTable(ident); @@ -1406,7 +1407,7 @@ public class SchemaStatVisitor extends SQLASTVisitorAdapter { Map aliasMap = getAliasMap(); putAliasMap(aliasMap, ident, ident); } else { - x.getTableSource().accept(this); + tableSource.accept(this); } accept(x.getFrom()); diff --git a/src/main/java/com/alibaba/druid/wall/WallCheckResult.java b/src/main/java/com/alibaba/druid/wall/WallCheckResult.java index 4147828aae..0a302815e9 100644 --- a/src/main/java/com/alibaba/druid/wall/WallCheckResult.java +++ b/src/main/java/com/alibaba/druid/wall/WallCheckResult.java @@ -22,7 +22,6 @@ import java.util.Map; import com.alibaba.druid.sql.ast.SQLStatement; public class WallCheckResult { - private final List statementList; private final Map tableStats; @@ -36,6 +35,8 @@ public class WallCheckResult { private String sql; + private List updateCheckItems; + public WallCheckResult(){ this(null); } @@ -103,4 +104,12 @@ public class WallCheckResult { public WallSqlStat getSqlStat() { return sqlStat; } + + public List getUpdateCheckItems() { + return updateCheckItems; + } + + public void setUpdateCheckItems(List updateCheckItems) { + this.updateCheckItems = updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/WallConfig.java b/src/main/java/com/alibaba/druid/wall/WallConfig.java index 85f9ab0510..23897d026c 100644 --- a/src/main/java/com/alibaba/druid/wall/WallConfig.java +++ b/src/main/java/com/alibaba/druid/wall/WallConfig.java @@ -15,10 +15,10 @@ */ package com.alibaba.druid.wall; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.wall.spi.WallVisitorUtils; -import java.util.Properties; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentSkipListSet; import static com.alibaba.druid.util.Utils.getBoolean; @@ -129,6 +129,9 @@ public class WallConfig implements WallConfigMBean { private int selectLimit = -1; + protected Map> updateCheckColumns = new HashMap>(); + protected WallUpdateCheckHandler updateCheckHandler; + public WallConfig(){ this.configFromProperties(System.getProperties()); } @@ -852,5 +855,53 @@ public class WallConfig implements WallConfigMBean { this.setSelectLimit(propertyValue); } } + { + String propertyValue = properties.getProperty("druid.wall.updateCheckColumns"); + if (propertyValue != null) { + String[] items = propertyValue.split(","); + for (String item : items) { + addUpdateCheckCoumns(item); + } + } + } + } + + public void addUpdateCheckCoumns(String columnInfo) { + String[] items = columnInfo.split("\\."); + if (items.length != 2) { + return; + } + String table = SQLUtils.normalize(items[0]).toLowerCase(); + String column = SQLUtils.normalize(items[1]).toLowerCase(); + Set columns = this.updateCheckColumns.get(table); + if (columns == null) { + columns = new LinkedHashSet(); + updateCheckColumns.put(table, columns); + } + columns.add(column); + } + + public boolean isUpdateCheckTable(String tableName) { + if (updateCheckColumns.isEmpty()) { + return false; + } + String tableNameLower = SQLUtils.normalize(tableName).toLowerCase(); + return updateCheckColumns.containsKey(tableNameLower); + } + + public Set getUpdateCheckTable(String tableName) { + if (updateCheckColumns.isEmpty()) { + return null; + } + String tableNameLower = SQLUtils.normalize(tableName).toLowerCase(); + return updateCheckColumns.get(tableNameLower); + } + + public WallUpdateCheckHandler getUpdateCheckHandler() { + return updateCheckHandler; + } + + public void setUpdateCheckHandler(WallUpdateCheckHandler updateCheckHandler) { + this.updateCheckHandler = updateCheckHandler; } } diff --git a/src/main/java/com/alibaba/druid/wall/WallContext.java b/src/main/java/com/alibaba/druid/wall/WallContext.java index 61e52656f9..0316af4c1b 100644 --- a/src/main/java/com/alibaba/druid/wall/WallContext.java +++ b/src/main/java/com/alibaba/druid/wall/WallContext.java @@ -16,6 +16,7 @@ package com.alibaba.druid.wall; import java.util.HashMap; +import java.util.List; import java.util.Map; public class WallContext { @@ -33,6 +34,8 @@ public class WallContext { private int deleteNoneConditionWarnings = 0; private int likeNumberWarnings = 0; + private List wallUpdateCheckItems; + public WallContext(String dbType){ this.dbType = dbType; } @@ -186,4 +189,11 @@ public class WallContext { this.deleteNoneConditionWarnings++; } + public List getWallUpdateCheckItems() { + return wallUpdateCheckItems; + } + + public void setWallUpdateCheckItems(List wallUpdateCheckItems) { + this.wallUpdateCheckItems = wallUpdateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/WallFilter.java b/src/main/java/com/alibaba/druid/wall/WallFilter.java index fa3745579d..5496c7f03c 100644 --- a/src/main/java/com/alibaba/druid/wall/WallFilter.java +++ b/src/main/java/com/alibaba/druid/wall/WallFilter.java @@ -17,13 +17,10 @@ package com.alibaba.druid.wall; import com.alibaba.druid.filter.FilterAdapter; import com.alibaba.druid.filter.FilterChain; -import com.alibaba.druid.proxy.jdbc.CallableStatementProxy; -import com.alibaba.druid.proxy.jdbc.ConnectionProxy; -import com.alibaba.druid.proxy.jdbc.DataSourceProxy; -import com.alibaba.druid.proxy.jdbc.PreparedStatementProxy; -import com.alibaba.druid.proxy.jdbc.ResultSetMetaDataProxy; -import com.alibaba.druid.proxy.jdbc.ResultSetProxy; -import com.alibaba.druid.proxy.jdbc.StatementProxy; +import com.alibaba.druid.proxy.jdbc.*; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import com.alibaba.druid.util.JdbcUtils; @@ -79,6 +76,7 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { private volatile boolean throwException = true; public final static String ATTR_SQL_STAT = "wall.sqlStat"; + public final static String ATTR_UPDATE_CHECK_ITEMS = "wall.updateCheckItems"; public WallFilter(){ configFromProperties(System.getProperties()); @@ -248,9 +246,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql); setSqlStatAttribute(stmt); return stmt; @@ -263,9 +263,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql, int autoGeneratedKeys) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql, autoGeneratedKeys); setSqlStatAttribute(stmt); return stmt; @@ -279,9 +281,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { String sql, int resultSetType, int resultSetConcurrency) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql, resultSetType, resultSetConcurrency); setSqlStatAttribute(stmt); @@ -296,9 +300,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql, resultSetType, resultSetConcurrency, resultSetHoldability); setSqlStatAttribute(stmt); @@ -312,9 +318,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql, int[] columnIndexes) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql, columnIndexes); setSqlStatAttribute(stmt); return stmt; @@ -327,9 +335,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql, String[] columnNames) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); PreparedStatementProxy stmt = chain.connection_prepareStatement(connection, sql, columnNames); setSqlStatAttribute(stmt); return stmt; @@ -342,9 +352,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { public CallableStatementProxy connection_prepareCall(FilterChain chain, ConnectionProxy connection, String sql) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); CallableStatementProxy stmt = chain.connection_prepareCall(connection, sql); setSqlStatAttribute(stmt); return stmt; @@ -358,9 +370,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { int resultSetType, int resultSetConcurrency) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); CallableStatementProxy stmt = chain.connection_prepareCall(connection, sql, resultSetType, resultSetConcurrency); setSqlStatAttribute(stmt); @@ -375,9 +389,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { String dbType = connection.getDirectDataSource().getDbType(); - WallContext.create(dbType); + WallContext context = WallContext.create(dbType); try { - sql = check(sql); + WallCheckResult result = checkInternal(sql); + context.setWallUpdateCheckItems(result.getUpdateCheckItems()); + sql = result.getSql(); CallableStatementProxy stmt = chain.connection_prepareCall(connection, sql, resultSetType, resultSetConcurrency, resultSetHoldability); setSqlStatAttribute(stmt); @@ -598,6 +614,8 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { @Override public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException { try { + wallUpdateCheck(statement); + boolean firstResult = chain.preparedStatement_execute(statement); if (!firstResult) { @@ -631,6 +649,8 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { @Override public int preparedStatement_executeUpdate(FilterChain chain, PreparedStatementProxy statement) throws SQLException { try { + wallUpdateCheck(statement); + int updateCount = chain.preparedStatement_executeUpdate(statement); WallSqlStat sqlStat = (WallSqlStat) statement.getAttribute(ATTR_SQL_STAT); if (sqlStat != null) { @@ -643,6 +663,49 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { } } + private void wallUpdateCheck(PreparedStatementProxy statement) throws SQLException { + Map parameterMap = statement.getParameters(); + List wallUpdateCheckItems = (List) statement.getAttribute(ATTR_UPDATE_CHECK_ITEMS); + if (wallUpdateCheckItems != null) { + for (WallUpdateCheckItem item : wallUpdateCheckItems) { + Object setValue; + if (item.value instanceof SQLValuableExpr) { + setValue = ((SQLValuableExpr) item.value).getValue(); + } else { + int index = ((SQLVariantRefExpr) item.value).getIndex(); + JdbcParameter parameter = parameterMap.get(index); + if (parameter != null) { + setValue = parameter.getValue(); + } else { + setValue = null; + } + } + + List filtersValues = new ArrayList(item.filterValues.size()); + for (SQLExpr filterValueExpr : item.filterValues) { + Object filterValue; + if (filterValueExpr instanceof SQLValuableExpr) { + filterValue = ((SQLValuableExpr) filterValueExpr).getValue(); + } else { + int index = ((SQLVariantRefExpr) filterValueExpr).getIndex(); + JdbcParameter parameter = parameterMap.get(index); + if (parameter != null) { + filterValue = parameter.getValue(); + } else { + filterValue = null; + } + } + filtersValues.add(filterValue); + } + + boolean valid = config.updateCheckHandler.check(item.tableName, item.columnName, setValue, filtersValues); + if (!valid) { + throw new SQLException("wall update check failed."); + } + } + } + } + @Override public ResultSetProxy statement_getResultSet(FilterChain chain, StatementProxy statement) throws SQLException { ResultSetProxy resultSetProxy = chain.statement_getResultSet(statement); @@ -669,6 +732,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { } stmt.putAttribute(ATTR_SQL_STAT, sqlStat); + + List wallUpdateCheckItems = context.getWallUpdateCheckItems(); + if (wallUpdateCheckItems != null) { + stmt.putAttribute(ATTR_UPDATE_CHECK_ITEMS, wallUpdateCheckItems); + } } public void statExecuteUpdate(int updateCount) { @@ -709,6 +777,11 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { } public String check(String sql) throws SQLException { + return checkInternal(sql) + .getSql(); + } + + private WallCheckResult checkInternal(String sql) throws SQLException { WallCheckResult checkResult = provider.check(sql); List violations = checkResult.getViolations(); @@ -722,14 +795,14 @@ public class WallFilter extends FilterAdapter implements WallFilterMBean { if (violations.get(0) instanceof SyntaxErrorViolation) { SyntaxErrorViolation violation = (SyntaxErrorViolation) violations.get(0); throw new SQLException("sql injection violation, " + firstViolation.getMessage() + " : " + sql, - violation.getException()); + violation.getException()); } else { throw new SQLException("sql injection violation, " + firstViolation.getMessage() + " : " + sql); } } } - return checkResult.getSql(); + return checkResult; } @Override diff --git a/src/main/java/com/alibaba/druid/wall/WallProvider.java b/src/main/java/com/alibaba/druid/wall/WallProvider.java index cd4d8b920b..e0f383f73a 100644 --- a/src/main/java/com/alibaba/druid/wall/WallProvider.java +++ b/src/main/java/com/alibaba/druid/wall/WallProvider.java @@ -704,6 +704,8 @@ public abstract class WallProvider { } result.setSql(resultSql); + result.setUpdateCheckItems(visitor.getUpdateCheckItems()); + return result; } diff --git a/src/main/java/com/alibaba/druid/wall/WallUpdateCheckHandler.java b/src/main/java/com/alibaba/druid/wall/WallUpdateCheckHandler.java new file mode 100644 index 0000000000..42ab7c5da5 --- /dev/null +++ b/src/main/java/com/alibaba/druid/wall/WallUpdateCheckHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2017 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.wall; + +import java.util.List; + +/** + * Created by wenshao on 13/08/2017. + */ +public interface WallUpdateCheckHandler { + boolean check(String table, String column, Object setValue, List filterValues); +} diff --git a/src/main/java/com/alibaba/druid/wall/WallUpdateCheckItem.java b/src/main/java/com/alibaba/druid/wall/WallUpdateCheckItem.java new file mode 100644 index 0000000000..efbb358a18 --- /dev/null +++ b/src/main/java/com/alibaba/druid/wall/WallUpdateCheckItem.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2017 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.wall; + +import com.alibaba.druid.sql.ast.SQLExpr; + +import java.util.List; + +/** + * Created by wenshao on 14/08/2017. + */ +public class WallUpdateCheckItem { + public final String tableName; + public final String columnName; + public final SQLExpr value; + public final List filterValues; + + public WallUpdateCheckItem(String tableName, String columnName, SQLExpr value, List filterValues) { + this.tableName = tableName; + this.columnName = columnName; + this.value = value; + this.filterValues = filterValues; + } +} diff --git a/src/main/java/com/alibaba/druid/wall/WallVisitor.java b/src/main/java/com/alibaba/druid/wall/WallVisitor.java index f4829cc0e0..b34eb9d60d 100644 --- a/src/main/java/com/alibaba/druid/wall/WallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/WallVisitor.java @@ -43,4 +43,8 @@ public interface WallVisitor extends SQLASTVisitor { boolean isSqlEndOfComment(); void setSqlEndOfComment(boolean sqlEndOfComment); + + void addWallUpdateCheckItem(WallUpdateCheckItem item); + + List getUpdateCheckItems(); } diff --git a/src/main/java/com/alibaba/druid/wall/spi/DB2WallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/DB2WallVisitor.java index b32ada4ef3..5ab0eb3401 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/DB2WallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/DB2WallVisitor.java @@ -28,10 +28,7 @@ import com.alibaba.druid.sql.ast.statement.*; import com.alibaba.druid.sql.dialect.db2.ast.stmt.DB2SelectQueryBlock; import com.alibaba.druid.sql.dialect.db2.visitor.DB2ASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants; -import com.alibaba.druid.wall.Violation; -import com.alibaba.druid.wall.WallConfig; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallVisitor; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -45,6 +42,7 @@ public class DB2WallVisitor extends DB2ASTVisitorAdapter implements WallVisitor private final List violations = new ArrayList(); private boolean sqlModified = false; private boolean sqlEndOfComment = false; + private List updateCheckItems; public DB2WallVisitor(WallProvider provider){ this.config = provider.getConfig(); @@ -279,4 +277,15 @@ public class DB2WallVisitor extends DB2ASTVisitorAdapter implements WallVisitor public void setSqlEndOfComment(boolean sqlEndOfComment) { this.sqlEndOfComment = sqlEndOfComment; } + + public void addWallUpdateCheckItem(WallUpdateCheckItem item) { + if (updateCheckItems == null) { + updateCheckItems = new ArrayList(); + } + updateCheckItems.add(item); + } + + public List getUpdateCheckItems() { + return updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java index 8a6e68d065..ee8ccf1b23 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java @@ -42,12 +42,7 @@ import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitor; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants; -import com.alibaba.druid.wall.Violation; -import com.alibaba.druid.wall.WallConfig; -import com.alibaba.druid.wall.WallContext; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallSqlTableStat; -import com.alibaba.druid.wall.WallVisitor; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.spi.WallVisitorUtils.WallTopStatementContext; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -59,6 +54,7 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi private final List violations = new ArrayList(); private boolean sqlModified = false; private boolean sqlEndOfComment = false; + private List updateCheckItems; public MySqlWallVisitor(WallProvider provider){ this.config = provider.getConfig(); @@ -449,5 +445,15 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi public void setSqlEndOfComment(boolean sqlEndOfComment) { this.sqlEndOfComment = sqlEndOfComment; } - + + public void addWallUpdateCheckItem(WallUpdateCheckItem item) { + if (updateCheckItems == null) { + updateCheckItems = new ArrayList(); + } + updateCheckItems.add(item); + } + + public List getUpdateCheckItems() { + return updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java index 72f1d8a93f..57ef0d646f 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java @@ -38,10 +38,7 @@ import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference; import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants; -import com.alibaba.druid.wall.Violation; -import com.alibaba.druid.wall.WallConfig; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallVisitor; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -52,6 +49,7 @@ public class OracleWallVisitor extends OracleASTVisitorAdapter implements WallVi private final List violations = new ArrayList(); private boolean sqlModified = false; private boolean sqlEndOfComment = false; + private List updateCheckItems; public OracleWallVisitor(WallProvider provider){ this.config = provider.getConfig(); @@ -345,4 +343,15 @@ public class OracleWallVisitor extends OracleASTVisitorAdapter implements WallVi public void setSqlEndOfComment(boolean sqlEndOfComment) { this.sqlEndOfComment = sqlEndOfComment; } + + public void addWallUpdateCheckItem(WallUpdateCheckItem item) { + if (updateCheckItems == null) { + updateCheckItems = new ArrayList(); + } + updateCheckItems.add(item); + } + + public List getUpdateCheckItems() { + return updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/spi/PGWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/PGWallVisitor.java index e46b0c2b01..559e2fdb65 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/PGWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/PGWallVisitor.java @@ -29,10 +29,7 @@ import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectQueryBlock; import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectStatement; import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants; -import com.alibaba.druid.wall.Violation; -import com.alibaba.druid.wall.WallConfig; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallVisitor; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -46,6 +43,7 @@ public class PGWallVisitor extends PGASTVisitorAdapter implements WallVisitor { private final List violations = new ArrayList(); private boolean sqlModified = false; private boolean sqlEndOfComment = false; + private List updateCheckItems; public PGWallVisitor(WallProvider provider){ this.config = provider.getConfig(); @@ -302,4 +300,15 @@ public class PGWallVisitor extends PGASTVisitorAdapter implements WallVisitor { public void setSqlEndOfComment(boolean sqlEndOfComment) { this.sqlEndOfComment = sqlEndOfComment; } + + public void addWallUpdateCheckItem(WallUpdateCheckItem item) { + if (updateCheckItems == null) { + updateCheckItems = new ArrayList(); + } + updateCheckItems.add(item); + } + + public List getUpdateCheckItems() { + return updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java index c1539b85c9..5bfbc2fa60 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java @@ -33,10 +33,7 @@ import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerInsertStatement import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor; import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants; -import com.alibaba.druid.wall.Violation; -import com.alibaba.druid.wall.WallConfig; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallVisitor; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.spi.WallVisitorUtils.WallTopStatementContext; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -51,6 +48,7 @@ public class SQLServerWallVisitor extends SQLServerASTVisitorAdapter implements private final List violations = new ArrayList(); private boolean sqlModified = false; private boolean sqlEndOfComment = false; + private List updateCheckItems; public SQLServerWallVisitor(WallProvider provider){ this.config = provider.getConfig(); @@ -338,4 +336,15 @@ public class SQLServerWallVisitor extends SQLServerASTVisitorAdapter implements public void setSqlEndOfComment(boolean sqlEndOfComment) { this.sqlEndOfComment = sqlEndOfComment; } + + public void addWallUpdateCheckItem(WallUpdateCheckItem item) { + if (updateCheckItems == null) { + updateCheckItems = new ArrayList(); + } + updateCheckItems.add(item); + } + + public List getUpdateCheckItems() { + return updateCheckItems; + } } diff --git a/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java b/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java index 2aa2018835..bc285057a0 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java +++ b/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java @@ -78,13 +78,9 @@ import com.alibaba.druid.support.logging.LogFactory; import com.alibaba.druid.util.JdbcUtils; import com.alibaba.druid.util.ServletPathMatcher; import com.alibaba.druid.util.StringUtils; -import com.alibaba.druid.wall.WallConfig; +import com.alibaba.druid.wall.*; import com.alibaba.druid.wall.WallConfig.TenantCallBack; import com.alibaba.druid.wall.WallConfig.TenantCallBack.StatementType; -import com.alibaba.druid.wall.WallContext; -import com.alibaba.druid.wall.WallProvider; -import com.alibaba.druid.wall.WallSqlTableStat; -import com.alibaba.druid.wall.WallVisitor; import com.alibaba.druid.wall.violation.ErrorCode; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; @@ -885,6 +881,10 @@ public class WallVisitorUtils { return; } + String tableName = x.getTableName().getSimpleName(); + Set updateCheckColumns = config.getUpdateCheckTable(tableName); + boolean isUpdateCheckTable = updateCheckColumns != null && !updateCheckColumns.isEmpty(); + SQLExpr where = x.getWhere(); if (where == null) { WallContext context = WallContext.current(); @@ -905,7 +905,6 @@ public class WallVisitorUtils { } } } else { - where.setParent(x); checkCondition(visitor, where); if (Boolean.TRUE == getConditionValue(visitor, where, config.isUpdateWhereAlayTrueCheck())) { @@ -913,6 +912,70 @@ public class WallVisitorUtils { addViolation(visitor, ErrorCode.ALWAYS_TRUE, "update alway true condition not allow", x); } } + + WallUpdateCheckHandler updateCheckHandler = config.getUpdateCheckHandler(); + if (isUpdateCheckTable && updateCheckHandler != null) { + String checkColumn = updateCheckColumns.iterator().next(); + + SQLExpr valueExpr = null; + for (SQLUpdateSetItem item : x.getItems()) { + if (item.columnMatch(checkColumn)) { + valueExpr = item.getValue(); + break; + } + } + + if (valueExpr != null) { + List conditions; + if (where instanceof SQLBinaryOpExpr) { + conditions = SQLBinaryOpExpr.split((SQLBinaryOpExpr) where, SQLBinaryOperator.BooleanAnd); + } else { + conditions = new ArrayList(); + conditions.add(where); + } + + List filterValueExprList = new ArrayList(); + for (SQLExpr condition : conditions) { + if (condition instanceof SQLBinaryOpExpr) { + SQLBinaryOpExpr binaryCondition = (SQLBinaryOpExpr) condition; + if (binaryCondition.getOperator() == SQLBinaryOperator.Equality + && binaryCondition.conditionContainsColumn(checkColumn)) { + SQLExpr left = binaryCondition.getLeft(); + SQLExpr right = binaryCondition.getRight(); + + if (left instanceof SQLValuableExpr || left instanceof SQLVariantRefExpr) { + filterValueExprList.add(left); + } else if (right instanceof SQLValuableExpr || right instanceof SQLVariantRefExpr) { + filterValueExprList.add(right); + } + } + } + } + + boolean allValue = valueExpr instanceof SQLValuableExpr; + if (allValue) { + for (SQLExpr filterValue : filterValueExprList) { + if (!(filterValue instanceof SQLValuableExpr)) { + allValue = false; + break; + } + } + } + + if (allValue) { + Object setValue = ((SQLValuableExpr) valueExpr).getValue(); + List filterValues = new ArrayList(filterValueExprList.size()); + for (SQLExpr expr : filterValueExprList) { + filterValues.add(((SQLValuableExpr) expr).getValue()); + } + boolean validate = updateCheckHandler.check(tableName, checkColumn, setValue, filterValues); + visitor.addViolation(new IllegalSQLObjectViolation(ErrorCode.UPDATE_CHECK_FAIL, "update check failed.", visitor.toSQL(x))); + } else { + visitor.addWallUpdateCheckItem(new WallUpdateCheckItem(tableName, checkColumn, valueExpr, filterValueExprList)); + } + //updateCheckHandler.check(tableName, checkColumn) + } + } } checkUpdateForMultiTenant(visitor, x); diff --git a/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java b/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java index 5d5446642b..5a6a850e5e 100644 --- a/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java +++ b/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java @@ -85,5 +85,7 @@ public interface ErrorCode { public final static int COMPOUND = 8000; + public final static int UPDATE_CHECK_FAIL = 9000; + public final static int OTHER = 9999; } diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/WallUpdateCheckTest.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallUpdateCheckTest.java new file mode 100644 index 0000000000..b324a62838 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallUpdateCheckTest.java @@ -0,0 +1,34 @@ +package com.alibaba.druid.bvt.filter.wall; + +import com.alibaba.druid.wall.WallCheckResult; +import com.alibaba.druid.wall.WallConfig; +import com.alibaba.druid.wall.WallUpdateCheckHandler; +import com.alibaba.druid.wall.spi.MySqlWallProvider; +import junit.framework.TestCase; + +import java.util.List; +import java.util.Properties; + +/** + * Created by wenshao on 13/08/2017. + */ +public class WallUpdateCheckTest extends TestCase { + private MySqlWallProvider wallProvider = new MySqlWallProvider(); + + protected void setUp() throws Exception { + Properties properties = new Properties(); + properties.put("druid.wall.updateCheckColumns", "t_orders.status"); + wallProvider.getConfig().configFromProperties(properties); + wallProvider.getConfig().setUpdateCheckHandler(new WallUpdateCheckHandler() { + + @Override + public boolean check(String table, String column, Object setValue, List filterValues) { + return false; + } + }); + } + public void test_update_check_handler() throws Exception { + WallCheckResult result = wallProvider.check("update t_orders set status = 3 where id = 3 and status = 4"); + assertTrue(result.getViolations().size() > 0); + } +}