morningman commented on code in PR #16091:
URL: https://github.com/apache/doris/pull/16091#discussion_r1097528550
##########
fe/fe-core/src/main/java/org/apache/doris/ldap/LdapManager.java:
##########
@@ -76,7 +77,12 @@ public LdapUserInfo getUserInfo(String fullName) {
if (ldapUserInfo != null && !ldapUserInfo.checkTimeout()) {
return ldapUserInfo;
}
- return getUserInfoAndUpdateCache(fullName);
+ try {
+ return getUserInfoAndUpdateCache(fullName);
+ } catch (DdlException e) {
+ LOG.warn("getUserInfo failed,", e);
Review Comment:
```suggestion
LOG.warn("getUserInfo for {} failed", fullName, e);
```
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogPrivTable.java:
##########
@@ -36,15 +30,11 @@ public class CatalogPrivTable extends PrivTable {
* Return first priv which match the user@host on ctl.* The returned priv
will be
* saved in 'savedPrivs'.
*/
- public void getPrivs(UserIdentity currentUser, String ctl, PrivBitSet
savedPrivs) {
+ public void getPrivs(String ctl, PrivBitSet savedPrivs) {
CatalogPrivEntry matchedEntry = null;
for (PrivEntry entry : entries) {
CatalogPrivEntry dsPrivEntry = (CatalogPrivEntry) entry;
Review Comment:
```suggestion
CatalogPrivEntry ctlPrivEntry = (CatalogPrivEntry) entry;
```
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivTable.java:
##########
@@ -0,0 +1,46 @@
+// 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.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/*
+ * GlobalPrivTable saves all global privs and also password for users
+ */
+public class GlobalPrivTable extends PrivTable {
+ private static final Logger LOG =
LogManager.getLogger(GlobalPrivTable.class);
+
+ public GlobalPrivTable() {
+ }
+
+ public void getPrivs(PrivBitSet savedPrivs) {
+ GlobalPrivEntry matchedEntry = null;
+ for (PrivEntry entry : entries) {
+ GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry;
Review Comment:
add comment in code to explain this logic
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String
origHost, PatternMatcher
}
}
- public PatternMatcher getHostPattern() {
- return hostPattern;
- }
-
- public String getOrigHost() {
- return origHost;
- }
-
- public boolean isAnyHost() {
- return isAnyHost;
- }
-
- public PatternMatcher getUserPattern() {
- return userPattern;
- }
-
- public String getOrigUser() {
- return origUser;
- }
-
- public boolean isAnyUser() {
- return isAnyUser;
- }
-
- public PrivBitSet getPrivSet() {
- return privSet;
- }
-
- public void setPrivSet(PrivBitSet privSet) {
- this.privSet = privSet;
- }
-
- public boolean isSetByDomainResolver() {
- return isSetByDomainResolver;
- }
-
- public void setSetByDomainResolver(boolean isSetByDomainResolver) {
- this.isSetByDomainResolver = isSetByDomainResolver;
- }
-
- public UserIdentity getUserIdent() {
- return userIdentity;
- }
-
- public boolean match(UserIdentity userIdent, boolean exactMatch) {
- if (exactMatch) {
- return origUser.equals(userIdent.getQualifiedUser()) &&
origHost.equals(userIdent.getHost());
- } else {
- return origUser.equals(userIdent.getQualifiedUser()) &&
hostPattern.match(userIdent.getHost());
- }
- }
-
- public abstract boolean keyMatch(PrivEntry other);
-
- /*
- * It's a bit complicated when persisting instance which its class has
derived classes.
- * eg: A (top class) -> B (derived) -> C (derived)
- *
- * Write process:
- * C.write()
- * |
- * --- write class name
- * |
- * --- super.write() -----> B.write()
- * | |
- * --- write C's self members --- write class name (if not write
before)
- * |
- * --- super.write() ----->
A.write()
- * | |
- * --- write B's self members
--- write class name (if not write before)
- * |
- *
--- write A's self members
- *
- * So the final write order is:
- * 1. C's class name
- * 2. A's self members
- * 3. B's self members
- * 4. C's self members
- *
- * In case that class name should only be wrote once, we use
isClassNameWrote flag.
- *
- * Read process:
- * static A.read()
- * |
- * --- read class name and instantiated the class instance (eg. C
class)
- * |
- * --- C.readFields()
- * |
- * --- super.readFields() --> B.readFields()
- * | |
- * --- read C's self members --- super.readFields() -->
A.readFields()
- * | |
- * --- read B's self members ---
read A's self members
- *
- * So the final read order is:
- * 1. C's class name
- * 2. A's self members
- * 3. B's self members
- * 4. C's self members
- *
- * Which is same as Write order.
- */
+ @Deprecated
public static PrivEntry read(DataInput in) throws IOException {
String className = Text.readString(in);
if (className.startsWith("com.baidu.palo")) {
Review Comment:
This can be removed
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Password.java:
##########
@@ -0,0 +1,61 @@
+// 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.common.io.Writable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class Password implements Writable {
+ public Password() {
+ }
+
+ public Password(byte[] password) {
+ this.password = password;
+ }
+
+ private byte[] password;
Review Comment:
Move the field at the begin of this class
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Password.java:
##########
@@ -0,0 +1,61 @@
+// 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.common.io.Writable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class Password implements Writable {
+ public Password() {
+ }
+
+ public Password(byte[] password) {
+ this.password = password;
+ }
+
+ private byte[] password;
+
+ public static Password read(DataInput in) throws IOException {
+ Password pwd = new Password();
Review Comment:
use Gson to do the read/write
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
import java.util.stream.Stream;
public class RoleManager implements Writable {
- private Map<String, PaloRole> roles = Maps.newHashMap();
+ private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+ //prefix of each user default role
+ public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
Review Comment:
Need to check to not allow user create role with this prefix
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java:
##########
@@ -0,0 +1,143 @@
+// 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.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Writable;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class User implements Comparable<User>, Writable {
+ private static final Logger LOG = LogManager.getLogger(User.class);
+
+ private UserIdentity userIdentity;
+ private UserIdentity domainUserIdentity;
+ private boolean isSetByDomainResolver = false;
+ // host is not case sensitive
+ protected PatternMatcher hostPattern;
+ protected boolean isAnyHost = false;
+ private Password password;
+
+
+ public Password getPassword() {
+ return password;
+ }
+
+ public void setPassword(Password password) {
+ this.password = password;
+ }
+
+ public void setPassword(byte[] password) {
+ this.password = new Password(password);
+ }
+
+ public UserIdentity getUserIdentity() {
+ return userIdentity;
+ }
+
+ public void setUserIdentity(UserIdentity userIdentity) {
+ this.userIdentity = userIdentity;
+ }
+
+ public UserIdentity getDomainUserIdentity() {
+ if (isSetByDomainResolver()) {
+ return domainUserIdentity;
+ } else {
+ return userIdentity;
+ }
+
+ }
+
+ public void setDomainUserIdentity(UserIdentity domainUserIdentity) {
+ this.domainUserIdentity = domainUserIdentity;
+ }
+
+ public boolean isSetByDomainResolver() {
+ return isSetByDomainResolver;
+ }
+
+ public void setSetByDomainResolver(boolean setByDomainResolver) {
+ isSetByDomainResolver = setByDomainResolver;
+ }
+
+ public PatternMatcher getHostPattern() {
+ return hostPattern;
+ }
+
+ public void setHostPattern(PatternMatcher hostPattern) {
+ this.hostPattern = hostPattern;
+ }
+
+ public boolean isAnyHost() {
+ return isAnyHost;
+ }
+
+ public void setAnyHost(boolean anyHost) {
+ isAnyHost = anyHost;
+ }
+
+ public boolean hasPassword() {
+ return password != null && password.getPassword() != null &&
password.getPassword().length != 0;
+ }
+
+ @Override
+ public int compareTo(@NotNull User o) {
+ return -userIdentity.getHost().compareTo(o.userIdentity.getHost());
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ userIdentity.write(out);
Review Comment:
Use Gson serde
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
+ }
+
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ // check password
+ byte[] saltPassword =
MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+ // when the length of password is zero, the user has no password
+ if ((remotePasswd.length == saltPassword.length)
+ && (remotePasswd.length == 0
+ || MysqlPassword.checkScramble(remotePasswd, randomString,
saltPassword))) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ // found the matched entry
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // case A. this means we already matched a entry by user@host,
but password is incorrect.
+ // return false, NOT continue matching other entries.
+ // For example, there are 2 entries in order:
+ // 1. cmy@"192.168.%" identified by '123';
+ // 2. cmy@"%" identified by 'abc';
+ // if user cmy@'192.168.1.1' try to login with password 'abc',
it will be denied.
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ remotePasswd.length == 0 ? "NO" : "YES");
+ }
+ }
+
+ }
+
+ public void checkPlainPassword(String remoteUser, String remoteHost,
String remotePasswd,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(),
remotePasswd)) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // set case A. in checkPassword()
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ }
+ throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR,
remoteUser + "@" + remoteHost,
+ "YES");
+ }
+
+ public void clearEntriesSetByResolver() {
+ Iterator<Entry<String, List<User>>> iterator =
nameToUsers.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<String, List<User>> next = iterator.next();
+ Iterator<User> iter = next.getValue().iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.isSetByDomainResolver()) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(next.getValue())) {
+ iterator.remove();
+ } else {
+ Collections.sort(next.getValue());
+ }
+ }
+
+ }
+
+ public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity
domainUserIdent, boolean setByResolver)
+ throws PatternMatcherException {
+ if (userIdentityExist(userIdent, true)) {
+ User userByUserIdentity = getUserByUserIdentity(userIdent);
+ userByUserIdentity.setPassword(pwd);
+ userByUserIdentity.setSetByDomainResolver(setByResolver);
+ return userByUserIdentity;
+ }
+
+ PatternMatcher hostPattern = PatternMatcher
+ .createMysqlPattern(userIdent.getHost(),
CaseSensibility.HOST.getCaseSensibility());
+ User user = new User();
+ user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+ user.setUserIdentity(userIdent);
+ Password password = new Password();
+ password.setPassword(pwd);
+ user.setPassword(password);
+ user.setHostPattern(hostPattern);
+ user.setSetByDomainResolver(setByResolver);
+ if (setByResolver) {
+ Preconditions.checkNotNull(domainUserIdent);
+ user.setDomainUserIdentity(domainUserIdent);
+ }
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToLists = Lists.newArrayList(user);
+ nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+ } else {
+ nameToLists.add(user);
+ Collections.sort(nameToLists);
+ }
+ return user;
+ }
+
+ public User getUserByUserIdentity(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return null;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ return user;
+ }
+ }
+ return null;
+ }
+
+ public void removeUser(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToUsers.remove(userIdent.getQualifiedUser());
+ } else {
+ Collections.sort(nameToLists);
+ }
+ }
+
+ public Map<String, List<User>> getNameToUsers() {
+ return nameToUsers;
Review Comment:
Not a good idea to expose field unprotected.
##########
fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java:
##########
@@ -34,7 +34,6 @@ public class ShowRolesStmt extends ShowStmt {
ShowResultSetMetaData.Builder builder =
ShowResultSetMetaData.builder();
builder.addColumn(new Column("Name", ScalarType.createVarchar(100)));
- builder.addColumn(new Column("Users", ScalarType.createVarchar(100)));
Review Comment:
Why removing this?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/GlobalPrivEntry.java:
##########
@@ -18,144 +18,52 @@
package org.apache.doris.mysql.privilege;
import org.apache.doris.analysis.UserIdentity;
-import org.apache.doris.common.AnalysisException;
-import org.apache.doris.common.CaseSensibility;
-import org.apache.doris.common.PatternMatcher;
-import org.apache.doris.common.PatternMatcherWrapper;
-import org.apache.doris.common.io.Text;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.DataInput;
-import java.io.DataOutput;
import java.io.IOException;
public class GlobalPrivEntry extends PrivEntry {
private static final Logger LOG =
LogManager.getLogger(GlobalPrivEntry.class);
- private byte[] password;
- // set domainUserIdent when this a password entry and is set by domain
resolver.
- // so that when user checking password with user@'IP' and match a entry
set by the resolver,
- // it should return this domainUserIdent as "current user". And user can
use this user ident to get privileges
- // further.
- private UserIdentity domainUserIdent;
+ @Deprecated
+ protected byte[] password;
+ @Deprecated
+ protected UserIdentity domainUserIdent;
Review Comment:
Is this `GlobalPrivEntry` deprecated?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
Review Comment:
```suggestion
private Map<String, List<User>> nameToUsers = Maps.newConcurrentMap();
```
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivEntry.java:
##########
@@ -82,108 +112,7 @@ protected PrivEntry(PatternMatcher hostPattern, String
origHost, PatternMatcher
}
}
- public PatternMatcher getHostPattern() {
- return hostPattern;
- }
-
- public String getOrigHost() {
- return origHost;
- }
-
- public boolean isAnyHost() {
- return isAnyHost;
- }
-
- public PatternMatcher getUserPattern() {
- return userPattern;
- }
-
- public String getOrigUser() {
- return origUser;
- }
-
- public boolean isAnyUser() {
- return isAnyUser;
- }
-
- public PrivBitSet getPrivSet() {
- return privSet;
- }
-
- public void setPrivSet(PrivBitSet privSet) {
- this.privSet = privSet;
- }
-
- public boolean isSetByDomainResolver() {
- return isSetByDomainResolver;
- }
-
- public void setSetByDomainResolver(boolean isSetByDomainResolver) {
- this.isSetByDomainResolver = isSetByDomainResolver;
- }
-
- public UserIdentity getUserIdent() {
- return userIdentity;
- }
-
- public boolean match(UserIdentity userIdent, boolean exactMatch) {
- if (exactMatch) {
- return origUser.equals(userIdent.getQualifiedUser()) &&
origHost.equals(userIdent.getHost());
- } else {
- return origUser.equals(userIdent.getQualifiedUser()) &&
hostPattern.match(userIdent.getHost());
- }
- }
-
- public abstract boolean keyMatch(PrivEntry other);
-
- /*
- * It's a bit complicated when persisting instance which its class has
derived classes.
- * eg: A (top class) -> B (derived) -> C (derived)
- *
- * Write process:
- * C.write()
- * |
- * --- write class name
- * |
- * --- super.write() -----> B.write()
- * | |
- * --- write C's self members --- write class name (if not write
before)
- * |
- * --- super.write() ----->
A.write()
- * | |
- * --- write B's self members
--- write class name (if not write before)
- * |
- *
--- write A's self members
- *
- * So the final write order is:
- * 1. C's class name
- * 2. A's self members
- * 3. B's self members
- * 4. C's self members
- *
- * In case that class name should only be wrote once, we use
isClassNameWrote flag.
- *
- * Read process:
- * static A.read()
- * |
- * --- read class name and instantiated the class instance (eg. C
class)
- * |
- * --- C.readFields()
- * |
- * --- super.readFields() --> B.readFields()
- * | |
- * --- read C's self members --- super.readFields() -->
A.readFields()
- * | |
- * --- read B's self members ---
read A's self members
- *
- * So the final read order is:
- * 1. C's class name
- * 2. A's self members
- * 3. B's self members
- * 4. C's self members
- *
- * Which is same as Write order.
- */
+ @Deprecated
public static PrivEntry read(DataInput in) throws IOException {
Review Comment:
I think we can refactor this read method.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,137 @@
+// 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.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable {
+ private Map<UserIdentity, Set<String>> userToRoles =
Maps.newConcurrentMap();
+ private Map<String, Set<UserIdentity>> roleToUsers =
Maps.newConcurrentMap();
+
+ public void addUserRole(UserIdentity userIdentity, String roleName) {
+ Set<String> roles = userToRoles.get(userIdentity);
+ if (CollectionUtils.isEmpty(roles)) {
+ roles = Sets.newHashSet();
+ }
+ roles.add(roleName);
+ userToRoles.put(userIdentity, roles);
+ Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+ if (CollectionUtils.isEmpty(userIdentities)) {
+ userIdentities = Sets.newHashSet();
+ }
+ userIdentities.add(userIdentity);
+ roleToUsers.put(roleName, userIdentities);
+ }
+
+ public void dropUser(UserIdentity userIdentity) {
+ if (!userToRoles.containsKey(userIdentity)) {
+ return;
+ }
+ Set<String> roles = userToRoles.remove(userIdentity);
+ for (String roleName : roles) {
+ Set<UserIdentity> userIdentities = roleToUsers.get(roleName);
+ if (CollectionUtils.isEmpty(userIdentities)) {
+ continue;
+ }
+ userIdentities.remove(userIdentity);
+ if (CollectionUtils.isEmpty(userIdentities)) {
+ roleToUsers.remove(roleName);
+ }
+ }
+ }
+
+ public void dropRole(String roleName) {
+ if (!roleToUsers.containsKey(roleName)) {
+ return;
+ }
+ Set<UserIdentity> remove = roleToUsers.remove(roleName);
+ for (UserIdentity userIdentity : remove) {
+ Set<String> roles = userToRoles.get(userIdentity);
+ if (CollectionUtils.isEmpty(roles)) {
+ continue;
+ }
+ roles.remove(roleName);
+ if (CollectionUtils.isEmpty(roles)) {
+ userToRoles.remove(userIdentity);
+ }
+ }
+ }
+
+ public Set<String> getRolesByUser(UserIdentity user) {
+ Set<String> roles = userToRoles.get(user);
+ return roles == null ? Collections.EMPTY_SET : roles;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ out.writeInt(userToRoles.size());
Review Comment:
Use Gson serde
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserRoleManager.java:
##########
@@ -0,0 +1,137 @@
+// 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.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserRoleManager implements Writable {
+ private Map<UserIdentity, Set<String>> userToRoles =
Maps.newConcurrentMap();
Review Comment:
Need a lock to make sure these 2 maps are updated atomically
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -72,37 +61,29 @@ public boolean hasPriv(String host, String user,
PrivPredicate wanted) {
* NOTICE, this method does not set password for the newly added entry if
this is a user priv table, the caller
* need to set password later.
*/
- public PrivEntry addEntry(PrivEntry newEntry, boolean errOnExist, boolean
errOnNonExist) throws DdlException {
+ public PrivEntry addEntry(PrivEntry newEntry,
+ boolean errOnExist, boolean errOnNonExist) throws DdlException {
PrivEntry existingEntry = getExistingEntry(newEntry);
if (existingEntry == null) {
if (errOnNonExist) {
- throw new DdlException("User " + newEntry.getUserIdent() + "
does not exist");
+ throw new DdlException("entry does not exist");
}
entries.add(newEntry);
Collections.sort(entries);
LOG.info("add priv entry: {}", newEntry);
return newEntry;
} else {
if (errOnExist) {
- throw new DdlException("User already exist");
+ throw new DdlException("entry already exist");
} else {
- checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY");
- // if existing entry is set by domain resolver, just replace
it with the new entry.
- // if existing entry is not set by domain resolver, merge the
2 entries.
- if (existingEntry.isSetByDomainResolver()) {
- existingEntry.setPrivSet(newEntry.getPrivSet());
-
existingEntry.setSetByDomainResolver(newEntry.isSetByDomainResolver());
- LOG.debug("reset priv entry: {}", existingEntry);
- } else if (!newEntry.isSetByDomainResolver()) {
- mergePriv(existingEntry, newEntry);
- existingEntry.setSetByDomainResolver(false);
- LOG.debug("merge priv entry: {}", existingEntry);
- }
+ mergePriv(existingEntry, newEntry);
+ LOG.debug("merge priv entry: {}", existingEntry);
Review Comment:
How to handle `domain` now?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java:
##########
@@ -0,0 +1,584 @@
+// 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.ResourcePattern;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.FeMetaVersion;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.Auth.PrivLevel;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+public class Role implements Writable {
+ private static final Logger LOG = LogManager.getLogger(Role.class);
+
+ // operator is responsible for operating cluster, such as add/drop node
+ public static String OPERATOR_ROLE = "operator";
+ // admin is like DBA, who has all privileges except for NODE privilege
held by operator
+ public static String ADMIN_ROLE = "admin";
+
+ public static Role OPERATOR = new Role(OPERATOR_ROLE,
+ TablePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV,
Privilege.ADMIN_PRIV),
+ ResourcePattern.ALL, PrivBitSet.of(Privilege.NODE_PRIV,
Privilege.ADMIN_PRIV));
+ public static Role ADMIN = new Role(ADMIN_ROLE,
+ TablePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV),
+ ResourcePattern.ALL, PrivBitSet.of(Privilege.ADMIN_PRIV));
+
+ private String roleName;
+ private Map<TablePattern, PrivBitSet> tblPatternToPrivs =
Maps.newConcurrentMap();
+ private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs =
Maps.newConcurrentMap();
+
+ private GlobalPrivTable globalPrivTable = new GlobalPrivTable();
+ private CatalogPrivTable catalogPrivTable = new CatalogPrivTable();
+ private DbPrivTable dbPrivTable = new DbPrivTable();
+ private TablePrivTable tablePrivTable = new TablePrivTable();
+ private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
+
+ @Deprecated
+ private Set<UserIdentity> users = Sets.newConcurrentHashSet();
+
+ @Deprecated
+ public Set<UserIdentity> getUsers() {
+ return users;
+ }
+
+ private Role() {
+
+ }
+
+ public Role(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public Role(String roleName, TablePattern tablePattern, PrivBitSet privs)
throws DdlException {
+ this.roleName = roleName;
+ this.tblPatternToPrivs.put(tablePattern, privs);
+ grantPrivs(tablePattern, privs.copy());
+ }
+
+ public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet
privs) throws DdlException {
+ this.roleName = roleName;
+ this.resourcePatternToPrivs.put(resourcePattern, privs);
+ grantPrivs(resourcePattern, privs.copy());
+ }
+
+ public Role(String roleName, TablePattern tablePattern, PrivBitSet
tablePrivs,
+ ResourcePattern resourcePattern, PrivBitSet resourcePrivs) {
+ this.roleName = roleName;
+ this.tblPatternToPrivs.put(tablePattern, tablePrivs);
+ this.resourcePatternToPrivs.put(resourcePattern, resourcePrivs);
+ //for init admin role,will not generate exception
+ try {
+ grantPrivs(tablePattern, tablePrivs.copy());
+ grantPrivs(resourcePattern, resourcePrivs.copy());
+ } catch (DdlException e) {
+ LOG.warn("grant failed,", e);
+ }
+
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public Map<TablePattern, PrivBitSet> getTblPatternToPrivs() {
+ return tblPatternToPrivs;
+ }
+
+ public Map<ResourcePattern, PrivBitSet> getResourcePatternToPrivs() {
+ return resourcePatternToPrivs;
+ }
+
+ // merge role not check role name.
+ public void mergeNotCheck(Role other) throws DdlException {
+ for (Map.Entry<TablePattern, PrivBitSet> entry :
other.getTblPatternToPrivs().entrySet()) {
+ if (tblPatternToPrivs.containsKey(entry.getKey())) {
+ PrivBitSet existPrivs = tblPatternToPrivs.get(entry.getKey());
+ existPrivs.or(entry.getValue());
+ } else {
+ tblPatternToPrivs.put(entry.getKey(), entry.getValue());
+ }
+ grantPrivs(entry.getKey(), entry.getValue().copy());
+ }
+ for (Map.Entry<ResourcePattern, PrivBitSet> entry :
other.resourcePatternToPrivs.entrySet()) {
+ if (resourcePatternToPrivs.containsKey(entry.getKey())) {
+ PrivBitSet existPrivs =
resourcePatternToPrivs.get(entry.getKey());
+ existPrivs.or(entry.getValue());
+ } else {
+ resourcePatternToPrivs.put(entry.getKey(), entry.getValue());
+ }
+ grantPrivs(entry.getKey(), entry.getValue().copy());
+ }
+ }
+
+ public void merge(Role other) throws DdlException {
+
Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName()));
+ mergeNotCheck(other);
+ }
+
+
+ public static Role read(DataInput in) throws IOException {
+ Role role = new Role();
+ try {
+ role.readFields(in);
+ } catch (DdlException e) {
+ LOG.warn("grant failed,", e);
+ }
+ return role;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ Text.writeString(out, roleName);
Review Comment:
use Gson serde
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
import java.util.stream.Stream;
public class RoleManager implements Writable {
- private Map<String, PaloRole> roles = Maps.newHashMap();
+ private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+ //prefix of each user default role
+ public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
+
+ private Map<String, Role> roles = Maps.newHashMap();
public RoleManager() {
- roles.put(PaloRole.OPERATOR.getRoleName(), PaloRole.OPERATOR);
- roles.put(PaloRole.ADMIN.getRoleName(), PaloRole.ADMIN);
+ roles.put(
+ Role.OPERATOR.getRoleName(), Role.OPERATOR);
+ roles.put(
+ Role.ADMIN.getRoleName(), Role.ADMIN);
}
- public PaloRole getRole(String role) {
- return roles.get(role);
+ public Role getRole(String name) {
+ return roles.get(name);
}
- public PaloRole addRole(PaloRole newRole, boolean errOnExist) throws
DdlException {
- PaloRole existingRole = roles.get(newRole.getRoleName());
+ public Role addRole(Role newRole, boolean errOnExist) throws DdlException {
Review Comment:
How about name it as `addOrMergeRole`?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java:
##########
@@ -40,19 +44,25 @@
import java.util.stream.Stream;
public class RoleManager implements Writable {
- private Map<String, PaloRole> roles = Maps.newHashMap();
+ private static final Logger LOG = LogManager.getLogger(RoleManager.class);
+ //prefix of each user default role
+ public static String DEFAULT_ROLE_PREFIX = "default_role_rbac_";
+
+ private Map<String, Role> roles = Maps.newHashMap();
Review Comment:
```suggestion
private Map<String, Role> roles = Maps.concurrentMap();
```
?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
Review Comment:
Remove unused code
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
Review Comment:
And add comment to explain the key
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
+ }
+
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ // check password
+ byte[] saltPassword =
MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+ // when the length of password is zero, the user has no password
+ if ((remotePasswd.length == saltPassword.length)
+ && (remotePasswd.length == 0
+ || MysqlPassword.checkScramble(remotePasswd, randomString,
saltPassword))) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ // found the matched entry
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // case A. this means we already matched a entry by user@host,
but password is incorrect.
+ // return false, NOT continue matching other entries.
+ // For example, there are 2 entries in order:
+ // 1. cmy@"192.168.%" identified by '123';
+ // 2. cmy@"%" identified by 'abc';
+ // if user cmy@'192.168.1.1' try to login with password 'abc',
it will be denied.
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ remotePasswd.length == 0 ? "NO" : "YES");
+ }
+ }
+
+ }
+
+ public void checkPlainPassword(String remoteUser, String remoteHost,
String remotePasswd,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(),
remotePasswd)) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // set case A. in checkPassword()
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ }
+ throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR,
remoteUser + "@" + remoteHost,
+ "YES");
+ }
+
+ public void clearEntriesSetByResolver() {
+ Iterator<Entry<String, List<User>>> iterator =
nameToUsers.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<String, List<User>> next = iterator.next();
+ Iterator<User> iter = next.getValue().iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.isSetByDomainResolver()) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(next.getValue())) {
+ iterator.remove();
+ } else {
+ Collections.sort(next.getValue());
+ }
+ }
+
+ }
+
+ public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity
domainUserIdent, boolean setByResolver)
+ throws PatternMatcherException {
+ if (userIdentityExist(userIdent, true)) {
+ User userByUserIdentity = getUserByUserIdentity(userIdent);
+ userByUserIdentity.setPassword(pwd);
+ userByUserIdentity.setSetByDomainResolver(setByResolver);
+ return userByUserIdentity;
+ }
+
+ PatternMatcher hostPattern = PatternMatcher
+ .createMysqlPattern(userIdent.getHost(),
CaseSensibility.HOST.getCaseSensibility());
+ User user = new User();
+ user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+ user.setUserIdentity(userIdent);
+ Password password = new Password();
+ password.setPassword(pwd);
+ user.setPassword(password);
+ user.setHostPattern(hostPattern);
+ user.setSetByDomainResolver(setByResolver);
+ if (setByResolver) {
+ Preconditions.checkNotNull(domainUserIdent);
+ user.setDomainUserIdentity(domainUserIdent);
+ }
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToLists = Lists.newArrayList(user);
+ nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+ } else {
+ nameToLists.add(user);
+ Collections.sort(nameToLists);
+ }
+ return user;
+ }
+
+ public User getUserByUserIdentity(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return null;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ return user;
+ }
+ }
+ return null;
+ }
+
+ public void removeUser(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToUsers.remove(userIdent.getQualifiedUser());
+ } else {
+ Collections.sort(nameToLists);
+ }
+ }
+
+ public Map<String, List<User>> getNameToUsers() {
+ return nameToUsers;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ out.writeInt(nameToUsers.size());
Review Comment:
And better to move read/write method at the end of the class. it is a
tradition.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
+ }
+
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ // check password
+ byte[] saltPassword =
MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+ // when the length of password is zero, the user has no password
+ if ((remotePasswd.length == saltPassword.length)
+ && (remotePasswd.length == 0
+ || MysqlPassword.checkScramble(remotePasswd, randomString,
saltPassword))) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ // found the matched entry
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // case A. this means we already matched a entry by user@host,
but password is incorrect.
+ // return false, NOT continue matching other entries.
+ // For example, there are 2 entries in order:
+ // 1. cmy@"192.168.%" identified by '123';
+ // 2. cmy@"%" identified by 'abc';
+ // if user cmy@'192.168.1.1' try to login with password 'abc',
it will be denied.
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ remotePasswd.length == 0 ? "NO" : "YES");
+ }
+ }
+
+ }
+
+ public void checkPlainPassword(String remoteUser, String remoteHost,
String remotePasswd,
Review Comment:
The logic of `checkPassword()` and `checkPlainPassword()` are almost same,
better merge them into one method.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
Review Comment:
should throw exception?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
+ }
+
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ // check password
+ byte[] saltPassword =
MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+ // when the length of password is zero, the user has no password
+ if ((remotePasswd.length == saltPassword.length)
+ && (remotePasswd.length == 0
+ || MysqlPassword.checkScramble(remotePasswd, randomString,
saltPassword))) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ // found the matched entry
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // case A. this means we already matched a entry by user@host,
but password is incorrect.
+ // return false, NOT continue matching other entries.
+ // For example, there are 2 entries in order:
+ // 1. cmy@"192.168.%" identified by '123';
+ // 2. cmy@"%" identified by 'abc';
+ // if user cmy@'192.168.1.1' try to login with password 'abc',
it will be denied.
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ remotePasswd.length == 0 ? "NO" : "YES");
+ }
+ }
+
+ }
+
+ public void checkPlainPassword(String remoteUser, String remoteHost,
String remotePasswd,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(),
remotePasswd)) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // set case A. in checkPassword()
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ }
+ throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR,
remoteUser + "@" + remoteHost,
+ "YES");
+ }
+
+ public void clearEntriesSetByResolver() {
+ Iterator<Entry<String, List<User>>> iterator =
nameToUsers.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<String, List<User>> next = iterator.next();
+ Iterator<User> iter = next.getValue().iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.isSetByDomainResolver()) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(next.getValue())) {
+ iterator.remove();
+ } else {
+ Collections.sort(next.getValue());
+ }
+ }
+
+ }
+
+ public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity
domainUserIdent, boolean setByResolver)
+ throws PatternMatcherException {
+ if (userIdentityExist(userIdent, true)) {
+ User userByUserIdentity = getUserByUserIdentity(userIdent);
+ userByUserIdentity.setPassword(pwd);
+ userByUserIdentity.setSetByDomainResolver(setByResolver);
+ return userByUserIdentity;
+ }
+
+ PatternMatcher hostPattern = PatternMatcher
+ .createMysqlPattern(userIdent.getHost(),
CaseSensibility.HOST.getCaseSensibility());
+ User user = new User();
+ user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
+ user.setUserIdentity(userIdent);
+ Password password = new Password();
+ password.setPassword(pwd);
+ user.setPassword(password);
+ user.setHostPattern(hostPattern);
+ user.setSetByDomainResolver(setByResolver);
+ if (setByResolver) {
+ Preconditions.checkNotNull(domainUserIdent);
+ user.setDomainUserIdentity(domainUserIdent);
+ }
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToLists = Lists.newArrayList(user);
+ nameToUsers.put(userIdent.getQualifiedUser(), nameToLists);
+ } else {
+ nameToLists.add(user);
+ Collections.sort(nameToLists);
+ }
+ return user;
+ }
+
+ public User getUserByUserIdentity(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return null;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ return user;
+ }
+ }
+ return null;
+ }
+
+ public void removeUser(UserIdentity userIdent) {
+ List<User> nameToLists = nameToUsers.get(userIdent.getQualifiedUser());
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ return;
+ }
+ Iterator<User> iter = nameToLists.iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.getUserIdentity().equals(userIdent)) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(nameToLists)) {
+ nameToUsers.remove(userIdent.getQualifiedUser());
+ } else {
+ Collections.sort(nameToLists);
+ }
+ }
+
+ public Map<String, List<User>> getNameToUsers() {
+ return nameToUsers;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ out.writeInt(nameToUsers.size());
Review Comment:
Use Gson serde
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java:
##########
@@ -0,0 +1,342 @@
+// 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.apache.doris.catalog.Env;
+import org.apache.doris.common.AuthenticationException;
+import org.apache.doris.common.CaseSensibility;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.PatternMatcher;
+import org.apache.doris.common.PatternMatcherException;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.MysqlPassword;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class UserManager implements Writable {
+ public static final String ANY_HOST = "%";
+ private static final Logger LOG = LogManager.getLogger(UserManager.class);
+ // Set<User> users = new HashSet<>();
+ //One name may have multiple User,because host can be different
+ private Map<String, List<User>> nameToUsers = new HashMap<>();
+
+ public boolean userIdentityExist(UserIdentity userIdentity, boolean
includeByDomain) {
+ List<User> users = nameToUsers.get(userIdentity.getQualifiedUser());
+ if (CollectionUtils.isEmpty(users)) {
+ return false;
+ }
+ for (User user : users) {
+ if
(user.getUserIdentity().getHost().equalsIgnoreCase(userIdentity.getHost())) {
+ if (includeByDomain || !user.isSetByDomainResolver()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<User> getUserByName(String name) {
+ List<User> users = nameToUsers.get(name);
+ return users == null ? Collections.EMPTY_LIST : users;
+ }
+
+ public void checkPassword(String remoteUser, String remoteHost, byte[]
remotePasswd, byte[] randomString,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ LOG.debug("check password for user: {} from {}, password: {}, random
string: {}",
+ remoteUser, remoteHost, remotePasswd, randomString);
+
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ return;
+ }
+
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ // check password
+ byte[] saltPassword =
MysqlPassword.getSaltFromPassword(user.getPassword().getPassword());
+ // when the length of password is zero, the user has no password
+ if ((remotePasswd.length == saltPassword.length)
+ && (remotePasswd.length == 0
+ || MysqlPassword.checkScramble(remotePasswd, randomString,
saltPassword))) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ // found the matched entry
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // case A. this means we already matched a entry by user@host,
but password is incorrect.
+ // return false, NOT continue matching other entries.
+ // For example, there are 2 entries in order:
+ // 1. cmy@"192.168.%" identified by '123';
+ // 2. cmy@"%" identified by 'abc';
+ // if user cmy@'192.168.1.1' try to login with password 'abc',
it will be denied.
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ remotePasswd.length == 0 ? "NO" : "YES");
+ }
+ }
+
+ }
+
+ public void checkPlainPassword(String remoteUser, String remoteHost,
String remotePasswd,
+ List<UserIdentity> currentUser) throws AuthenticationException {
+ PasswordPolicyManager passwdPolicyMgr =
Env.getCurrentEnv().getAuth().getPasswdPolicyManager();
+ List<User> users = nameToUsers.get(remoteUser);
+ if (CollectionUtils.isEmpty(users)) {
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ for (User user : users) {
+ if (user.getUserIdentity().isDomain()) {
+ continue;
+ }
+ // check host
+ if (!user.isAnyHost() && !user.getHostPattern().match(remoteHost))
{
+ continue;
+ }
+ UserIdentity curUser = user.getDomainUserIdentity();
+ if (MysqlPassword.checkPlainPass(user.getPassword().getPassword(),
remotePasswd)) {
+
passwdPolicyMgr.checkAccountLockedAndPasswordExpiration(curUser);
+ if (currentUser != null) {
+ currentUser.add(curUser);
+ }
+ return;
+ } else {
+ // set case A. in checkPassword()
+ passwdPolicyMgr.onFailedLogin(curUser);
+ throw new
AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" +
remoteHost,
+ "YES");
+ }
+ }
+ throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR,
remoteUser + "@" + remoteHost,
+ "YES");
+ }
+
+ public void clearEntriesSetByResolver() {
+ Iterator<Entry<String, List<User>>> iterator =
nameToUsers.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<String, List<User>> next = iterator.next();
+ Iterator<User> iter = next.getValue().iterator();
+ while (iter.hasNext()) {
+ User user = iter.next();
+ if (user.isSetByDomainResolver()) {
+ iter.remove();
+ }
+ }
+ if (CollectionUtils.isEmpty(next.getValue())) {
+ iterator.remove();
+ } else {
+ Collections.sort(next.getValue());
+ }
+ }
+
+ }
+
+ public User createUser(UserIdentity userIdent, byte[] pwd, UserIdentity
domainUserIdent, boolean setByResolver)
+ throws PatternMatcherException {
+ if (userIdentityExist(userIdent, true)) {
+ User userByUserIdentity = getUserByUserIdentity(userIdent);
+ userByUserIdentity.setPassword(pwd);
+ userByUserIdentity.setSetByDomainResolver(setByResolver);
+ return userByUserIdentity;
+ }
+
+ PatternMatcher hostPattern = PatternMatcher
+ .createMysqlPattern(userIdent.getHost(),
CaseSensibility.HOST.getCaseSensibility());
+ User user = new User();
+ user.setAnyHost(userIdent.getHost().equals(ANY_HOST));
Review Comment:
How about create a constructor to do these `set` method?
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java:
##########
@@ -604,7 +579,9 @@ public void readFields(DataInput in) throws IOException {
}
// whiteList
- whiteList.readFields(in);
+ if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_116) {
+ whiteList.readFields(in);
+ }
Review Comment:
else {
whiteList = new WhiteList();
}
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivTable.java:
##########
@@ -236,9 +170,9 @@ public static PrivTable read(DataInput in) throws
IOException {
try {
Class<? extends PrivTable> derivedClass = (Class<? extends
PrivTable>) Class.forName(className);
privTable = derivedClass.newInstance();
- Class[] paramTypes = { DataInput.class };
+ Class[] paramTypes = {DataInput.class};
Review Comment:
This read method is also deprecated
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]