This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-2.1 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.1 by this push: new b87674630e9 [enhance](auth)Abstract authentication interface (#33668) (#33961) b87674630e9 is described below commit b87674630e979819a1ebc08736b60f13e21e806c Author: Mingyu Chen <morning...@163.com> AuthorDate: Mon Apr 22 14:47:59 2024 +0800 [enhance](auth)Abstract authentication interface (#33668) (#33961) bp #33668 Co-authored-by: zhangdong <493738...@qq.com> --- .../org/apache/doris/analysis/CreateUserStmt.java | 4 +- .../org/apache/doris/analysis/DropUserStmt.java | 4 +- .../main/java/org/apache/doris/catalog/Env.java | 9 + .../java/org/apache/doris/mysql/MysqlProto.java | 4 +- ...MysqlAuthType.java => AuthenticateRequest.java} | 36 ++-- .../mysql/authenticate/AuthenticateResponse.java | 76 ++++++++ .../{MysqlAuthType.java => AuthenticateType.java} | 4 +- .../{MysqlAuthType.java => Authenticator.java} | 24 +-- .../mysql/authenticate/AuthenticatorManager.java | 83 +++++++++ .../mysql/authenticate/DefaultAuthenticator.java | 75 ++++++++ .../apache/doris/mysql/authenticate/MysqlAuth.java | 205 --------------------- ...dapAuthenticate.java => LdapAuthenticator.java} | 78 ++++++-- .../doris/mysql/authenticate/ldap/LdapManager.java | 6 +- .../ClearPassword.java} | 24 +-- .../password/ClearPasswordResolver.java | 60 ++++++ .../NativePassword.java} | 30 ++- .../password/NativePasswordResolver.java | 71 +++++++ .../{MysqlAuthType.java => password/Password.java} | 20 +- .../PasswordResolver.java} | 30 ++- .../org/apache/doris/mysql/privilege/Auth.java | 4 +- .../org/apache/doris/mysql/MysqlProtoTest.java | 53 +++--- .../authenticate/DefaultAuthenticatorTest.java | 100 ++++++++++ .../authenticate/ldap/LdapAuthenticateTest.java | 203 -------------------- .../authenticate/ldap/LdapAuthenticatorTest.java | 146 +++++++++++++++ 24 files changed, 783 insertions(+), 566 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateUserStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateUserStmt.java index 008e691af7e..9c07b7aee12 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateUserStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateUserStmt.java @@ -24,7 +24,7 @@ import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.UserException; -import org.apache.doris.mysql.authenticate.MysqlAuthType; +import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.mysql.privilege.Role; import org.apache.doris.qe.ConnectContext; @@ -120,7 +120,7 @@ public class CreateUserStmt extends DdlStmt { super.analyze(analyzer); if (Config.access_controller_type.equalsIgnoreCase("ranger-doris") - && MysqlAuthType.getAuthTypeConfig() == MysqlAuthType.LDAP) { + && AuthenticateType.getAuthTypeConfig() == AuthenticateType.LDAP) { throw new AnalysisException("Create user is prohibited when Ranger and LDAP are enabled at same time."); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropUserStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropUserStmt.java index aa985751723..e336040d7f8 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropUserStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropUserStmt.java @@ -23,7 +23,7 @@ import org.apache.doris.common.Config; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.UserException; -import org.apache.doris.mysql.authenticate.MysqlAuthType; +import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.privilege.PrivPredicate; import org.apache.doris.qe.ConnectContext; @@ -57,7 +57,7 @@ public class DropUserStmt extends DdlStmt { super.analyze(analyzer); if (Config.access_controller_type.equalsIgnoreCase("ranger-doris") - && MysqlAuthType.getAuthTypeConfig() == MysqlAuthType.LDAP) { + && AuthenticateType.getAuthTypeConfig() == AuthenticateType.LDAP) { throw new AnalysisException("Drop user is prohibited when Ranger and LDAP are enabled at same time."); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java index a70b0c05161..352583362bb 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java @@ -182,6 +182,8 @@ import org.apache.doris.mtmv.MTMVRefreshPartitionSnapshot; import org.apache.doris.mtmv.MTMVRelation; import org.apache.doris.mtmv.MTMVService; import org.apache.doris.mtmv.MTMVStatus; +import org.apache.doris.mysql.authenticate.AuthenticateType; +import org.apache.doris.mysql.authenticate.AuthenticatorManager; import org.apache.doris.mysql.privilege.AccessControllerManager; import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -453,6 +455,8 @@ public class Env { private Auth auth; private AccessControllerManager accessManager; + private AuthenticatorManager authenticatorManager; + private DomainResolver domainResolver; private TabletSchedulerStat stat; @@ -700,6 +704,7 @@ public class Env { this.auth = new Auth(); this.accessManager = new AccessControllerManager(auth); + this.authenticatorManager = new AuthenticatorManager(AuthenticateType.getAuthTypeConfig()); this.domainResolver = new DomainResolver(auth); this.metaContext = new MetaContext(); @@ -824,6 +829,10 @@ public class Env { return accessManager; } + public AuthenticatorManager getAuthenticatorManager() { + return authenticatorManager; + } + public MTMVService getMtmvService() { return mtmvService; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java index 802a5a8ad96..426074f32e3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java @@ -23,7 +23,6 @@ import org.apache.doris.common.DdlException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.datasource.CatalogIf; -import org.apache.doris.mysql.authenticate.MysqlAuth; import org.apache.doris.qe.ConnectContext; import com.google.common.base.Strings; @@ -194,7 +193,8 @@ public class MysqlProto { } // authenticate - if (!MysqlAuth.authenticate(context, qualifiedUser, channel, serializer, authPacket, handshakePacket)) { + if (!Env.getCurrentEnv().getAuthenticatorManager() + .authenticate(context, qualifiedUser, channel, serializer, authPacket, handshakePacket)) { return false; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateRequest.java similarity index 59% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateRequest.java index 9c19e5d9a58..6f19cbbfa1e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateRequest.java @@ -17,22 +17,28 @@ package org.apache.doris.mysql.authenticate; -import org.apache.doris.common.Config; +import org.apache.doris.mysql.authenticate.password.Password; -public enum MysqlAuthType { - DEFAULT, - LDAP; +public class AuthenticateRequest { + private String userName; + private Password password; + private String remoteIp; - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } + public AuthenticateRequest(String userName, Password password, String remoteIp) { + this.userName = userName; + this.password = password; + this.remoteIp = remoteIp; + } + + public String getUserName() { + return userName; + } + + public Password getPassword() { + return password; + } + + public String getRemoteIp() { + return remoteIp; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateResponse.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateResponse.java new file mode 100644 index 00000000000..cab90f81a7f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateResponse.java @@ -0,0 +1,76 @@ +// 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.authenticate; + +import org.apache.doris.analysis.UserIdentity; + +public class AuthenticateResponse { + public static AuthenticateResponse failedResponse = new AuthenticateResponse(false); + + private boolean success; + private UserIdentity userIdentity; + private boolean isTemp = false; + + public AuthenticateResponse(boolean success) { + this.success = success; + } + + public AuthenticateResponse(boolean success, UserIdentity userIdentity) { + this.success = success; + this.userIdentity = userIdentity; + } + + public AuthenticateResponse(boolean success, UserIdentity userIdentity, boolean isTemp) { + this.success = success; + this.userIdentity = userIdentity; + this.isTemp = isTemp; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public UserIdentity getUserIdentity() { + return userIdentity; + } + + public void setUserIdentity(UserIdentity userIdentity) { + this.userIdentity = userIdentity; + } + + public boolean isTemp() { + return isTemp; + } + + public void setTemp(boolean temp) { + isTemp = temp; + } + + @Override + public String toString() { + return "AuthenticateResponse{" + + "success=" + success + + ", userIdentity=" + userIdentity + + ", isTemp=" + isTemp + + '}'; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateType.java similarity index 93% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateType.java index 9c19e5d9a58..0d180eba23d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticateType.java @@ -19,11 +19,11 @@ package org.apache.doris.mysql.authenticate; import org.apache.doris.common.Config; -public enum MysqlAuthType { +public enum AuthenticateType { DEFAULT, LDAP; - public static MysqlAuthType getAuthTypeConfig() { + public static AuthenticateType getAuthTypeConfig() { switch (Config.authentication_type.toLowerCase()) { case "default": return DEFAULT; diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/Authenticator.java similarity index 64% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/Authenticator.java index 9c19e5d9a58..56d045f4340 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/Authenticator.java @@ -17,22 +17,14 @@ package org.apache.doris.mysql.authenticate; -import org.apache.doris.common.Config; +import org.apache.doris.mysql.authenticate.password.PasswordResolver; -public enum MysqlAuthType { - DEFAULT, - LDAP; +import java.io.IOException; - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } - } +public interface Authenticator { + AuthenticateResponse authenticate(AuthenticateRequest request) throws IOException; + + boolean canDeal(String qualifiedUser); + + PasswordResolver getPasswordResolver(); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java new file mode 100644 index 00000000000..c00828f82fa --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java @@ -0,0 +1,83 @@ +// 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.authenticate; + +import org.apache.doris.mysql.MysqlAuthPacket; +import org.apache.doris.mysql.MysqlChannel; +import org.apache.doris.mysql.MysqlHandshakePacket; +import org.apache.doris.mysql.MysqlProto; +import org.apache.doris.mysql.MysqlSerializer; +import org.apache.doris.mysql.authenticate.ldap.LdapAuthenticator; +import org.apache.doris.mysql.authenticate.password.Password; +import org.apache.doris.qe.ConnectContext; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Optional; + +public class AuthenticatorManager { + private static final Logger LOG = LogManager.getLogger(AuthenticatorManager.class); + + private Authenticator defaultAuthenticator; + private Authenticator authTypeAuthenticator; + + public AuthenticatorManager(AuthenticateType type) { + LOG.info("authenticate type: {}", type); + this.defaultAuthenticator = new DefaultAuthenticator(); + switch (type) { + case LDAP: + this.authTypeAuthenticator = new LdapAuthenticator(); + break; + case DEFAULT: + default: + this.authTypeAuthenticator = defaultAuthenticator; + break; + } + } + + public boolean authenticate(ConnectContext context, + String userName, + MysqlChannel channel, + MysqlSerializer serializer, + MysqlAuthPacket authPacket, + MysqlHandshakePacket handshakePacket) throws IOException { + Authenticator authenticator = chooseAuthenticator(userName); + Optional<Password> password = authenticator.getPasswordResolver() + .resolvePassword(context, channel, serializer, authPacket, handshakePacket); + if (!password.isPresent()) { + return false; + } + String remoteIp = context.getMysqlChannel().getRemoteIp(); + AuthenticateRequest request = new AuthenticateRequest(userName, password.get(), remoteIp); + AuthenticateResponse response = authenticator.authenticate(request); + if (!response.isSuccess()) { + MysqlProto.sendResponsePacket(context); + return false; + } + context.setCurrentUserIdentity(response.getUserIdentity()); + context.setRemoteIP(remoteIp); + context.setIsTempUser(response.isTemp()); + return true; + } + + private Authenticator chooseAuthenticator(String userName) { + return authTypeAuthenticator.canDeal(userName) ? authTypeAuthenticator : defaultAuthenticator; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/DefaultAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/DefaultAuthenticator.java new file mode 100644 index 00000000000..7111477ce9e --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/DefaultAuthenticator.java @@ -0,0 +1,75 @@ +// 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.authenticate; + +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.Env; +import org.apache.doris.common.AuthenticationException; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.mysql.authenticate.password.NativePassword; +import org.apache.doris.mysql.authenticate.password.NativePasswordResolver; +import org.apache.doris.mysql.authenticate.password.Password; +import org.apache.doris.mysql.authenticate.password.PasswordResolver; + +import com.google.common.collect.Lists; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.List; + +public class DefaultAuthenticator implements Authenticator { + private static final Logger LOG = LogManager.getLogger(DefaultAuthenticator.class); + private PasswordResolver passwordResolver; + + public DefaultAuthenticator() { + this.passwordResolver = new NativePasswordResolver(); + } + + @Override + public AuthenticateResponse authenticate(AuthenticateRequest request) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("user:{} start to default authenticate.", request.getUserName()); + } + Password password = request.getPassword(); + if (!(password instanceof NativePassword)) { + return AuthenticateResponse.failedResponse; + } + NativePassword nativePassword = (NativePassword) password; + + List<UserIdentity> currentUserIdentity = Lists.newArrayList(); + try { + Env.getCurrentEnv().getAuth().checkPassword(request.getUserName(), request.getRemoteIp(), + nativePassword.getRemotePasswd(), nativePassword.getRandomString(), currentUserIdentity); + } catch (AuthenticationException e) { + ErrorReport.report(e.errorCode, e.msgs); + return AuthenticateResponse.failedResponse; + } + return new AuthenticateResponse(true, currentUserIdentity.get(0)); + } + + @Override + public boolean canDeal(String qualifiedUser) { + return true; + } + + @Override + public PasswordResolver getPasswordResolver() { + return passwordResolver; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuth.java deleted file mode 100644 index bb26c20796e..00000000000 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuth.java +++ /dev/null @@ -1,205 +0,0 @@ -// 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.authenticate; - -import org.apache.doris.analysis.UserIdentity; -import org.apache.doris.catalog.Env; -import org.apache.doris.common.AuthenticationException; -import org.apache.doris.common.Config; -import org.apache.doris.common.ErrorCode; -import org.apache.doris.common.ErrorReport; -import org.apache.doris.mysql.MysqlAuthPacket; -import org.apache.doris.mysql.MysqlAuthSwitchPacket; -import org.apache.doris.mysql.MysqlChannel; -import org.apache.doris.mysql.MysqlClearTextPacket; -import org.apache.doris.mysql.MysqlHandshakePacket; -import org.apache.doris.mysql.MysqlProto; -import org.apache.doris.mysql.MysqlSerializer; -import org.apache.doris.mysql.authenticate.ldap.LdapAuthenticate; -import org.apache.doris.mysql.privilege.Auth; -import org.apache.doris.qe.ConnectContext; - -import com.google.common.collect.Lists; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.List; - - -public class MysqlAuth { - private static final Logger LOG = LogManager.getLogger(MysqlAuth.class); - - // scramble: data receive from server. - // randomString: data send by server in plugin data field - // user_name#HIGH@cluster_name - private static boolean internalAuthenticate(ConnectContext context, byte[] scramble, - byte[] randomString, String qualifiedUser) { - String remoteIp = context.getMysqlChannel().getRemoteIp(); - List<UserIdentity> currentUserIdentity = Lists.newArrayList(); - - try { - Env.getCurrentEnv().getAuth().checkPassword(qualifiedUser, remoteIp, - scramble, randomString, currentUserIdentity); - } catch (AuthenticationException e) { - ErrorReport.report(e.errorCode, e.msgs); - return false; - } - - context.setCurrentUserIdentity(currentUserIdentity.get(0)); - context.setRemoteIP(remoteIp); - return true; - } - - // Default auth uses doris internal user system to authenticate. - private static boolean defaultAuth( - ConnectContext context, - String qualifiedUser, - MysqlChannel channel, - MysqlSerializer serializer, - MysqlAuthPacket authPacket, - MysqlHandshakePacket handshakePacket) throws IOException { - // Starting with MySQL 8.0.4, MySQL changed the default authentication plugin for MySQL client - // from mysql_native_password to caching_sha2_password. - // ref: https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/ - // So, User use mysql client or ODBC Driver after 8.0.4 have problem to connect to Doris - // with password. - // So Doris support the Protocol::AuthSwitchRequest to tell client to keep the default password plugin - // which Doris is using now. - // Note: Check the authPacket whether support plugin auth firstly, - // before we check AuthPlugin between doris and client to compatible with older version: like mysql 5.1 - if (authPacket.getCapability().isPluginAuth() - && !handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) { - // 1. clear the serializer - serializer.reset(); - // 2. build the auth switch request and send to the client - handshakePacket.buildAuthSwitchRequest(serializer); - channel.sendAndFlush(serializer.toByteBuffer()); - // Server receive auth switch response packet from client. - ByteBuffer authSwitchResponse = channel.fetchOnePacket(); - if (authSwitchResponse == null) { - // receive response failed. - return false; - } - // 3. the client use default password plugin of Doris to dispose - // password - authPacket.setAuthResponse(MysqlProto.readEofString(authSwitchResponse)); - } - - // NOTE: when we behind proxy, we need random string sent by proxy. - byte[] randomString = handshakePacket.getAuthPluginData(); - if (Config.proxy_auth_enable && authPacket.getRandomString() != null) { - randomString = authPacket.getRandomString(); - } - // check authenticate - if (!internalAuthenticate(context, authPacket.getAuthResponse(), randomString, qualifiedUser)) { - MysqlProto.sendResponsePacket(context); - return false; - } - return true; - } - - /* - * ldap: - * server ---AuthSwitch---> client - * server <--- clear text password --- client - */ - private static boolean ldapAuth( - ConnectContext context, - String qualifiedUser, - MysqlChannel channel, - MysqlSerializer serializer) throws IOException { - if (LOG.isDebugEnabled()) { - LOG.debug("user:{} start to ldap authenticate.", qualifiedUser); - } - // server send authentication switch packet to request password clear text. - // https://dev.mysql.com/doc/internals/en/authentication-method-change.html - serializer.reset(); - MysqlAuthSwitchPacket mysqlAuthSwitchPacket = new MysqlAuthSwitchPacket(); - mysqlAuthSwitchPacket.writeTo(serializer); - channel.sendAndFlush(serializer.toByteBuffer()); - - // Server receive password clear text. - ByteBuffer authSwitchResponse = channel.fetchOnePacket(); - if (authSwitchResponse == null) { - return false; - } - MysqlClearTextPacket clearTextPacket = new MysqlClearTextPacket(); - if (!clearTextPacket.readFrom(authSwitchResponse)) { - ErrorReport.report(ErrorCode.ERR_NOT_SUPPORTED_AUTH_MODE); - MysqlProto.sendResponsePacket(context); - return false; - } - if (!LdapAuthenticate.authenticate(context, clearTextPacket.getPassword(), qualifiedUser)) { - MysqlProto.sendResponsePacket(context); - return false; - } - return true; - } - - // Based on FE configuration and some prerequisites, decide which authentication type to actually use - private static MysqlAuthType useWhichAuthType(ConnectContext context, String qualifiedUser) throws IOException { - MysqlAuthType typeConfig = MysqlAuthType.getAuthTypeConfig(); - - // Root and admin are internal users of the Doris. - // They are used to set the ldap admin password. - // Cannot use external authentication. - if (qualifiedUser.equals(Auth.ROOT_USER) || qualifiedUser.equals(Auth.ADMIN_USER)) { - return MysqlAuthType.DEFAULT; - } - - // precondition - switch (typeConfig) { - case LDAP: - try { - // If LDAP authentication is enabled and the user exists in LDAP, use LDAP authentication, - // otherwise use Doris internal authentication. - if (!Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser)) { - return MysqlAuthType.DEFAULT; - } - } catch (Exception e) { - // TODO: can we catch exception here? - LOG.warn("Check if user exists in ldap error.", e); - MysqlProto.sendResponsePacket(context); - return MysqlAuthType.DEFAULT; - } - break; - default: - } - return typeConfig; - } - - public static boolean authenticate( - ConnectContext context, - String qualifiedUser, - MysqlChannel channel, - MysqlSerializer serializer, - MysqlAuthPacket authPacket, - MysqlHandshakePacket handshakePacket) throws IOException { - MysqlAuthType authType = useWhichAuthType(context, qualifiedUser); - switch (authType) { - case DEFAULT: - return defaultAuth(context, qualifiedUser, channel, serializer, authPacket, handshakePacket); - case LDAP: - return ldapAuth(context, qualifiedUser, channel, serializer); - default: - } - return false; - } -} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticate.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java similarity index 57% rename from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticate.java rename to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java index ee22aecc40d..e37112372ce 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticate.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java @@ -22,12 +22,20 @@ import org.apache.doris.catalog.Env; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; -import org.apache.doris.qe.ConnectContext; +import org.apache.doris.mysql.authenticate.AuthenticateRequest; +import org.apache.doris.mysql.authenticate.AuthenticateResponse; +import org.apache.doris.mysql.authenticate.Authenticator; +import org.apache.doris.mysql.authenticate.password.ClearPassword; +import org.apache.doris.mysql.authenticate.password.ClearPasswordResolver; +import org.apache.doris.mysql.authenticate.password.Password; +import org.apache.doris.mysql.authenticate.password.PasswordResolver; +import org.apache.doris.mysql.privilege.Auth; import com.google.common.base.Strings; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.io.IOException; import java.util.List; /** @@ -35,8 +43,43 @@ import java.util.List; * This means that users can log in to Doris with a user name and LDAP password, * and the user will get the privileges of all roles corresponding to the LDAP group. */ -public class LdapAuthenticate { - private static final Logger LOG = LogManager.getLogger(LdapAuthenticate.class); +public class LdapAuthenticator implements Authenticator { + private static final Logger LOG = LogManager.getLogger(LdapAuthenticator.class); + + private PasswordResolver passwordResolver; + + public LdapAuthenticator() { + this.passwordResolver = new ClearPasswordResolver(); + } + + /* + * ldap: + * server ---AuthSwitch---> client + * server <--- clear text password --- client + */ + @Override + public AuthenticateResponse authenticate(AuthenticateRequest request) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("user:{} start to ldap authenticate.", request.getUserName()); + } + Password password = request.getPassword(); + if (!(password instanceof ClearPassword)) { + return AuthenticateResponse.failedResponse; + } + ClearPassword clearPassword = (ClearPassword) password; + return internalAuthenticate(clearPassword.getPassword(), request.getUserName(), request.getRemoteIp()); + } + + @Override + public boolean canDeal(String qualifiedUser) { + if (qualifiedUser.equals(Auth.ROOT_USER) || qualifiedUser.equals(Auth.ADMIN_USER)) { + return false; + } + if (!Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser)) { + return false; + } + return true; + } /** * The LDAP authentication process is as follows: @@ -45,7 +88,7 @@ public class LdapAuthenticate { * step3: Set current userIdentity. If the user account does not exist in Doris, login as a temporary user. * Otherwise, login to the Doris account. */ - public static boolean authenticate(ConnectContext context, String password, String qualifiedUser) { + private AuthenticateResponse internalAuthenticate(String password, String qualifiedUser, String remoteIp) { String usePasswd = (Strings.isNullOrEmpty(password)) ? "NO" : "YES"; String userName = ClusterNamespace.getNameFromFullName(qualifiedUser); if (LOG.isDebugEnabled()) { @@ -56,35 +99,36 @@ public class LdapAuthenticate { try { if (!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, password)) { LOG.info("user:{} use check LDAP password failed.", userName); - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, context.getRemoteIP(), usePasswd); - return false; + ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd); + return AuthenticateResponse.failedResponse; } } catch (Exception e) { LOG.error("Check ldap password error.", e); - return false; + return AuthenticateResponse.failedResponse; } - String remoteIp = context.getMysqlChannel().getRemoteIp(); UserIdentity tempUserIdentity = UserIdentity.createAnalyzedUserIdentWithIp(qualifiedUser, remoteIp); // Search the user in doris. List<UserIdentity> userIdentities = Env.getCurrentEnv().getAuth() .getUserIdentityForLdap(qualifiedUser, remoteIp); - UserIdentity userIdentity; + AuthenticateResponse response = new AuthenticateResponse(true); if (userIdentities.isEmpty()) { - userIdentity = tempUserIdentity; + response.setUserIdentity(tempUserIdentity); if (LOG.isDebugEnabled()) { LOG.debug("User:{} does not exists in doris, login as temporary users.", userName); } - context.setIsTempUser(true); + response.setTemp(true); } else { - userIdentity = userIdentities.get(0); + response.setUserIdentity(userIdentities.get(0)); } - - context.setCurrentUserIdentity(userIdentity); - context.setRemoteIP(remoteIp); if (LOG.isDebugEnabled()) { - LOG.debug("ldap authentication success: identity:{}", context.getCurrentUserIdentity()); + LOG.debug("ldap authentication success: identity:{}", response.getUserIdentity()); } - return true; + return response; + } + + @Override + public PasswordResolver getPasswordResolver() { + return passwordResolver; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java index 2accb404237..5f6003cd6c1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java @@ -25,7 +25,7 @@ import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AnalysisException; import org.apache.doris.common.DdlException; import org.apache.doris.common.LdapConfig; -import org.apache.doris.mysql.authenticate.MysqlAuthType; +import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.PrivBitSet; import org.apache.doris.mysql.privilege.Privilege; @@ -103,7 +103,7 @@ public class LdapManager { public boolean checkUserPasswd(String fullName, String passwd) { String userName = ClusterNamespace.getNameFromFullName(fullName); - if (MysqlAuthType.getAuthTypeConfig() != MysqlAuthType.LDAP || Strings.isNullOrEmpty(userName) + if (AuthenticateType.getAuthTypeConfig() != AuthenticateType.LDAP || Strings.isNullOrEmpty(userName) || Objects.isNull(passwd)) { return false; } @@ -137,7 +137,7 @@ public class LdapManager { } private boolean checkParam(String fullName) { - return MysqlAuthType.getAuthTypeConfig() == MysqlAuthType.LDAP + return AuthenticateType.getAuthTypeConfig() == AuthenticateType.LDAP && !Strings.isNullOrEmpty(fullName) && !fullName.equalsIgnoreCase(Auth.ROOT_USER) && !fullName.equalsIgnoreCase(Auth.ADMIN_USER); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPassword.java similarity index 61% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPassword.java index 9c19e5d9a58..47d82a3dd08 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPassword.java @@ -15,24 +15,16 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.mysql.authenticate; +package org.apache.doris.mysql.authenticate.password; -import org.apache.doris.common.Config; +public class ClearPassword extends Password { + private String password; -public enum MysqlAuthType { - DEFAULT, - LDAP; + public ClearPassword(String password) { + this.password = password; + } - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } + public String getPassword() { + return password; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPasswordResolver.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPasswordResolver.java new file mode 100644 index 00000000000..e11f38b1948 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/ClearPasswordResolver.java @@ -0,0 +1,60 @@ +// 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.authenticate.password; + +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.mysql.MysqlAuthPacket; +import org.apache.doris.mysql.MysqlAuthSwitchPacket; +import org.apache.doris.mysql.MysqlChannel; +import org.apache.doris.mysql.MysqlClearTextPacket; +import org.apache.doris.mysql.MysqlHandshakePacket; +import org.apache.doris.mysql.MysqlProto; +import org.apache.doris.mysql.MysqlSerializer; +import org.apache.doris.qe.ConnectContext; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Optional; + +public class ClearPasswordResolver implements PasswordResolver { + @Override + public Optional<Password> resolvePassword(ConnectContext context, MysqlChannel channel, MysqlSerializer serializer, + MysqlAuthPacket authPacket, + MysqlHandshakePacket handshakePacket) throws IOException { + // server send authentication switch packet to request password clear text. + // https://dev.mysql.com/doc/internals/en/authentication-method-change.html + serializer.reset(); + MysqlAuthSwitchPacket mysqlAuthSwitchPacket = new MysqlAuthSwitchPacket(); + mysqlAuthSwitchPacket.writeTo(serializer); + channel.sendAndFlush(serializer.toByteBuffer()); + + // Server receive password clear text. + ByteBuffer authSwitchResponse = channel.fetchOnePacket(); + if (authSwitchResponse == null) { + return Optional.empty(); + } + MysqlClearTextPacket clearTextPacket = new MysqlClearTextPacket(); + if (!clearTextPacket.readFrom(authSwitchResponse)) { + ErrorReport.report(ErrorCode.ERR_NOT_SUPPORTED_AUTH_MODE); + MysqlProto.sendResponsePacket(context); + return Optional.empty(); + } + return Optional.of(new ClearPassword(clearTextPacket.getPassword())); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePassword.java similarity index 61% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePassword.java index 9c19e5d9a58..b88d3077d84 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePassword.java @@ -15,24 +15,22 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.mysql.authenticate; +package org.apache.doris.mysql.authenticate.password; -import org.apache.doris.common.Config; +public class NativePassword extends Password { + private byte[] remotePasswd; + private byte[] randomString; -public enum MysqlAuthType { - DEFAULT, - LDAP; + public NativePassword(byte[] remotePasswd, byte[] randomString) { + this.remotePasswd = remotePasswd; + this.randomString = randomString; + } + + public byte[] getRemotePasswd() { + return remotePasswd; + } - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } + public byte[] getRandomString() { + return randomString; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePasswordResolver.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePasswordResolver.java new file mode 100644 index 00000000000..e36eb1ab5dc --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/NativePasswordResolver.java @@ -0,0 +1,71 @@ +// 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.authenticate.password; + +import org.apache.doris.common.Config; +import org.apache.doris.mysql.MysqlAuthPacket; +import org.apache.doris.mysql.MysqlChannel; +import org.apache.doris.mysql.MysqlHandshakePacket; +import org.apache.doris.mysql.MysqlProto; +import org.apache.doris.mysql.MysqlSerializer; +import org.apache.doris.qe.ConnectContext; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Optional; + +public class NativePasswordResolver implements PasswordResolver { + @Override + public Optional<Password> resolvePassword(ConnectContext context, MysqlChannel channel, MysqlSerializer serializer, + MysqlAuthPacket authPacket, + MysqlHandshakePacket handshakePacket) throws IOException { + // Starting with MySQL 8.0.4, MySQL changed the default authentication plugin for MySQL client + // from mysql_native_password to caching_sha2_password. + // ref: https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/ + // So, User use mysql client or ODBC Driver after 8.0.4 have problem to connect to Doris + // with password. + // So Doris support the Protocol::AuthSwitchRequest to tell client to keep the default password plugin + // which Doris is using now. + // Note: Check the authPacket whether support plugin auth firstly, + // before we check AuthPlugin between doris and client to compatible with older version: like mysql 5.1 + if (authPacket.getCapability().isPluginAuth() + && !handshakePacket.checkAuthPluginSameAsDoris(authPacket.getPluginName())) { + // 1. clear the serializer + serializer.reset(); + // 2. build the auth switch request and send to the client + handshakePacket.buildAuthSwitchRequest(serializer); + channel.sendAndFlush(serializer.toByteBuffer()); + // Server receive auth switch response packet from client. + ByteBuffer authSwitchResponse = channel.fetchOnePacket(); + if (authSwitchResponse == null) { + // receive response failed. + return Optional.empty(); + } + // 3. the client use default password plugin of Doris to dispose + // password + authPacket.setAuthResponse(MysqlProto.readEofString(authSwitchResponse)); + } + + // NOTE: when we behind proxy, we need random string sent by proxy. + byte[] randomString = handshakePacket.getAuthPluginData(); + if (Config.proxy_auth_enable && authPacket.getRandomString() != null) { + randomString = authPacket.getRandomString(); + } + return Optional.of(new NativePassword(authPacket.getAuthResponse(), randomString)); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/Password.java similarity index 60% copy from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java copy to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/Password.java index 9c19e5d9a58..1ed2c95bd66 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/Password.java @@ -15,24 +15,8 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.mysql.authenticate; +package org.apache.doris.mysql.authenticate.password; -import org.apache.doris.common.Config; +public abstract class Password { -public enum MysqlAuthType { - DEFAULT, - LDAP; - - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } - } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/PasswordResolver.java similarity index 56% rename from fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java rename to fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/PasswordResolver.java index 9c19e5d9a58..84855320022 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/MysqlAuthType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/password/PasswordResolver.java @@ -15,24 +15,20 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.mysql.authenticate; +package org.apache.doris.mysql.authenticate.password; -import org.apache.doris.common.Config; +import org.apache.doris.mysql.MysqlAuthPacket; +import org.apache.doris.mysql.MysqlChannel; +import org.apache.doris.mysql.MysqlHandshakePacket; +import org.apache.doris.mysql.MysqlSerializer; +import org.apache.doris.qe.ConnectContext; -public enum MysqlAuthType { - DEFAULT, - LDAP; +import java.io.IOException; +import java.util.Optional; - public static MysqlAuthType getAuthTypeConfig() { - switch (Config.authentication_type.toLowerCase()) { - case "default": - return DEFAULT; - case "ldap": - return LDAP; - // add other authentication system here - // case otherAuthType: - default: - return DEFAULT; - } - } +public interface PasswordResolver { + Optional<Password> resolvePassword(ConnectContext context, MysqlChannel channel, + MysqlSerializer serializer, + MysqlAuthPacket authPacket, + MysqlHandshakePacket handshakePacket) throws IOException; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java index ef3e264487d..59ff2da6bec 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java @@ -53,7 +53,7 @@ import org.apache.doris.common.UserException; import org.apache.doris.common.io.Writable; import org.apache.doris.datasource.InternalCatalog; import org.apache.doris.mysql.MysqlPassword; -import org.apache.doris.mysql.authenticate.MysqlAuthType; +import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.authenticate.ldap.LdapManager; import org.apache.doris.mysql.authenticate.ldap.LdapUserInfo; import org.apache.doris.persist.AlterUserOperationLog; @@ -419,7 +419,7 @@ public class Auth implements Writable { // Check if LDAP authentication is enabled. private boolean isLdapAuthEnabled() { - return MysqlAuthType.getAuthTypeConfig() == MysqlAuthType.LDAP; + return AuthenticateType.getAuthTypeConfig() == AuthenticateType.LDAP; } // create user diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java index 26239f18457..4ab2ba275de 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java @@ -20,12 +20,13 @@ package org.apache.doris.mysql; import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.Env; -import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.AuthenticationException; import org.apache.doris.common.Config; import org.apache.doris.common.DdlException; import org.apache.doris.datasource.InternalCatalog; -import org.apache.doris.mysql.authenticate.ldap.LdapAuthenticate; +import org.apache.doris.mysql.authenticate.AuthenticateRequest; +import org.apache.doris.mysql.authenticate.AuthenticatorManager; +import org.apache.doris.mysql.authenticate.ldap.LdapAuthenticator; import org.apache.doris.mysql.authenticate.ldap.LdapManager; import org.apache.doris.mysql.privilege.AccessControllerManager; import org.apache.doris.mysql.privilege.Auth; @@ -65,14 +66,16 @@ public class MysqlProtoTest { @Mocked private LdapManager ldapManager; @Mocked - private LdapAuthenticate ldapAuthenticate; - @Mocked private MysqlClearTextPacket clearTextPacket; @Mocked - StreamConnection streamConnection; + private StreamConnection streamConnection; + @Mocked + private LdapAuthenticator ldapAuthenticator; + @Mocked + private AuthenticatorManager authenticatorManager; @Before - public void setUp() throws DdlException, AuthenticationException { + public void setUp() throws DdlException, AuthenticationException, IOException { // mock auth new Expectations() { @@ -95,6 +98,15 @@ public class MysqlProtoTest { minTimes = 0; result = catalog; + env.getAuthenticatorManager(); + minTimes = 0; + result = authenticatorManager; + + authenticatorManager.authenticate((ConnectContext) any, anyString, (MysqlChannel) any, + (MysqlSerializer) any, (MysqlAuthPacket) any, (MysqlHandshakePacket) any); + minTimes = 0; + result = true; + catalog.getDbNullable(anyString); minTimes = 0; result = new Database(); @@ -215,19 +227,14 @@ public class MysqlProtoTest { private void mockAccess() throws Exception { } - private void mockLdap(String user, boolean userExist) { + private void mockLdap(boolean userExist) throws IOException { Config.authentication_type = "ldap"; new Expectations() { { - LdapAuthenticate.authenticate((ConnectContext) any, anyString, anyString); + ldapAuthenticator.authenticate((AuthenticateRequest) any); minTimes = 0; - result = new Delegate() { - boolean fakeLdapAuthenticate(ConnectContext context, String password, String qualifiedUser) { - return password.equals(PASSWORD_CLEAR_TEXT) - && ClusterNamespace.getNameFromFullName(qualifiedUser).equals(user); - } - }; + result = true; ldapManager.checkUserPasswd(anyString, anyString); minTimes = 0; @@ -285,7 +292,7 @@ public class MysqlProtoTest { mockPassword(true); mockAccess(); mockMysqlClearTextPacket(PASSWORD_CLEAR_TEXT); - mockLdap("user", true); + mockLdap(true); ConnectContext context = new ConnectContext(streamConnection); context.setEnv(env); context.setThreadLocalInfo(); @@ -293,26 +300,12 @@ public class MysqlProtoTest { Config.authentication_type = "default"; } - @Test - public void testNegotiateLdapInvalidPasswd() throws Exception { - mockChannel("user", true); - mockPassword(true); - mockAccess(); - mockMysqlClearTextPacket("654321"); - mockLdap("user", true); - ConnectContext context = new ConnectContext(streamConnection); - context.setEnv(env); - context.setThreadLocalInfo(); - Assert.assertFalse(MysqlProto.negotiate(context)); - Config.authentication_type = "default"; - } - @Test public void testNegotiateLdapRoot() throws Exception { mockChannel("root", true); mockPassword(true); mockAccess(); - mockLdap("root", false); + mockLdap(false); mockMysqlClearTextPacket("654321"); ConnectContext context = new ConnectContext(streamConnection); context.setEnv(env); diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/DefaultAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/DefaultAuthenticatorTest.java new file mode 100644 index 00000000000..04fd28dea28 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/DefaultAuthenticatorTest.java @@ -0,0 +1,100 @@ +// 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.authenticate; + +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.common.AuthenticationException; +import org.apache.doris.common.DdlException; +import org.apache.doris.mysql.authenticate.password.NativePassword; +import org.apache.doris.mysql.authenticate.password.NativePasswordResolver; +import org.apache.doris.mysql.privilege.Auth; + +import mockit.Delegate; +import mockit.Expectations; +import mockit.Mocked; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +public class DefaultAuthenticatorTest { + private static final String USER_NAME = "user"; + private static final String IP = "192.168.1.1"; + + @Mocked + private Auth auth; + + private DefaultAuthenticator defaultAuthenticator = new DefaultAuthenticator(); + private AuthenticateRequest request = new AuthenticateRequest(USER_NAME, + new NativePassword(new byte[2], new byte[2]), IP); + + + @Before + public void setUp() throws DdlException, AuthenticationException, IOException { + + // mock auth + new Expectations() { + { + auth.checkPassword(anyString, anyString, (byte[]) any, (byte[]) any, (List<UserIdentity>) any); + minTimes = 0; + result = new Delegate() { + void fakeCheckPassword(String remoteUser, String remoteHost, byte[] remotePasswd, + byte[] randomString, List<UserIdentity> currentUser) { + UserIdentity userIdentity = new UserIdentity(USER_NAME, IP); + currentUser.add(userIdentity); + } + }; + } + }; + } + + + @Test + public void testAuthenticate() throws IOException { + AuthenticateResponse response = defaultAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + Assert.assertFalse(response.isTemp()); + Assert.assertEquals("'user'@'192.168.1.1'", response.getUserIdentity().toString()); + } + + @Test + public void testAuthenticateFailed() throws IOException, AuthenticationException { + new Expectations() { + { + auth.checkPassword(anyString, anyString, (byte[]) any, (byte[]) any, (List<UserIdentity>) any); + minTimes = 0; + result = new AuthenticationException("exception"); + } + }; + AuthenticateResponse response = defaultAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + } + + + @Test + public void testCanDeal() { + Assert.assertTrue(defaultAuthenticator.canDeal("ss")); + } + + @Test + public void testGetPasswordResolver() { + Assert.assertTrue(defaultAuthenticator.getPasswordResolver() instanceof NativePasswordResolver); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticateTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticateTest.java deleted file mode 100644 index cee3feb6c46..00000000000 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticateTest.java +++ /dev/null @@ -1,203 +0,0 @@ -// 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.authenticate.ldap; - -import org.apache.doris.analysis.UserIdentity; -import org.apache.doris.catalog.Env; -import org.apache.doris.common.DdlException; -import org.apache.doris.mysql.privilege.AccessControllerManager; -import org.apache.doris.mysql.privilege.Auth; -import org.apache.doris.mysql.privilege.Role; -import org.apache.doris.qe.ConnectContext; - -import com.google.common.collect.Sets; -import mockit.Delegate; -import mockit.Expectations; -import mockit.Mocked; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.List; - -public class LdapAuthenticateTest { - private static final String USER_NAME = "user"; - private static final String IP = "192.168.1.1"; - private static final String TABLE_RD = "palo_rd"; - - private Role ldapGroupsPrivs; - - @Mocked - private LdapManager ldapManager; - @Mocked - private Env env; - @Mocked - private Auth auth; - @Mocked - private AccessControllerManager accessManager; - - @Before - public void setUp() throws DdlException { - new Expectations() { - { - auth.doesRoleExist(anyString); - minTimes = 0; - result = true; - - auth.mergeRolesNoCheckName((List<String>) any, (Role) any); - minTimes = 0; - result = new Delegate() { - void fakeMergeRolesNoCheckName(List<String> roles, Role savedRole) { - ldapGroupsPrivs = savedRole; - } - }; - - env.getAccessManager(); - minTimes = 0; - result = accessManager; - - env.getAuth(); - minTimes = 0; - result = auth; - - Env.getCurrentEnv(); - minTimes = 0; - result = env; - } - }; - } - - private void setCheckPassword(boolean res) { - new Expectations() { - { - ldapManager.checkUserPasswd(anyString, anyString); - minTimes = 0; - result = res; - } - }; - } - - private void setCheckPasswordException() { - new Expectations() { - { - ldapManager.checkUserPasswd(anyString, anyString); - minTimes = 0; - result = new RuntimeException("exception"); - } - }; - } - - private void setGetUserInfo(boolean res) { - new Expectations() { - { - if (res) { - ldapManager.getUserInfo(anyString); - minTimes = 0; - result = new Delegate() { - LdapUserInfo fakeGetGroups(String user) { - return new LdapUserInfo(anyString, false, "", Sets.newHashSet(new Role(anyString))); - } - }; - } else { - ldapManager.getUserInfo(anyString); - minTimes = 0; - result = null; - } - } - }; - } - - private void setGetCurrentUserIdentity(boolean res) { - new Expectations() { - { - if (res) { - auth.getCurrentUserIdentity((UserIdentity) any); - minTimes = 0; - result = new UserIdentity(USER_NAME, IP); - } else { - auth.getCurrentUserIdentity((UserIdentity) any); - minTimes = 0; - result = null; - } - } - }; - } - - private ConnectContext getContext() { - ConnectContext context = new ConnectContext(); - context.setEnv(env); - context.setThreadLocalInfo(); - return context; - } - - - @Test - public void testAuthenticate() { - ConnectContext context = getContext(); - setCheckPassword(true); - setGetUserInfo(true); - setGetCurrentUserIdentity(true); - String qualifiedUser = USER_NAME; - Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser)); - Assert.assertTrue(context.getIsTempUser()); - } - - @Test - public void testAuthenticateWithWrongPassword() { - ConnectContext context = getContext(); - setCheckPassword(false); - setGetUserInfo(true); - setGetCurrentUserIdentity(true); - String qualifiedUser = USER_NAME; - Assert.assertFalse(LdapAuthenticate.authenticate(context, "123", qualifiedUser)); - Assert.assertFalse(context.getIsTempUser()); - } - - @Test - public void testAuthenticateWithCheckPasswordException() { - ConnectContext context = getContext(); - setCheckPasswordException(); - setGetUserInfo(true); - setGetCurrentUserIdentity(true); - String qualifiedUser = USER_NAME; - Assert.assertFalse(LdapAuthenticate.authenticate(context, "123", qualifiedUser)); - Assert.assertFalse(context.getIsTempUser()); - } - - @Test - public void testAuthenticateGetGroupsNull() { - ConnectContext context = getContext(); - setCheckPassword(true); - setGetUserInfo(false); - setGetCurrentUserIdentity(true); - String qualifiedUser = USER_NAME; - Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser)); - Assert.assertTrue(context.getIsTempUser()); - } - - @Test - public void testAuthenticateUserNotExistInDoris() { - ConnectContext context = getContext(); - setCheckPassword(true); - setGetUserInfo(true); - setGetCurrentUserIdentity(false); - String qualifiedUser = USER_NAME; - Assert.assertTrue(LdapAuthenticate.authenticate(context, "123", qualifiedUser)); - Assert.assertTrue(context.getIsTempUser()); - } -} diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java new file mode 100644 index 00000000000..99cbcdb5fad --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -0,0 +1,146 @@ +// 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.authenticate.ldap; + +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.mysql.authenticate.AuthenticateRequest; +import org.apache.doris.mysql.authenticate.AuthenticateResponse; +import org.apache.doris.mysql.authenticate.password.ClearPassword; +import org.apache.doris.mysql.authenticate.password.ClearPasswordResolver; +import org.apache.doris.mysql.privilege.Auth; + +import com.google.common.collect.Lists; +import mockit.Expectations; +import mockit.Mocked; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +public class LdapAuthenticatorTest { + private static final String USER_NAME = "user"; + private static final String IP = "192.168.1.1"; + + @Mocked + private LdapManager ldapManager; + + @Mocked + private Auth auth; + + private LdapAuthenticator ldapAuthenticator = new LdapAuthenticator(); + private AuthenticateRequest request = new AuthenticateRequest(USER_NAME, new ClearPassword("123"), IP); + + private void setCheckPassword(boolean res) { + new Expectations() { + { + ldapManager.checkUserPasswd(anyString, anyString); + minTimes = 0; + result = res; + } + }; + } + + private void setCheckPasswordException() { + new Expectations() { + { + ldapManager.checkUserPasswd(anyString, anyString); + minTimes = 0; + result = new RuntimeException("exception"); + } + }; + } + + private void setGetUserInDoris(boolean res) { + new Expectations() { + { + if (res) { + List<UserIdentity> list = Lists.newArrayList(new UserIdentity(USER_NAME, IP)); + auth.getUserIdentityForLdap(anyString, anyString); + minTimes = 0; + result = list; + } else { + auth.getCurrentUserIdentity((UserIdentity) any); + minTimes = 0; + result = null; + } + } + }; + } + + private void setLdapUserExist(boolean res) { + new Expectations() { + { + ldapManager.doesUserExist(anyString); + minTimes = 0; + result = res; + } + }; + } + + @Test + public void testAuthenticate() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + Assert.assertFalse(response.isTemp()); + Assert.assertEquals("'user'@'192.168.1.1'", response.getUserIdentity().toString()); + } + + @Test + public void testAuthenticateWithWrongPassword() throws IOException { + setCheckPassword(false); + setGetUserInDoris(true); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + } + + @Test + public void testAuthenticateWithCheckPasswordException() throws IOException { + setCheckPasswordException(); + setGetUserInDoris(true); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + } + + @Test + public void testAuthenticateUserNotExistInDoris() throws IOException { + setCheckPassword(true); + setGetUserInDoris(false); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + Assert.assertTrue(response.isTemp()); + Assert.assertEquals("'user'@'192.168.1.1'", response.getUserIdentity().toString()); + } + + @Test + public void testCanDeal() { + setLdapUserExist(true); + Assert.assertFalse(ldapAuthenticator.canDeal(Auth.ROOT_USER)); + Assert.assertFalse(ldapAuthenticator.canDeal(Auth.ADMIN_USER)); + Assert.assertTrue(ldapAuthenticator.canDeal("ss")); + setLdapUserExist(false); + Assert.assertFalse(ldapAuthenticator.canDeal("ss")); + } + + @Test + public void testGetPasswordResolver() { + Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof ClearPasswordResolver); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org