Skip to content

Commit

Permalink
[Refactor] Separate the Author and Authentication Analyzers (StarRock…
Browse files Browse the repository at this point in the history
…s#49234)

Signed-off-by: HangyuanLiu <[email protected]>
  • Loading branch information
HangyuanLiu authored Aug 1, 2024
1 parent 6eea7aa commit 2be5b01
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,6 @@ public UserIdentity checkPlainPassword(String remoteUser, String remoteHost, Str
remotePasswd.getBytes(StandardCharsets.UTF_8), null);
}

public void checkPasswordReuse(UserIdentity user, String plainPassword) throws DdlException {
if (Config.enable_password_reuse) {
return;
}
if (checkPlainPassword(user.getUser(), user.getHost(), plainPassword) != null) {
throw new DdlException("password should not be the same as the previous one!");
}
}

public void createUser(CreateUserStmt stmt) throws DdlException {
UserIdentity userIdentity = stmt.getUserIdentity();
UserAuthenticationInfo info = stmt.getAuthenticationInfo();
Expand Down
25 changes: 13 additions & 12 deletions fe/fe-core/src/main/java/com/starrocks/sql/analyzer/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -696,71 +696,72 @@ public Void visitCancelLoadStatement(CancelLoadStmt statement, ConnectContext co
return null;
}

// ---------------------------------------- Privilege Statement ------------------------------------------------
// ---------------------------------------- Authentication Statement ------------------------------------------------

@Override
public Void visitBaseCreateAlterUserStmt(BaseCreateAlterUserStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthenticationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitDropUserStatement(DropUserStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthenticationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitShowAuthenticationStatement(ShowAuthenticationStmt statement, ConnectContext context) {
PrivilegeStmtAnalyzer.analyze(statement, context);
AuthenticationAnalyzer.analyze(statement, context);
return null;
}

@Override
public Void visitExecuteAsStatement(ExecuteAsStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthenticationAnalyzer.analyze(stmt, session);
return null;
}

// ---------------------------------------- Authorization Statement ------------------------------------------------
@Override
public Void visitCreateRoleStatement(CreateRoleStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitDropRoleStatement(DropRoleStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitGrantRevokeRoleStatement(BaseGrantRevokeRoleStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitSetRoleStatement(SetRoleStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitSetDefaultRoleStatement(SetDefaultRoleStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitGrantRevokePrivilegeStatement(BaseGrantRevokePrivilegeStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

@Override
public Void visitShowGrantsStatement(ShowGrantsStmt stmt, ConnectContext session) {
PrivilegeStmtAnalyzer.analyze(stmt, session);
AuthorizationAnalyzer.analyze(stmt, session);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2021-present StarRocks, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.starrocks.sql.analyzer;

import com.google.common.base.Strings;
import com.starrocks.authentication.AuthenticationException;
import com.starrocks.authentication.AuthenticationMgr;
import com.starrocks.authentication.AuthenticationProvider;
import com.starrocks.authentication.AuthenticationProviderFactory;
import com.starrocks.authentication.PlainPasswordAuthenticationProvider;
import com.starrocks.authentication.UserAuthenticationInfo;
import com.starrocks.common.Config;
import com.starrocks.mysql.MysqlPassword;
import com.starrocks.privilege.AuthorizationMgr;
import com.starrocks.qe.ConnectContext;
import com.starrocks.server.GlobalStateMgr;
import com.starrocks.sql.ast.AlterUserStmt;
import com.starrocks.sql.ast.AstVisitor;
import com.starrocks.sql.ast.CreateUserStmt;
import com.starrocks.sql.ast.DropUserStmt;
import com.starrocks.sql.ast.ExecuteAsStmt;
import com.starrocks.sql.ast.ShowAuthenticationStmt;
import com.starrocks.sql.ast.StatementBase;
import com.starrocks.sql.ast.UserAuthOption;
import com.starrocks.sql.ast.UserIdentity;

import java.nio.charset.StandardCharsets;

public class AuthenticationAnalyzer {
public static void analyze(StatementBase statement, ConnectContext session) {
new AuthenticationAnalyzerVisitor().analyze(statement, session);
}

public static class AuthenticationAnalyzerVisitor implements AstVisitor<Void, ConnectContext> {
private AuthenticationMgr authenticationManager = null;
private AuthorizationMgr authorizationManager = null;

public void analyze(StatementBase statement, ConnectContext session) {
authenticationManager = GlobalStateMgr.getCurrentState().getAuthenticationMgr();
authorizationManager = GlobalStateMgr.getCurrentState().getAuthorizationMgr();
visit(statement, session);
}

/**
* analyse user identity + check if user exists in UserPrivTable
*/
private void analyseUser(UserIdentity userIdent, boolean checkExist) {
userIdent.analyze();

// check if user exists
if (checkExist && !authenticationManager.doesUserExist(userIdent)) {
throw new SemanticException("cannot find user " + userIdent + "!");
}
}

/**
* check if role name valid and get full role name
*/
private void validRoleName(String roleName, String errMsg, boolean checkExist) {
// always set to true, we can validate if it's allowed to operation on admin later
FeNameFormat.checkRoleName(roleName, true, errMsg);
// check if role exists
if (checkExist && !authorizationManager.checkRoleExists(roleName)) {
throw new SemanticException(errMsg + ": cannot find role " + roleName + "!");
}
}

/**
* Get scrambled password from plain password
*/
private byte[] analysePassword(String originalPassword, boolean isPasswordPlain) {
if (Strings.isNullOrEmpty(originalPassword)) {
return MysqlPassword.EMPTY_PASSWORD;
}
if (isPasswordPlain) {
return MysqlPassword.makeScrambledPassword(originalPassword);
} else {
return MysqlPassword.checkPassword(originalPassword);
}
}

@Override
public Void visitCreateUserStatement(CreateUserStmt stmt, ConnectContext context) {
stmt.getUserIdentity().analyze();
if (authenticationManager.doesUserExist(stmt.getUserIdentity()) && !stmt.isIfNotExists()) {
throw new SemanticException("Operation CREATE USER failed for " + stmt.getUserIdentity()
+ " : user already exists");
}
if (!stmt.getDefaultRoles().isEmpty()) {
stmt.getDefaultRoles().forEach(r -> validRoleName(r, "Valid role name fail", true));
}

UserAuthenticationInfo userAuthenticationInfo = analyzeAuthOption(stmt.getUserIdentity(), stmt.getAuthOption());
stmt.setAuthenticationInfo(userAuthenticationInfo);
return null;
}

@Override
public Void visitAlterUserStatement(AlterUserStmt stmt, ConnectContext context) {
stmt.getUserIdentity().analyze();
if (!authenticationManager.doesUserExist(stmt.getUserIdentity()) && !stmt.isIfExists()) {
throw new SemanticException("Operation ALTER USER failed for " + stmt.getUserIdentity()
+ " : user not exists");
}

UserAuthenticationInfo userAuthenticationInfo = analyzeAuthOption(stmt.getUserIdentity(), stmt.getAuthOption());
stmt.setAuthenticationInfo(userAuthenticationInfo);

if (!Config.enable_password_reuse) {
UserIdentity user = stmt.getUserIdentity();
GlobalStateMgr.getCurrentState().getAuthenticationMgr().checkPlainPassword(
user.getUser(), user.getHost(), stmt.getAuthOption().getPassword());
}
return null;
}

private UserAuthenticationInfo analyzeAuthOption(UserIdentity userIdentity, UserAuthOption userAuthOption) {
byte[] password = MysqlPassword.EMPTY_PASSWORD;
String authPluginUsing;
if (userAuthOption == null) {
authPluginUsing = authenticationManager.getDefaultPlugin();
} else {
authPluginUsing = userAuthOption.getAuthPlugin();
if (authPluginUsing == null) {
authPluginUsing = authenticationManager.getDefaultPlugin();
password = analysePassword(userAuthOption.getPassword(), userAuthOption.isPasswordPlain());
} else {
authPluginUsing = userAuthOption.getAuthPlugin();
if (authPluginUsing.equals(PlainPasswordAuthenticationProvider.PLUGIN_NAME)) {
// In this case, authString is the password
password = analysePassword(userAuthOption.getAuthString(), userAuthOption.isPasswordPlain());
}
}
}

try {
AuthenticationProvider provider = AuthenticationProviderFactory.create(authPluginUsing);
UserAuthenticationInfo info = provider.validAuthenticationInfo(
userIdentity, new String(password, StandardCharsets.UTF_8),
userAuthOption == null ? null : userAuthOption.getAuthString());
info.setAuthPlugin(authPluginUsing);
info.setOrigUserHost(userIdentity.getUser(), userIdentity.getHost());
return info;
} catch (AuthenticationException e) {
throw new SemanticException("invalidate authentication: " + e.getMessage(), e);
}
}

private boolean needProtectAdminUser(UserIdentity userIdentity, ConnectContext context) {
return Config.authorization_enable_admin_user_protection &&
userIdentity.getUser().equalsIgnoreCase("admin") &&
!context.getCurrentUserIdentity().equals(UserIdentity.ROOT);
}

@Override
public Void visitDropUserStatement(DropUserStmt stmt, ConnectContext session) {
UserIdentity userIdentity = stmt.getUserIdentity();
userIdentity.analyze();

if (needProtectAdminUser(userIdentity, session)) {
throw new SemanticException("'admin' user cannot be dropped because of " +
"'authorization_enable_admin_user_protection' configuration is enabled");
}

if (!authenticationManager.doesUserExist(userIdentity) && !stmt.isIfExists()) {
throw new SemanticException("Operation DROP USER failed for " + userIdentity + " : user not exists");
}

if (stmt.getUserIdentity().equals(UserIdentity.ROOT)) {
throw new SemanticException("Operation DROP USER failed for " + UserIdentity.ROOT +
" : cannot drop user " + UserIdentity.ROOT);
}
return null;
}

@Override
public Void visitShowAuthenticationStatement(ShowAuthenticationStmt statement, ConnectContext context) {
UserIdentity user = statement.getUserIdent();
if (user != null) {
analyseUser(user, true);
} else if (!statement.isAll()) {
statement.setUserIdent(context.getCurrentUserIdentity());
}
return null;
}

@Override
public Void visitExecuteAsStatement(ExecuteAsStmt stmt, ConnectContext session) {
if (stmt.isAllowRevert()) {
throw new SemanticException("`EXECUTE AS` must use with `WITH NO REVERT` for now!");
}
analyseUser(stmt.getToUser(), true);
return null;
}
}
}
Loading

0 comments on commit 2be5b01

Please sign in to comment.