Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/help/Contents/Account Management/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Syntax:
user_identity:
'user_name'@'host'

CREATE USER 命令用于创建一个 Palo 用户。在 Palo 中,一个 user_identity 唯一标识一个用户。user_identity 由两部分组成,user_name 和 host,其中 username 为用户名。host 标识用户端连接所在的主机地址。host 部分可以使用 % 进行模糊匹配。如果不指定 host,默认为 '%',即表示该用户可以从任意 host 连接到 Palo
CREATE USER 命令用于创建一个 Doris 用户。在 Doris 中,一个 user_identity 唯一标识一个用户。user_identity 由两部分组成,user_name 和 host,其中 username 为用户名。host 标识用户端连接所在的主机地址。host 部分可以使用 % 进行模糊匹配。如果不指定 host,默认为 '%',即表示该用户可以从任意 host 连接到 Doris

host 部分也可指定为 domain,语法为:'user_name'@['domain'],即使用中括号包围,则 Palo 会认为这个是一个 domain,并尝试解析其 ip 地址。目前仅支持百度内部的 BNS 解析。
host 部分也可指定为 domain,语法为:'user_name'@['domain'],即使用中括号包围,则 Doris 会认为这个是一个 domain,并尝试解析其 ip 地址。目前仅支持百度内部的 BNS 解析。

如果指定了角色(ROLE),则会自动将该角色所拥有的权限赋予新创建的这个用户。如果不指定,则该用户默认没有任何权限。指定的 ROLE 必须已经存在。

Expand Down Expand Up @@ -54,7 +54,7 @@ Syntax:

DROP USER 'user_name'

DROP USER 命令会删除一个 palo 用户。这里 Palo 不支持删除指定的 user_identity。当删除一个指定用户后,该用户所对应的所有 user_identity 都会被删除。比如之前通过 CREATE USER 语句创建了 jack@'192.%' 以及 jack@['domain'] 两个用户,则在执行 DROP USER 'jack' 后,jack@'192.%' 以及 jack@['domain'] 都将被删除。
DROP USER 命令会删除一个 palo 用户。这里 Doris 不支持删除指定的 user_identity。当删除一个指定用户后,该用户所对应的所有 user_identity 都会被删除。比如之前通过 CREATE USER 语句创建了 jack@'192.%' 以及 jack@['domain'] 两个用户,则在执行 DROP USER 'jack' 后,jack@'192.%' 以及 jack@['domain'] 都将被删除。

## example

Expand Down Expand Up @@ -105,7 +105,7 @@ Syntax:
GRANT privilege_list ON db_name[.tbl_name] TO user_identity [ROLE role_name]


privilege_list 是需要赋予的权限列表,以逗号分隔。当前Palo支持如下权限
privilege_list 是需要赋予的权限列表,以逗号分隔。当前 Doris 支持如下权限

NODE_PRIV:集群节点操作权限,包括节点上下线等操作,只有 root 用户有该权限,不可赋予其他用户。
ADMIN_PRIV:除 NODE_PRIV 以外的所有权限。
Expand Down
2 changes: 2 additions & 0 deletions fe/src/main/cup/sql_parser.cup
Original file line number Diff line number Diff line change
Expand Up @@ -3281,6 +3281,8 @@ non_pred_expr ::=
{: RESULT = new FunctionCallExpr(new FunctionName(null, id), params); :}
| KW_DATABASE LPAREN RPAREN
{: RESULT = new InformationFunction("DATABASE"); :}
| KW_USER LPAREN RPAREN
{: RESULT = new InformationFunction("USER"); :}
| KW_CURRENT_USER LPAREN RPAREN
{: RESULT = new InformationFunction("CURRENT_USER"); :}
| KW_CONNECTION_ID LPAREN RPAREN
Expand Down
8 changes: 8 additions & 0 deletions fe/src/main/java/org/apache/doris/analysis/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,14 @@ public String getQualifiedUser() {
return globalState.context.getQualifiedUser();
}

public String getUserIdentity(boolean currentUser) {
if (currentUser) {
return "";
} else {
return getQualifiedUser() + "@" + ConnectContext.get().getRemoteIP();
}
}

public String getSchemaDb() {
return schemaDb;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@

package org.apache.doris.analysis;

import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.thrift.TExprNode;
import org.apache.doris.thrift.TExprNodeType;
import org.apache.doris.thrift.TInfoFunc;

/**
*/
public class InformationFunction extends Expr {
private final String funcType;
private long intValue;
Expand Down Expand Up @@ -55,10 +53,10 @@ protected void analyzeImpl(Analyzer analyzer) throws AnalysisException {
strValue = analyzer.getDefaultDb();
} else if (funcType.equalsIgnoreCase("USER")) {
type = Type.VARCHAR;
strValue = analyzer.getQualifiedUser();
strValue = ConnectContext.get().getUserIdentity().toString();
} else if (funcType.equalsIgnoreCase("CURRENT_USER")) {
type = Type.VARCHAR;
strValue = analyzer.getQualifiedUser();
strValue = ConnectContext.get().getCurrentUserIdentity().toString();
} else if (funcType.equalsIgnoreCase("CONNECTION_ID")) {
type = Type.BIGINT;
intValue = analyzer.getConnectId();
Expand Down
13 changes: 8 additions & 5 deletions fe/src/main/java/org/apache/doris/analysis/SetPassVar.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,15 @@ public void analyze(Analyzer analyzer) throws AnalysisException {
boolean isSelf = false;
ConnectContext ctx = ConnectContext.get();
if (userIdent == null) {
// set userIdent as itself
userIdent = new UserIdentity(ClusterNamespace.getNameFromFullName(analyzer.getQualifiedUser()),
ctx.getRemoteIP());
// set userIdent as what current_user() returns
userIdent = ctx.getCurrentUserIdentity();
isSelf = true;
} else {
userIdent.analyze(analyzer.getClusterName());
if (userIdent.equals(ctx.getCurrentUserIdentity())) {
isSelf = true;
}
}
userIdent.analyze(analyzer.getClusterName());

// Check password
passwdBytes = MysqlPassword.checkPassword(passwdParam);
Expand Down Expand Up @@ -92,6 +95,6 @@ public String toString() {

@Override
public String toSql() {
return "SET PASSWORD FOR " + userIdent + " = '*XXX'";
return "SET PASSWORD FOR " + userIdent.toString() + " = '*XXX'";
}
}
4 changes: 4 additions & 0 deletions fe/src/main/java/org/apache/doris/analysis/UserIdentity.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ public boolean equals(Object obj) {
return false;
}
UserIdentity other = (UserIdentity) obj;
if (this.isDomain != other.isDomain) {
return false;
}
return user.equals(other.getQualifiedUser()) && host.equals(other.getHost());
}

Expand All @@ -168,6 +171,7 @@ public int hashCode() {
int result = 17;
result = 31 * result + user.hashCode();
result = 31 * result + host.hashCode();
result = 31 * result + Boolean.valueOf(isDomain).hashCode();
return result;
}

Expand Down
7 changes: 6 additions & 1 deletion fe/src/main/java/org/apache/doris/mysql/MysqlProto.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.doris.mysql;

import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.Config;
Expand All @@ -28,12 +29,14 @@
import org.apache.doris.system.SystemInfoService;

import com.google.common.base.Strings;
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;

// MySQL protocol util
public class MysqlProto {
Expand Down Expand Up @@ -92,12 +95,14 @@ private static boolean authenticate(ConnectContext context, byte[] scramble, byt
String qualifiedUser = ClusterNamespace.getFullName(clusterName, tmpUser);
String remoteIp = context.getMysqlChannel().getRemoteIp();

List<UserIdentity> currentUserIdentity = Lists.newArrayList();
if (!Catalog.getCurrentCatalog().getAuth().checkPassword(qualifiedUser, remoteIp,
scramble, randomString)) {
scramble, randomString, currentUserIdentity)) {
ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, usePasswd);
return false;
}

context.setCurrentUserIdentitfy(currentUserIdentity.get(0));
context.setQualifiedUser(qualifiedUser);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import java.io.DataOutput;
import java.io.IOException;

/*
* DbPrivTable saves all database level privs
*/
public class DbPrivTable extends PrivTable {
private static final Logger LOG = LogManager.getLogger(DbPrivTable.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ private void revokeTblPrivs(String host, String db, String user, String tbl, Pri
}
}

public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString) {
public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
List<UserIdentity> currentUser) {
if (!Config.enable_auth_check) {
return true;
}
Expand All @@ -209,7 +210,7 @@ public boolean checkPassword(String remoteUser, String remoteHost, byte[] remote

readLock();
try {
return userPrivTable.checkPassword(remoteUser, remoteHost, remotePasswd, randomString);
return userPrivTable.checkPassword(remoteUser, remoteHost, remotePasswd, randomString, currentUser);
} finally {
readUnlock();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import java.io.DataOutput;
import java.io.IOException;

/*
* TablePrivTable saves all table level privs
*/
public class TablePrivTable extends PrivTable {

public void getPrivs(String host, String db, String user, String tbl, PrivBitSet savedPrivs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@

import java.io.DataOutput;
import java.io.IOException;
import java.util.List;

/*
* UserPrivTable saves all global privs and also password for users
*/
public class UserPrivTable extends PrivTable {
private static final Logger LOG = LogManager.getLogger(UserPrivTable.class);

Expand Down Expand Up @@ -61,7 +65,9 @@ public void getPrivs(String host, String user, PrivBitSet savedPrivs) {

// validate the connection by host, user and password.
// return true if this connection is valid, and 'savedPrivs' save all global privs got from user table.
public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString) {
// if currentUser is not null, save the current user identity
public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
List<UserIdentity> currentUser) {
LOG.debug("check password for user: {} from {}, password: {}, random string: {}",
remoteUser, remoteHost, remotePasswd, randomString);

Expand All @@ -87,6 +93,9 @@ public boolean checkPassword(String remoteUser, String remoteHost, byte[] remote
&& (remotePasswd.length == 0
|| MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) {
// found the matched entry
if (currentUser != null) {
currentUser.add(entry.getUserIdent());
}
return true;
} else {
continue;
Expand Down
17 changes: 16 additions & 1 deletion fe/src/main/java/org/apache/doris/qe/ConnectContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.doris.qe;

import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.mysql.MysqlCapability;
Expand Down Expand Up @@ -63,8 +64,9 @@ public class ConnectContext {
private volatile String currentDb = "";
// cluster name
private volatile String clusterName = "";
// User
// user
private volatile String qualifiedUser;
private volatile UserIdentity currentUserIdentity;
// Serializer used to pack MySQL packet.
private volatile MysqlSerializer serializer;
// Variables belong to this session.
Expand Down Expand Up @@ -164,6 +166,19 @@ public void setQualifiedUser(String qualifiedUser) {
this.qualifiedUser = qualifiedUser;
}

// for USER() function
public UserIdentity getUserIdentity() {
return new UserIdentity(qualifiedUser, remoteIP);
}

public UserIdentity getCurrentUserIdentity() {
return currentUserIdentity;
}

public void setCurrentUserIdentitfy(UserIdentity currentUserIdentity) {
this.currentUserIdentity = currentUserIdentity;
}

public SessionVariable getSessionVariable() {
return sessionVariable;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public void setUp() {
analyzer = AccessTestUtil.fetchAdminAnalyzer(true);
MockedAuth.mockedAuth(auth);
MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1");
UserIdentity currentUser = new UserIdentity("root", "192.168.1.1");
currentUser.setIsAnalyzed();
ctx.setCurrentUserIdentitfy(currentUser);
}

@Test
Expand All @@ -70,7 +73,7 @@ public void testNormal() throws UserException, AnalysisException {
// empty password
stmt = new SetPassVar(null, null);
stmt.analyze(analyzer);
Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser'@'192.168.1.1' = '*XXX'", stmt.toString());
Assert.assertEquals("SET PASSWORD FOR 'root'@'192.168.1.1' = '*XXX'", stmt.toString());
}

@Test(expected = AnalysisException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@

package org.apache.doris.mysql;

import org.junit.Assert;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

Expand Down
18 changes: 16 additions & 2 deletions fe/src/test/java/org/apache/doris/mysql/MysqlProtoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.doris.mysql;

import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.catalog.Database;
import org.apache.doris.common.DdlException;
Expand All @@ -40,6 +41,7 @@
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.List;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({ "org.apache.log4j.*", "javax.management.*" })
Expand All @@ -56,9 +58,20 @@ public void setUp() throws DdlException {
// mock auth
PaloAuth auth = EasyMock.createMock(PaloAuth.class);
EasyMock.expect(auth.checkGlobalPriv(EasyMock.anyObject(ConnectContext.class),
EasyMock.anyObject(PrivPredicate.class))).andReturn(true).anyTimes();
EasyMock.anyObject(PrivPredicate.class))).andReturn(true).anyTimes();

EasyMock.expect(auth.checkPassword(EasyMock.anyString(), EasyMock.anyString(), (byte[]) EasyMock.anyObject(),
(byte[]) EasyMock.anyObject())).andReturn(true).anyTimes();
(byte[]) EasyMock.anyObject(), (List<UserIdentity>) EasyMock.anyObject())).andDelegateTo(
new WrappedAuth() {
@Override
public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd,
byte[] randomString,
List<UserIdentity> currentUser) {
UserIdentity userIdentity = new UserIdentity("defaut_cluster:user", "192.168.1.1");
currentUser.add(userIdentity);
return true;
}
}).anyTimes();
EasyMock.replay(auth);

// Mock catalog
Expand Down Expand Up @@ -141,6 +154,7 @@ public void testNegotiate() throws Exception {
mockAccess();
ConnectContext context = new ConnectContext(null);
context.setCatalog(catalog);
context.setThreadLocalInfo();
Assert.assertTrue(MysqlProto.negotiate(context));
}

Expand Down
36 changes: 36 additions & 0 deletions fe/src/test/java/org/apache/doris/mysql/WrappedAuth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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;

import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.mysql.privilege.PaloAuth;

import java.util.List;

/*
* Author: Chenmingyu
* Date: Mar 24, 2019
*/

public class WrappedAuth extends PaloAuth {
@Override
public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString,
List<UserIdentity> currentUser) {
return true;
}
}
Loading