diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/PatternMatcher.java b/fe/fe-core/src/main/java/org/apache/doris/common/PatternMatcher.java index a9eddc1b5e..5b5f3f0903 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/PatternMatcher.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/PatternMatcher.java @@ -20,27 +20,70 @@ package org.apache.doris.common; import com.google.common.base.Strings; import com.google.common.collect.Sets; +import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; // Wrap for Java pattern and matcher public class PatternMatcher { + public static final PatternMatcher MATCH_ANY = new PatternMatcher(Pattern.compile(".*")); private Pattern pattern; + // The name of 'user', 'database' and 'table' don't support complex matching in grant statement. + // Only using '%' to match any string. In other cases, it's string case-sensitive(or not) equivalent matching, + // so using the origin string to determine whether it matches. + private String originString; + private boolean caseSensitive; private static final Set FORBIDDEN_CHARS = Sets.newHashSet('<', '(', '[', '{', '^', '=', '$', '!', '|', ']', '}', ')', '?', '*', '+', '>', '@'); + public PatternMatcher(Pattern pattern) { + this.pattern = pattern; + } + + public PatternMatcher(String originString, boolean caseSensitive) { + this.originString = caseSensitive ? originString : originString.toLowerCase(Locale.ROOT); + this.caseSensitive = caseSensitive; + } + public boolean match(String candidate) { - if (pattern == null || candidate == null) { - // No pattern, how can I explain this? Return false now. - // No candidate, return false. + if (candidate == null) { return false; } - if (pattern.matcher(candidate).matches()) { - return true; + if (pattern != null) { + return pattern.matcher(candidate).matches(); } - return false; + if (caseSensitive) { + return candidate.equals(originString); + } else { + return candidate.toLowerCase(Locale.ROOT).equals(originString); + } + } + + /** + * Use in grant statement to support case-sensitive(or not) equivalent matching. + * + * @param originString The string to match. + * @param caseSensitive Case sensitive. + */ + public static PatternMatcher createFlatPattern(String originString, boolean caseSensitive) { + return createFlatPattern(originString, caseSensitive, false); + } + + /** + * Use in grant statement to support case-sensitive(or not) equivalent matching, or arbitrary matching. + * + * @param originString The string to match. If matchAny = true, this parameter has no effect. + * @param caseSensitive Case sensitive. + * @param matchAny match any string. + */ + public static PatternMatcher createFlatPattern( + String originString, boolean caseSensitive, boolean matchAny) { + if (matchAny) { + return MATCH_ANY; + } + return new PatternMatcher(originString, caseSensitive); } /* @@ -149,7 +192,7 @@ public class PatternMatcher { public static PatternMatcher createMysqlPattern(String mysqlPattern, boolean caseSensitive) throws AnalysisException { - PatternMatcher matcher = new PatternMatcher(); + PatternMatcher matcher; // Match nothing String newMysqlPattern = Strings.nullToEmpty(mysqlPattern); @@ -157,9 +200,9 @@ public class PatternMatcher { String javaPattern = convertMysqlPattern(newMysqlPattern); try { if (caseSensitive) { - matcher.pattern = Pattern.compile(javaPattern); + matcher = new PatternMatcher(Pattern.compile(javaPattern)); } else { - matcher.pattern = Pattern.compile(javaPattern, Pattern.CASE_INSENSITIVE); + matcher = new PatternMatcher(Pattern.compile(javaPattern, Pattern.CASE_INSENSITIVE)); } } catch (Exception e) { throw new AnalysisException("Bad pattern in SQL: " + e.getMessage()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java index 06a37b1942..a8f1337df7 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/DbPrivEntry.java @@ -54,7 +54,7 @@ public class DbPrivEntry extends PrivEntry { PatternMatcher dbPattern = createDbPatternMatcher(db); - PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); if (privs.containsNodePriv() || privs.containsResourcePriv()) { throw new AnalysisException("Db privilege can not contains global or resource privileges: " + privs); @@ -70,8 +70,7 @@ public class DbPrivEntry extends PrivEntry { dbCaseSensibility = false; } - PatternMatcher dbPattern = PatternMatcher.createMysqlPattern(db.equals(ANY_DB) ? "%" : db, dbCaseSensibility); - return dbPattern; + return PatternMatcher.createFlatPattern(db, dbCaseSensibility, db.equals(ANY_DB)); } public PatternMatcher getDbPattern() { diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java index ba74d280b2..6444d6b8e2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java @@ -53,7 +53,7 @@ public class GlobalPrivEntry extends PrivEntry { public static GlobalPrivEntry create(String host, String user, boolean isDomain, byte[] password, PrivBitSet privs) throws AnalysisException { PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); - PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); return new GlobalPrivEntry(hostPattern, host, userPattern, user, isDomain, password, privs); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java index 58fd169c59..9b47c69ebd 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java @@ -47,7 +47,7 @@ public class PrivBitSet implements Writable { public void unset(int index) { Preconditions.checkState(index < PaloPrivilege.privileges.length, index); - set &= ~set; + set &= 1 << index; } public boolean get(int index) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java index 82752c7ba3..c85a1f2912 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/TablePrivEntry.java @@ -50,12 +50,12 @@ public class TablePrivEntry extends DbPrivEntry { public static TablePrivEntry create(String host, String db, String user, String tbl, boolean isDomain, PrivBitSet privs) throws AnalysisException { PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); - PatternMatcher dbPattern = PatternMatcher.createMysqlPattern(db.equals(ANY_DB) ? "%" : db, - CaseSensibility.DATABASE.getCaseSensibility()); - PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + PatternMatcher dbPattern = PatternMatcher.createFlatPattern( + db, CaseSensibility.DATABASE.getCaseSensibility(), db.equals(ANY_DB)); + PatternMatcher userPattern = PatternMatcher.createFlatPattern(user, CaseSensibility.USER.getCaseSensibility()); - PatternMatcher tblPattern = PatternMatcher.createMysqlPattern(tbl.equals(ANY_TBL) ? "%" : tbl, - CaseSensibility.TABLE.getCaseSensibility()); + PatternMatcher tblPattern = PatternMatcher.createFlatPattern( + tbl, CaseSensibility.TABLE.getCaseSensibility(), tbl.equals(ANY_TBL)); if (privs.containsNodePriv() || privs.containsResourcePriv()) { throw new AnalysisException("Table privilege can not contains global or resource privileges: " + privs); diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java new file mode 100644 index 0000000000..71f6990191 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PrivEntryTest.java @@ -0,0 +1,48 @@ +// 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.doris.mysql.privilege; + +import org.apache.doris.analysis.UserIdentity; + +import org.junit.Assert; +import org.junit.Test; + +public class PrivEntryTest { + @Test + public void testNameWithUnderscores() throws Exception { + TablePrivEntry tablePrivEntry = TablePrivEntry.create( + "127.%", "db_db1", "user1", "tbl_tbl1", false, + PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.DROP_PRIV)); + // pattern match + Assert.assertFalse(tablePrivEntry.getDbPattern().match("db-db1")); + Assert.assertFalse(tablePrivEntry.getTblPattern().match("tbl-tbl1")); + // create TablePrivTable + TablePrivTable tablePrivTable = new TablePrivTable(); + tablePrivTable.addEntry(tablePrivEntry, false, false); + UserIdentity userIdentity = new UserIdentity("user1", "127.%", false); + userIdentity.setIsAnalyzed(); + + PrivBitSet privs1 = PrivBitSet.of(); + tablePrivTable.getPrivs(userIdentity, "db#db1", "tbl#tbl1", privs1); + Assert.assertFalse(PaloPrivilege.satisfy(privs1, PrivPredicate.DROP)); + + PrivBitSet privs2 = PrivBitSet.of(); + tablePrivTable.getPrivs(userIdentity, "db_db1", "tbl_tbl1", privs2); + Assert.assertTrue(PaloPrivilege.satisfy(privs2, PrivPredicate.DROP)); + } +}