[Enhancement] Support revoke external group and add more test case (backport #63385) (#63397)

Co-authored-by: Harbor Liu <460660596@qq.com>
This commit is contained in:
mergify[bot] 2025-09-23 13:30:34 +08:00 committed by GitHub
parent 3d2a0d5301
commit e5b9ac8c92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 331 additions and 0 deletions

View File

@ -14,23 +14,31 @@
package com.starrocks.authentication;
import com.starrocks.authorization.AccessDeniedException;
import com.starrocks.authorization.AuthorizationMgr;
import com.starrocks.authorization.DefaultAuthorizationProvider;
import com.starrocks.authorization.GrantType;
import com.starrocks.authorization.PrivilegeType;
import com.starrocks.common.Config;
import com.starrocks.common.DdlException;
import com.starrocks.common.ErrorReportException;
import com.starrocks.mysql.MysqlPassword;
import com.starrocks.persist.EditLog;
import com.starrocks.qe.ConnectContext;
import com.starrocks.qe.ExecuteAsExecutor;
import com.starrocks.server.GlobalStateMgr;
import com.starrocks.sql.analyzer.Analyzer;
import com.starrocks.sql.analyzer.Authorizer;
import com.starrocks.sql.ast.CreateRoleStmt;
import com.starrocks.sql.ast.CreateUserStmt;
import com.starrocks.sql.ast.ExecuteAsStmt;
import com.starrocks.sql.ast.GrantPrivilegeStmt;
import com.starrocks.sql.ast.GrantRoleStmt;
import com.starrocks.sql.ast.RevokePrivilegeStmt;
import com.starrocks.sql.ast.RevokeRoleStmt;
import com.starrocks.sql.ast.UserIdentity;
import com.starrocks.sql.parser.NodePosition;
import com.starrocks.utframe.UtFrameUtils;
import mockit.Mock;
import mockit.MockUp;
import org.junit.jupiter.api.Assertions;
@ -191,4 +199,128 @@ public class ExecuteAsExecutorTest {
Assertions.assertEquals(Set.of(), context.getGroups());
Assertions.assertEquals(Set.of(), context.getCurrentRoleIds());
}
@Test
public void testImpersonatePermissionWithRoleGroupUser() throws Exception {
ConnectContext context = new ConnectContext();
// Create roles
authorizationMgr.createRole(new CreateRoleStmt(List.of("impersonate_role"), true, ""));
// Create users
CreateUserStmt createUserStmt =
new CreateUserStmt(new UserIdentity("admin_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO);
Analyzer.analyze(createUserStmt, context);
authenticationMgr.createUser(createUserStmt);
createUserStmt =
new CreateUserStmt(new UserIdentity("target_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO);
Analyzer.analyze(createUserStmt, context);
authenticationMgr.createUser(createUserStmt);
createUserStmt =
new CreateUserStmt(new UserIdentity("group_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO);
Analyzer.analyze(createUserStmt, context);
authenticationMgr.createUser(createUserStmt);
// Grant impersonate permission to role
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
"GRANT IMPERSONATE ON USER target_user TO ROLE impersonate_role",
new ConnectContext());
authorizationMgr.grant(grantStmt);
// Grant role to external group
GrantRoleStmt grantRoleStmt =
new GrantRoleStmt(List.of("impersonate_role"), "test_group", GrantType.GROUP, NodePosition.ZERO);
authorizationMgr.grantRole(grantRoleStmt);
// Set up LDAP group mapping for group_user to belong to test_group
LDAPGroupProvider ldapGroupProvider = (LDAPGroupProvider) authenticationMgr.getGroupProvider("ldap_group_provider");
Map<String, Set<String>> groups = new HashMap<>();
groups.put("group_user", Set.of("test_group"));
ldapGroupProvider.setUserToGroupCache(groups);
// Test 1: User with impersonate permission through role-group can execute as target user
AuthenticationHandler.authenticate(context, "group_user", "%", MysqlPassword.EMPTY_PASSWORD);
// Verify user has the role through group membership
long roleId = authorizationMgr.getRoleIdByNameAllowNull("impersonate_role");
Assertions.assertEquals(Set.of("test_group"), context.getGroups());
Assertions.assertEquals(Set.of(roleId), context.getCurrentRoleIds());
// Verify impersonate permission check passes
UserIdentity targetUser = new UserIdentity("target_user", "%");
try {
Authorizer.checkUserAction(context, targetUser, PrivilegeType.IMPERSONATE);
// If no exception is thrown, permission check passed
} catch (AccessDeniedException e) {
Assertions.fail("User should have impersonate permission through role-group membership");
}
// Execute as target user should succeed
ExecuteAsStmt executeAsStmt = new ExecuteAsStmt(new UserIdentity("target_user", "%"), false);
ExecuteAsExecutor.execute(executeAsStmt, context);
Assertions.assertEquals("target_user", context.getCurrentUserIdentity().getUser());
// Test 2: Revoke impersonate permission from role
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
"REVOKE IMPERSONATE ON USER target_user FROM ROLE impersonate_role",
new ConnectContext());
authorizationMgr.revoke(revokeStmt);
// Re-authenticate to refresh context
AuthenticationHandler.authenticate(context, "group_user", "%", MysqlPassword.EMPTY_PASSWORD);
// Verify user still has the role through group membership
Assertions.assertEquals(Set.of("test_group"), context.getGroups());
Assertions.assertEquals(Set.of(roleId), context.getCurrentRoleIds());
// Verify impersonate permission check now fails
Assertions.assertThrows(AccessDeniedException.class,
() -> Authorizer.checkUserAction(context, targetUser, PrivilegeType.IMPERSONATE));
// Execute as target user should fail
Assertions.assertThrows(ErrorReportException.class, () -> Authorizer.check(executeAsStmt, context));
// Test 3: Grant impersonate permission back to role
grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
"GRANT IMPERSONATE ON USER target_user TO ROLE impersonate_role",
new ConnectContext());
authorizationMgr.grant(grantStmt);
// Re-authenticate to refresh context
AuthenticationHandler.authenticate(context, "group_user", "%", MysqlPassword.EMPTY_PASSWORD);
// Verify impersonate permission check passes again
try {
Authorizer.checkUserAction(context, targetUser, PrivilegeType.IMPERSONATE);
// If no exception is thrown, permission check passed
} catch (AccessDeniedException e) {
Assertions.fail("User should have impersonate permission after re-granting to role");
}
// Execute as target user should succeed again
ExecuteAsExecutor.execute(executeAsStmt, context);
Assertions.assertEquals("target_user", context.getCurrentUserIdentity().getUser());
// Test 4: Revoke role from group
RevokeRoleStmt
revokeRoleStmt =
new RevokeRoleStmt(List.of("impersonate_role"), "test_group", GrantType.GROUP, NodePosition.ZERO);
authorizationMgr.revokeRole(revokeRoleStmt);
// Re-authenticate to refresh context
AuthenticationHandler.authenticate(context, "group_user", "%", MysqlPassword.EMPTY_PASSWORD);
// Verify user no longer has the role
Assertions.assertEquals(Set.of("test_group"), context.getGroups());
Assertions.assertEquals(Set.of(), context.getCurrentRoleIds());
// Verify impersonate permission check fails
Assertions.assertThrows(AccessDeniedException.class,
() -> Authorizer.checkUserAction(context, targetUser, PrivilegeType.IMPERSONATE));
// Execute as target user should fail
Assertions.assertThrows(ErrorReportException.class, () -> Authorizer.check(executeAsStmt, context));
}
}

View File

@ -24,6 +24,7 @@ import com.starrocks.qe.ConnectContext;
import com.starrocks.qe.DDLStmtExecutor;
import com.starrocks.qe.ShowExecutor;
import com.starrocks.qe.ShowResultSet;
import com.starrocks.qe.SqlModeHelper;
import com.starrocks.server.GlobalStateMgr;
import com.starrocks.sql.analyzer.Analyzer;
import com.starrocks.sql.analyzer.Authorizer;
@ -349,4 +350,202 @@ public class GrantRoleToGroupTest {
Assertions.assertThrows(AccessDeniedException.class, () -> Authorizer.checkSystemAction(ctx, PrivilegeType.GRANT));
Assertions.assertThrows(ErrorReportException.class, () -> Authorizer.check(stmt3, ctx));
}
@Test
public void testShowGrantsForExternalGroup() throws Exception {
EditLog editLog = spy(new EditLog(null));
doNothing().when(editLog).logEdit(anyShort(), any());
GlobalStateMgr.getCurrentState().setEditLog(editLog);
ConnectContext ctx = new ConnectContext();
ctx.setGlobalStateMgr(GlobalStateMgr.getCurrentState());
AuthorizationMgr authorizationMgr = new AuthorizationMgr(new DefaultAuthorizationProvider());
GlobalStateMgr.getCurrentState().setAuthorizationMgr(authorizationMgr);
GlobalStateMgr.getCurrentState().setAuthenticationMgr(new AuthenticationMgr());
// Create roles
for (int i = 1; i <= 4; i++) {
String sql = "create role r" + i;
StatementBase stmt = UtFrameUtils.parseStmtWithNewParser(sql, ctx);
DDLStmtExecutor.execute(stmt, ctx);
}
// Test 1: Grant single role to external group and verify show grants
GrantRoleStmt grantRoleStmt = new GrantRoleStmt(List.of("r1"), "test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(grantRoleStmt, ctx);
authorizationMgr.grantRole(grantRoleStmt);
ShowGrantsStmt stmt = new ShowGrantsStmt("test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
ShowResultSet showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[[test_group_1, null, GRANT 'r1' TO EXTERNAL GROUP test_group_1]]",
showResultSet.getResultRows().toString());
// Test 2: Grant multiple roles to external group and verify show grants
grantRoleStmt = new GrantRoleStmt(List.of("r2", "r3", "r4"), "test_group_2", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(grantRoleStmt, ctx);
authorizationMgr.grantRole(grantRoleStmt);
stmt = new ShowGrantsStmt("test_group_2", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[[test_group_2, null, GRANT 'r2', 'r3', 'r4' TO EXTERNAL GROUP test_group_2]]",
showResultSet.getResultRows().toString());
// Test 3: Grant additional roles to existing group and verify show grants
grantRoleStmt = new GrantRoleStmt(List.of("r3", "r4"), "test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(grantRoleStmt, ctx);
authorizationMgr.grantRole(grantRoleStmt);
stmt = new ShowGrantsStmt("test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[[test_group_1, null, GRANT 'r1', 'r3', 'r4' TO EXTERNAL GROUP test_group_1]]",
showResultSet.getResultRows().toString());
// Test 4: Revoke some roles and verify show grants reflects the changes
RevokeRoleStmt revokeRoleStmt = new RevokeRoleStmt(List.of("r3"), "test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(revokeRoleStmt, ctx);
authorizationMgr.revokeRole(revokeRoleStmt);
stmt = new ShowGrantsStmt("test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[[test_group_1, null, GRANT 'r1', 'r4' TO EXTERNAL GROUP test_group_1]]",
showResultSet.getResultRows().toString());
// Test 5: Revoke all roles and verify show grants shows empty result
revokeRoleStmt = new RevokeRoleStmt(List.of("r1", "r4"), "test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(revokeRoleStmt, ctx);
authorizationMgr.revokeRole(revokeRoleStmt);
stmt = new ShowGrantsStmt("test_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[]", showResultSet.getResultRows().toString());
// Test 6: Verify that test_group_2 still has its roles (isolation test)
stmt = new ShowGrantsStmt("test_group_2", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[[test_group_2, null, GRANT 'r2', 'r3', 'r4' TO EXTERNAL GROUP test_group_2]]",
showResultSet.getResultRows().toString());
// Test 7: Test non-existent group shows empty result
stmt = new ShowGrantsStmt("non_existent_group", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(stmt, ctx);
showResultSet = ShowExecutor.execute(stmt, ctx);
Assertions.assertEquals("[]", showResultSet.getResultRows().toString());
}
@Test
public void testGrantAndRevokeExternalGroup() throws Exception {
EditLog editLog = spy(new EditLog(null));
doNothing().when(editLog).logEdit(anyShort(), any());
GlobalStateMgr.getCurrentState().setEditLog(editLog);
ConnectContext ctx = new ConnectContext();
ctx.setGlobalStateMgr(GlobalStateMgr.getCurrentState());
AuthorizationMgr authorizationMgr = new AuthorizationMgr(new DefaultAuthorizationProvider());
GlobalStateMgr.getCurrentState().setAuthorizationMgr(authorizationMgr);
GlobalStateMgr.getCurrentState().setAuthenticationMgr(new AuthenticationMgr());
// Create roles
for (int i = 1; i <= 3; i++) {
String sql = "create role r" + i;
StatementBase stmt = UtFrameUtils.parseStmtWithNewParser(sql, ctx);
DDLStmtExecutor.execute(stmt, ctx);
}
Long r1Id = authorizationMgr.getRoleIdByNameAllowNull("r1");
Long r2Id = authorizationMgr.getRoleIdByNameAllowNull("r2");
Long r3Id = authorizationMgr.getRoleIdByNameAllowNull("r3");
// Test 1: Grant multiple roles to external group
GrantRoleStmt grantRoleStmt =
new GrantRoleStmt(List.of("r1", "r2", "r3"), "external_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(grantRoleStmt, ctx);
authorizationMgr.grantRole(grantRoleStmt);
// Verify roles are granted
Set<Long> roleIds = authorizationMgr.getRoleIdListByGroup("external_group_1");
Assertions.assertEquals(3, roleIds.size());
Assertions.assertTrue(roleIds.contains(r1Id));
Assertions.assertTrue(roleIds.contains(r2Id));
Assertions.assertTrue(roleIds.contains(r3Id));
// Test 2: Revoke one role from external group
RevokeRoleStmt revokeRoleStmt = new RevokeRoleStmt(List.of("r2"), "external_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(revokeRoleStmt, ctx);
authorizationMgr.revokeRole(revokeRoleStmt);
// Verify revocation takes effect immediately
roleIds = authorizationMgr.getRoleIdListByGroup("external_group_1");
Assertions.assertEquals(2, roleIds.size());
Assertions.assertTrue(roleIds.contains(r1Id));
Assertions.assertFalse(roleIds.contains(r2Id)); // r2 should be revoked
Assertions.assertTrue(roleIds.contains(r3Id));
// Test 3: Revoke multiple roles at once
revokeRoleStmt = new RevokeRoleStmt(List.of("r1", "r3"), "external_group_1", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(revokeRoleStmt, ctx);
authorizationMgr.revokeRole(revokeRoleStmt);
// Verify all roles are revoked
roleIds = authorizationMgr.getRoleIdListByGroup("external_group_1");
Assertions.assertEquals(0, roleIds.size());
// Test 4: Grant role to another external group and verify isolation
grantRoleStmt = new GrantRoleStmt(List.of("r1", "r2"), "external_group_2", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(grantRoleStmt, ctx);
authorizationMgr.grantRole(grantRoleStmt);
// Verify external_group_1 is still empty
roleIds = authorizationMgr.getRoleIdListByGroup("external_group_1");
Assertions.assertEquals(0, roleIds.size());
// Verify external_group_2 has the roles
roleIds = authorizationMgr.getRoleIdListByGroup("external_group_2");
Assertions.assertEquals(2, roleIds.size());
Assertions.assertTrue(roleIds.contains(r1Id));
Assertions.assertTrue(roleIds.contains(r2Id));
// Test 5: Revoke from external_group_2 and verify effect
revokeRoleStmt = new RevokeRoleStmt(List.of("r1"), "external_group_2", GrantType.GROUP, NodePosition.ZERO);
Analyzer.analyze(revokeRoleStmt, ctx);
authorizationMgr.revokeRole(revokeRoleStmt);
roleIds = authorizationMgr.getRoleIdListByGroup("external_group_2");
Assertions.assertEquals(1, roleIds.size());
Assertions.assertFalse(roleIds.contains(r1Id)); // r1 should be revoked
Assertions.assertTrue(roleIds.contains(r2Id)); // r2 should still be there
}
@Test
public void testGrantRoleStmtParser() {
String sql = "grant r1 to external group g1";
StatementBase stmt = SqlParser.parseSingleStatement(sql, SqlModeHelper.MODE_DEFAULT);
Assertions.assertInstanceOf(GrantRoleStmt.class, stmt);
GrantRoleStmt grantRoleStmt = (GrantRoleStmt) stmt;
Assertions.assertEquals(List.of("r1"), grantRoleStmt.getGranteeRole());
Assertions.assertEquals("g1", grantRoleStmt.getRoleOrGroup());
Assertions.assertEquals(GrantType.GROUP, grantRoleStmt.getGrantType());
sql = "revoke r1 from external group g1";
stmt = SqlParser.parseSingleStatement(sql, SqlModeHelper.MODE_DEFAULT);
Assertions.assertInstanceOf(RevokeRoleStmt.class, stmt);
RevokeRoleStmt revokeRoleStmt = (RevokeRoleStmt) stmt;
Assertions.assertEquals(List.of("r1"), revokeRoleStmt.getGranteeRole());
Assertions.assertEquals("g1", revokeRoleStmt.getRoleOrGroup());
Assertions.assertEquals(GrantType.GROUP, revokeRoleStmt.getGrantType());
sql = "show grants for external group g1";
stmt = SqlParser.parseSingleStatement(sql, SqlModeHelper.MODE_DEFAULT);
Assertions.assertInstanceOf(ShowGrantsStmt.class, stmt);
ShowGrantsStmt showGrantsStmt = (ShowGrantsStmt) stmt;
Assertions.assertEquals("g1", showGrantsStmt.getGroupOrRole());
Assertions.assertEquals(GrantType.GROUP, showGrantsStmt.getGrantType());
}
}