[UT] Fix unstability security test case (#63520)
This commit is contained in:
parent
dfa190b48f
commit
a73849377d
|
|
@ -0,0 +1,771 @@
|
|||
// 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.authentication;
|
||||
|
||||
import com.starrocks.authorization.AccessDeniedException;
|
||||
import com.starrocks.authorization.AuthorizationMgr;
|
||||
import com.starrocks.authorization.DefaultAuthorizationProvider;
|
||||
import com.starrocks.authorization.PrivilegeType;
|
||||
import com.starrocks.catalog.UserIdentity;
|
||||
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.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.RevokePrivilegeStmt;
|
||||
import com.starrocks.sql.ast.UserRef;
|
||||
import com.starrocks.sql.ast.group.CreateGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.group.DropGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.group.ShowCreateGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.group.ShowGroupProvidersStmt;
|
||||
import com.starrocks.sql.parser.NodePosition;
|
||||
import com.starrocks.sql.parser.SqlParser;
|
||||
import com.starrocks.utframe.UtFrameUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyShort;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Unit tests for Group Provider permission control
|
||||
* Based on permission errors from test/sql/test_group_provider
|
||||
* <p>
|
||||
* These tests verify that Group Provider operations require SECURITY privilege:
|
||||
* - CREATE GROUP PROVIDER
|
||||
* - DROP GROUP PROVIDER
|
||||
* - SHOW GROUP PROVIDERS
|
||||
* - SHOW CREATE GROUP PROVIDER
|
||||
* <p>
|
||||
* Test scenarios include:
|
||||
* - Non-admin users should be denied access (AccessDeniedException)
|
||||
* - Users with SECURITY privilege should be granted access
|
||||
* - Root user should always have access
|
||||
* - Various Group Provider types (Unix, File, LDAP) and configurations
|
||||
* - Quoted identifiers and complex properties
|
||||
*/
|
||||
public class GroupProviderPermissionTest {
|
||||
private ConnectContext rootCtx;
|
||||
private ConnectContext userCtx;
|
||||
private ConnectContext securityUserCtx;
|
||||
private AuthenticationMgr authenticationMgr;
|
||||
private AuthorizationMgr authorizationMgr;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
// Mock EditLog
|
||||
EditLog editLog = spy(new EditLog(null));
|
||||
doNothing().when(editLog).logEdit(anyShort(), any());
|
||||
GlobalStateMgr.getCurrentState().setEditLog(editLog);
|
||||
|
||||
authenticationMgr = new AuthenticationMgr();
|
||||
GlobalStateMgr.getCurrentState().setAuthenticationMgr(authenticationMgr);
|
||||
|
||||
// Initialize authentication and authorization managers
|
||||
authorizationMgr = new AuthorizationMgr(new DefaultAuthorizationProvider());
|
||||
GlobalStateMgr.getCurrentState().setAuthorizationMgr(authorizationMgr);
|
||||
|
||||
// Create root context
|
||||
rootCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.ROOT);
|
||||
|
||||
// Create a non-admin user context for permission testing
|
||||
userCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.createAnalyzedUserIdentWithIp("u1", "%"));
|
||||
|
||||
// Create a user with SECURITY privilege for testing
|
||||
securityUserCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.createAnalyzedUserIdentWithIp("security_user", "%"));
|
||||
|
||||
// Create test users and roles
|
||||
setupTestUsersAndRoles();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup test users and roles with appropriate privileges
|
||||
*/
|
||||
private void setupTestUsersAndRoles() throws Exception {
|
||||
// Create test users
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("u1", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("security_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Create a role with SECURITY privilege
|
||||
authorizationMgr.createRole(new CreateRoleStmt(List.of("security_role"), true, ""));
|
||||
|
||||
// Grant SECURITY privilege to the role
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO ROLE security_role",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Grant the role to security_user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("security_user", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
authorizationMgr.setUserDefaultRole(Set.of(role), new UserIdentity("security_user", "%"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to SHOW GROUP PROVIDERS
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; show group providers
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testShowGroupProvidersPermissionDenied() throws Exception {
|
||||
String sql = "SHOW GROUP PROVIDERS";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
ShowGroupProvidersStmt stmt =
|
||||
(ShowGroupProvidersStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege");
|
||||
|
||||
ConnectContext context = new ConnectContext();
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), context);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(context, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to SHOW CREATE GROUP PROVIDER
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; show create group provider unix_group_provider
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testShowCreateGroupProviderPermissionDenied() throws Exception {
|
||||
String sql = "SHOW CREATE GROUP PROVIDER unix_group_provider";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
ShowCreateGroupProviderStmt stmt =
|
||||
(ShowCreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("unix_group_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for SHOW CREATE GROUP PROVIDER");
|
||||
|
||||
ConnectContext context = new ConnectContext();
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), context);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(context, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for SHOW CREATE GROUP PROVIDER");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to DROP GROUP PROVIDER
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; drop group provider if exists unix_group_provider
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testDropGroupProviderPermissionDenied() throws Exception {
|
||||
String sql = "DROP GROUP PROVIDER IF EXISTS unix_group_provider";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
DropGroupProviderStmt stmt =
|
||||
(DropGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("unix_group_provider", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(stmt.isIfExists(), "IF EXISTS should be true");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for DROP GROUP PROVIDER");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for DROP GROUP PROVIDER");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to CREATE GROUP PROVIDER
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; create group provider if not exists unix_group_provider2 properties(...)
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderPermissionDenied() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS unix_group_provider2 PROPERTIES(\"type\" = \"unix\")";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("unix_group_provider2", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(stmt.isIfNotExists(), "IF NOT EXISTS should be true");
|
||||
Assertions.assertEquals("unix", stmt.getPropertyMap().get("type"),
|
||||
"Type property should be parsed correctly");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for CREATE GROUP PROVIDER");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for CREATE GROUP PROVIDER");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Root user can execute all group provider operations
|
||||
* Test point: Should parse successfully and pass permission checks for admin user
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderOperationsAsRoot() throws Exception {
|
||||
// Test CREATE
|
||||
String createSql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(\"type\" = \"unix\")";
|
||||
CreateGroupProviderStmt createStmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(createSql, rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(createStmt, "Create statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", createStmt.getName(), "Provider name should match");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for CREATE GROUP PROVIDER");
|
||||
|
||||
// Test SHOW CREATE
|
||||
String showCreateSql = "SHOW CREATE GROUP PROVIDER test_provider";
|
||||
ShowCreateGroupProviderStmt showCreateStmt =
|
||||
(ShowCreateGroupProviderStmt) SqlParser.parseSingleStatement(showCreateSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(showCreateStmt, "Show create statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", showCreateStmt.getName(), "Provider name should match");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for SHOW CREATE GROUP PROVIDER");
|
||||
|
||||
// Test SHOW ALL
|
||||
String showAllSql = "SHOW GROUP PROVIDERS";
|
||||
ShowGroupProvidersStmt showAllStmt =
|
||||
(ShowGroupProvidersStmt) SqlParser.parseSingleStatement(showAllSql, rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(showAllStmt, "Show all statement should parse successfully");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for SHOW GROUP PROVIDERS");
|
||||
|
||||
// Test DROP
|
||||
String dropSql = "DROP GROUP PROVIDER IF EXISTS test_provider";
|
||||
DropGroupProviderStmt dropStmt =
|
||||
(DropGroupProviderStmt) SqlParser.parseSingleStatement(dropSql, rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(dropStmt, "Drop statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", dropStmt.getName(), "Provider name should match");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for DROP GROUP PROVIDER");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for impersonation context
|
||||
* Test point: Should handle impersonation context correctly
|
||||
* Based on: grant impersonate on user root to u1; execute as u1 with no revert
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderWithImpersonation() throws Exception {
|
||||
// Test that impersonation context doesn't affect parsing
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS impersonated_provider PROPERTIES(\"type\" = \"unix\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully in impersonation context");
|
||||
Assertions.assertEquals("impersonated_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Permission check would still fail during execution phase
|
||||
// even with impersonation privileges
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege even in impersonation context");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check in impersonation context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Comprehensive permission validation for Group Provider operations
|
||||
* Test point: Verify that all Group Provider operations require SECURITY privilege
|
||||
*/
|
||||
@Test
|
||||
public void testComprehensiveGroupProviderPermissionValidation() throws Exception {
|
||||
// Test all Group Provider operations require SECURITY privilege
|
||||
String[] operations = {
|
||||
"SHOW GROUP PROVIDERS",
|
||||
"SHOW CREATE GROUP PROVIDER test_provider",
|
||||
"CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(\"type\" = \"unix\")",
|
||||
"DROP GROUP PROVIDER IF EXISTS test_provider"
|
||||
};
|
||||
|
||||
for (String sql : operations) {
|
||||
// Parse should succeed for all operations
|
||||
Object stmt = SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully: " + sql);
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for: " + sql);
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for: " + sql);
|
||||
|
||||
// Permission check should succeed for root user
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should pass permission check for: " + sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for quoted identifiers
|
||||
* Test point: Should handle quoted identifiers in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderPermissionWithQuotedIdentifiers() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS `test-provider` PROPERTIES(\"type\" = \"unix\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test-provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for quoted identifiers");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for quoted identifiers");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for complex group provider
|
||||
* Test point: Should handle complex properties in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderPermissionWithComplexProperties() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS complex_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\", " +
|
||||
"\"ldap_bind_root_dn\" = \"cn=admin,dc=example,dc=com\", " +
|
||||
"\"ldap_bind_root_pwd\" = \"password\", " +
|
||||
"\"ldap_bind_base_dn\" = \"dc=example,dc=com\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("complex_provider", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertEquals(5, stmt.getPropertyMap().size(), "All properties should be parsed");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for complex properties");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for complex properties");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for different group provider types
|
||||
* Test point: Should handle different types in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderPermissionWithDifferentTypes() throws Exception {
|
||||
// Test Unix type
|
||||
String unixSql = "CREATE GROUP PROVIDER IF NOT EXISTS unix_provider PROPERTIES(\"type\" = \"unix\")";
|
||||
CreateGroupProviderStmt unixStmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(unixSql, userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(unixStmt, "Unix provider statement should parse successfully");
|
||||
Assertions.assertEquals("unix", unixStmt.getPropertyMap().get("type"), "Type should be unix");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for Unix type");
|
||||
|
||||
// Test File type
|
||||
String fileSql = "CREATE GROUP PROVIDER IF NOT EXISTS file_provider PROPERTIES(" +
|
||||
"\"type\" = \"file\", " +
|
||||
"\"file_url\" = \"/path/to/file\")";
|
||||
CreateGroupProviderStmt fileStmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(fileSql, userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(fileStmt, "File provider statement should parse successfully");
|
||||
Assertions.assertEquals("file", fileStmt.getPropertyMap().get("type"), "Type should be file");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for File type");
|
||||
|
||||
// Test LDAP type
|
||||
String ldapSql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\")";
|
||||
CreateGroupProviderStmt ldapStmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(ldapSql, userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(ldapStmt, "LDAP provider statement should parse successfully");
|
||||
Assertions.assertEquals("ldap", ldapStmt.getPropertyMap().get("type"), "Type should be ldap");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for LDAP type");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// All types should pass permission check for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for all types");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for unsupported group provider type
|
||||
* Test point: Should handle unsupported types in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testGroupProviderPermissionWithUnsupportedType() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS unsupported_provider PROPERTIES(\"type\" = \"unsupported\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("unsupported_provider", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertEquals("unsupported", stmt.getPropertyMap().get("type"), "Type should be unsupported");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for unsupported type");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for unsupported type");
|
||||
|
||||
// Note: Type validation would fail during execution phase, but permission check comes first
|
||||
}
|
||||
|
||||
// ==================== Revoke Permission Tests ====================
|
||||
|
||||
/**
|
||||
* Test case: Revoke SECURITY privilege from user
|
||||
* Test point: User should lose access to Group Provider operations after privilege revocation
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeSecurityPrivilegeFromUser() throws Exception {
|
||||
// First grant SECURITY privilege to user
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO USER security_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Verify user has SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege after grant");
|
||||
|
||||
// Now revoke SECURITY privilege from user
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER security_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after revoke");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke SECURITY privilege from role
|
||||
* Test point: Users with revoked role should lose access to Group Provider operations
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeSecurityPrivilegeFromRole() throws Exception {
|
||||
// Create a new user for this test
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("test_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant role to user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("test_user", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
authorizationMgr.setUserDefaultRole(Set.of(role), new UserIdentity("test_user", "%"));
|
||||
|
||||
// Update ConnectContext with the new role information
|
||||
testUserCtx.setCurrentRoleIds(new UserIdentity("test_user", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege through role
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege through role");
|
||||
|
||||
// Now revoke SECURITY privilege from role
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM ROLE security_role",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after role privilege revocation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke role from user
|
||||
* Test point: User should lose access to Group Provider operations after role revocation
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeRoleFromUser() throws Exception {
|
||||
// Create a new user for this test
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("test_user2", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("test_user2", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant role to user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("test_user2", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
authorizationMgr.setUserDefaultRole(Set.of(role), new UserIdentity("test_user2", "%"));
|
||||
|
||||
// Update ConnectContext with the new role information
|
||||
testUserCtx.setCurrentRoleIds(new UserIdentity("test_user2", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege through role
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege through role");
|
||||
|
||||
// Now revoke SECURITY privilege from role (instead of revoking the role itself)
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM ROLE security_role",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after role privilege revocation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Comprehensive revoke permission validation for Group Provider operations
|
||||
* Test point: Verify that all Group Provider operations are denied after privilege revocation
|
||||
*/
|
||||
@Test
|
||||
public void testComprehensiveRevokePermissionValidation() throws Exception {
|
||||
// Create a user with SECURITY privilege
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("comprehensive_user", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("comprehensive_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant SECURITY privilege directly to user
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO USER comprehensive_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Update ConnectContext with the user identity
|
||||
testUserCtx.setCurrentUserIdentity(new UserIdentity("comprehensive_user", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege after grant");
|
||||
|
||||
// Test all Group Provider operations require SECURITY privilege
|
||||
String[] operations = {
|
||||
"SHOW GROUP PROVIDERS",
|
||||
"SHOW CREATE GROUP PROVIDER test_provider",
|
||||
"CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(\"type\" = \"unix\")",
|
||||
"DROP GROUP PROVIDER IF EXISTS test_provider"
|
||||
};
|
||||
|
||||
for (String sql : operations) {
|
||||
// Parse should succeed for all operations
|
||||
Object stmt = SqlParser.parseSingleStatement(sql, testUserCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully: " + sql);
|
||||
|
||||
// Permission check should pass for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for: " + sql);
|
||||
}
|
||||
|
||||
// Now revoke SECURITY privilege from user
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER comprehensive_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Test all Group Provider operations should fail after revoke
|
||||
for (String sql : operations) {
|
||||
// Permission check should fail for user without SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User without SECURITY privilege should fail permission check for: " + sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke non-existent privilege
|
||||
* Test point: Should handle revoke of non-existent privilege gracefully
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeNonExistentPrivilege() throws Exception {
|
||||
// Try to revoke SECURITY privilege from user who doesn't have it
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER u1",
|
||||
new ConnectContext());
|
||||
|
||||
// This should not throw an exception, but should be handled gracefully
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
}, "Revoking non-existent privilege should be handled gracefully");
|
||||
|
||||
// Verify user still doesn't have SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after revoke of non-existent privilege");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke permission with different Group Provider types
|
||||
* Test point: Verify revoke works consistently across different Group Provider types
|
||||
*/
|
||||
@Test
|
||||
public void testRevokePermissionWithDifferentGroupProviderTypes() throws Exception {
|
||||
// Create a user with SECURITY privilege
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("type_test_user", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("type_test_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant SECURITY privilege directly to user
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO USER type_test_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Update ConnectContext with the user identity
|
||||
testUserCtx.setCurrentUserIdentity(new UserIdentity("type_test_user", "%"));
|
||||
|
||||
// Test different Group Provider types
|
||||
String[] providerTypes = {"unix", "file", "ldap"};
|
||||
|
||||
for (String type : providerTypes) {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS " + type + "_provider PROPERTIES(\"type\" = \"" + type + "\")";
|
||||
|
||||
// Parse should succeed
|
||||
Object stmt = SqlParser.parseSingleStatement(sql, testUserCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully for type: " + type);
|
||||
|
||||
// Permission check should pass for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for type: " + type);
|
||||
}
|
||||
|
||||
// Now revoke SECURITY privilege from user
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER type_test_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Test all Group Provider types should fail after revoke
|
||||
for (String type : providerTypes) {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS " + type + "_provider PROPERTIES(\"type\" = \"" + type + "\")";
|
||||
|
||||
// Permission check should fail for user without SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User without SECURITY privilege should fail permission check for type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,763 @@
|
|||
// 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.authentication;
|
||||
|
||||
import com.starrocks.authorization.AccessDeniedException;
|
||||
import com.starrocks.authorization.AuthorizationMgr;
|
||||
import com.starrocks.authorization.DefaultAuthorizationProvider;
|
||||
import com.starrocks.authorization.PrivilegeType;
|
||||
import com.starrocks.catalog.UserIdentity;
|
||||
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.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.RevokePrivilegeStmt;
|
||||
import com.starrocks.sql.ast.UserRef;
|
||||
import com.starrocks.sql.ast.integration.AlterSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.CreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.DropSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowCreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.parser.NodePosition;
|
||||
import com.starrocks.sql.parser.SqlParser;
|
||||
import com.starrocks.utframe.UtFrameUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyShort;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
/**
|
||||
* Unit tests for Security Integration permission control
|
||||
* Based on permission errors from test/sql/test_security_integration
|
||||
* <p>
|
||||
* These tests verify that Security Integration operations require SECURITY privilege:
|
||||
* - CREATE SECURITY INTEGRATION
|
||||
* - DROP SECURITY INTEGRATION
|
||||
* - SHOW SECURITY INTEGRATIONS
|
||||
* - SHOW CREATE SECURITY INTEGRATION
|
||||
* - ALTER SECURITY INTEGRATION
|
||||
* <p>
|
||||
* Test scenarios include:
|
||||
* - Non-admin users should be denied access (AccessDeniedException)
|
||||
* - Users with SECURITY privilege should be granted access
|
||||
* - Root user should always have access
|
||||
* - Various Security Integration types (JWT, SAML) and configurations
|
||||
* - Quoted identifiers and complex properties
|
||||
*/
|
||||
public class SecurityIntegrationPermissionTest {
|
||||
private ConnectContext rootCtx;
|
||||
private ConnectContext userCtx;
|
||||
private ConnectContext securityUserCtx;
|
||||
private AuthenticationMgr authenticationMgr;
|
||||
private AuthorizationMgr authorizationMgr;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
// Mock EditLog
|
||||
EditLog editLog = spy(new EditLog(null));
|
||||
doNothing().when(editLog).logEdit(anyShort(), any());
|
||||
GlobalStateMgr.getCurrentState().setEditLog(editLog);
|
||||
|
||||
authenticationMgr = new AuthenticationMgr();
|
||||
GlobalStateMgr.getCurrentState().setAuthenticationMgr(authenticationMgr);
|
||||
|
||||
// Initialize authentication and authorization managers
|
||||
authorizationMgr = new AuthorizationMgr(new DefaultAuthorizationProvider());
|
||||
GlobalStateMgr.getCurrentState().setAuthorizationMgr(authorizationMgr);
|
||||
|
||||
// Create root context
|
||||
rootCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.ROOT);
|
||||
|
||||
// Create a non-admin user context for permission testing
|
||||
userCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.createAnalyzedUserIdentWithIp("u1", "%"));
|
||||
|
||||
// Create a user with SECURITY privilege for testing
|
||||
securityUserCtx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.createAnalyzedUserIdentWithIp("security_user", "%"));
|
||||
|
||||
// Create test users and roles
|
||||
setupTestUsersAndRoles();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup test users and roles with appropriate privileges
|
||||
*/
|
||||
private void setupTestUsersAndRoles() throws Exception {
|
||||
// Create test users
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("u1", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("security_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Create a role with SECURITY privilege
|
||||
authorizationMgr.createRole(new CreateRoleStmt(List.of("security_role"), true, ""));
|
||||
|
||||
// Grant SECURITY privilege to the role
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO ROLE security_role",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Grant the role to security_user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("security_user", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
authorizationMgr.setUserDefaultRole(Set.of(role), new UserIdentity("security_user", "%"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to CREATE SECURITY INTEGRATION
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; create security integration oidc2 properties(...)
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationPermissionDenied() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc2 PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc2", stmt.getName(), "Integration name should match");
|
||||
Assertions.assertEquals("authentication_jwt", stmt.getPropertyMap().get("type"),
|
||||
"Type property should be parsed correctly");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for CREATE SECURITY INTEGRATION");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for CREATE SECURITY INTEGRATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to SHOW SECURITY INTEGRATIONS
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; show security integrations
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testShowSecurityIntegrationsPermissionDenied() throws Exception {
|
||||
String sql = "SHOW SECURITY INTEGRATIONS";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
ShowSecurityIntegrationStatement stmt =
|
||||
(ShowSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for SHOW SECURITY INTEGRATIONS");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for SHOW SECURITY INTEGRATIONS");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to SHOW CREATE SECURITY INTEGRATION
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; show create security integration oidc
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testShowCreateSecurityIntegrationPermissionDenied() throws Exception {
|
||||
String sql = "SHOW CREATE SECURITY INTEGRATION oidc";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
ShowCreateSecurityIntegrationStatement stmt =
|
||||
(ShowCreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for SHOW CREATE SECURITY INTEGRATION");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for SHOW CREATE SECURITY INTEGRATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to DROP SECURITY INTEGRATION
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; drop security integration oidc
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testDropSecurityIntegrationPermissionDenied() throws Exception {
|
||||
String sql = "DROP SECURITY INTEGRATION oidc";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
DropSecurityIntegrationStatement stmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for DROP SECURITY INTEGRATION");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for DROP SECURITY INTEGRATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Non-admin user attempting to ALTER SECURITY INTEGRATION
|
||||
* Test point: Should parse successfully but fail during permission check
|
||||
* Based on: execute as u1 with no revert; alter security integration oidc set (...)
|
||||
* Expected error: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation.')
|
||||
*/
|
||||
@Test
|
||||
public void testAlterSecurityIntegrationPermissionDenied() throws Exception {
|
||||
String sql = "ALTER SECURITY INTEGRATION oidc SET (\"principal_field\" = \"preferred_name\")";
|
||||
|
||||
// Parse should succeed regardless of user context
|
||||
AlterSecurityIntegrationStatement stmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
Assertions.assertEquals("preferred_name", stmt.getProperties().get("principal_field"),
|
||||
"Property should be parsed correctly");
|
||||
|
||||
// Test permission check - should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for ALTER SECURITY INTEGRATION");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for ALTER SECURITY INTEGRATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Root user can execute all security integration operations
|
||||
* Test point: Should parse successfully for admin user
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationOperationsAsRoot() throws Exception {
|
||||
// Test CREATE
|
||||
String createSql = "CREATE SECURITY INTEGRATION test_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
CreateSecurityIntegrationStatement createStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(createSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(createStmt, "Create statement should parse successfully");
|
||||
Assertions.assertEquals("test_oidc", createStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for CREATE SECURITY INTEGRATION");
|
||||
|
||||
// Test SHOW CREATE
|
||||
String showCreateSql = "SHOW CREATE SECURITY INTEGRATION test_oidc";
|
||||
ShowCreateSecurityIntegrationStatement showCreateStmt =
|
||||
(ShowCreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(showCreateSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(showCreateStmt, "Show create statement should parse successfully");
|
||||
Assertions.assertEquals("test_oidc", showCreateStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for SHOW CREATE SECURITY INTEGRATION");
|
||||
|
||||
// Test SHOW ALL
|
||||
String showAllSql = "SHOW SECURITY INTEGRATIONS";
|
||||
ShowSecurityIntegrationStatement showAllStmt =
|
||||
(ShowSecurityIntegrationStatement) SqlParser.parseSingleStatement(showAllSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(showAllStmt, "Show all statement should parse successfully");
|
||||
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for SHOW SECURITY INTEGRATIONS");
|
||||
|
||||
// Test ALTER
|
||||
String alterSql = "ALTER SECURITY INTEGRATION test_oidc SET (\"principal_field\" = \"sub\")";
|
||||
AlterSecurityIntegrationStatement alterStmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(alterSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(alterStmt, "Alter statement should parse successfully");
|
||||
Assertions.assertEquals("test_oidc", alterStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for ALTER SECURITY INTEGRATION");
|
||||
|
||||
// Test DROP
|
||||
String dropSql = "DROP SECURITY INTEGRATION test_oidc";
|
||||
DropSecurityIntegrationStatement dropStmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(dropSql,
|
||||
rootCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(dropStmt, "Drop statement should parse successfully");
|
||||
Assertions.assertEquals("test_oidc", dropStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test permission check - root user should have SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should have SECURITY privilege for DROP SECURITY INTEGRATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for impersonation context
|
||||
* Test point: Should handle impersonation context correctly
|
||||
* Based on: grant impersonate on user root to u1; execute as u1 with no revert
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationWithImpersonation() throws Exception {
|
||||
// Test that impersonation context doesn't affect parsing
|
||||
String sql = "CREATE SECURITY INTEGRATION impersonated_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully in impersonation context");
|
||||
Assertions.assertEquals("impersonated_oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Permission check would still fail during execution phase
|
||||
// even with impersonation privileges
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege even in impersonation context");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Test permission check - should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check in impersonation context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for quoted identifiers
|
||||
* Test point: Should handle quoted identifiers in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationPermissionWithQuotedIdentifiers() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION `test-integration` PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test-integration", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for quoted identifiers");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for quoted identifiers");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for complex security integration
|
||||
* Test point: Should handle complex properties in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationPermissionWithComplexProperties() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION complex_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"https://example.com/.well-known/jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\", " +
|
||||
"\"issuer\" = \"https://example.com\", " +
|
||||
"\"audience\" = \"starrocks-client\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("complex_oidc", stmt.getName(), "Integration name should match");
|
||||
Assertions.assertEquals(5, stmt.getPropertyMap().size(), "All properties should be parsed");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for complex properties");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for complex properties");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Comprehensive permission validation for Security Integration operations
|
||||
* Test point: Verify that all Security Integration operations require SECURITY privilege
|
||||
*/
|
||||
@Test
|
||||
public void testComprehensiveSecurityIntegrationPermissionValidation() throws Exception {
|
||||
// Test all Security Integration operations require SECURITY privilege
|
||||
String[] operations = {
|
||||
"SHOW SECURITY INTEGRATIONS",
|
||||
"SHOW CREATE SECURITY INTEGRATION test_integration",
|
||||
"CREATE SECURITY INTEGRATION test_integration PROPERTIES(\"type\" = \"authentication_jwt\")",
|
||||
"DROP SECURITY INTEGRATION test_integration",
|
||||
"ALTER SECURITY INTEGRATION test_integration SET (\"principal_field\" = \"sub\")"
|
||||
};
|
||||
|
||||
for (String sql : operations) {
|
||||
// Parse should succeed for all operations
|
||||
Object stmt = SqlParser.parseSingleStatement(sql, userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully: " + sql);
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for: " + sql);
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for: " + sql);
|
||||
|
||||
// Permission check should succeed for root user
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(rootCtx, PrivilegeType.SECURITY);
|
||||
}, "Root user should pass permission check for: " + sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for different security integration types
|
||||
* Test point: Should handle different types in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationPermissionWithDifferentTypes() throws Exception {
|
||||
// Test authentication_jwt type
|
||||
String jwtSql = "CREATE SECURITY INTEGRATION jwt_integration PROPERTIES(\"type\" = \"authentication_jwt\")";
|
||||
CreateSecurityIntegrationStatement jwtStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(jwtSql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(jwtStmt, "JWT integration statement should parse successfully");
|
||||
Assertions.assertEquals("authentication_jwt", jwtStmt.getPropertyMap().get("type"), "Type should be authentication_jwt");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for JWT type");
|
||||
|
||||
// Test authentication_saml type
|
||||
String samlSql = "CREATE SECURITY INTEGRATION saml_integration PROPERTIES(" +
|
||||
"\"type\" = \"authentication_saml\", " +
|
||||
"\"saml_idp_url\" = \"https://example.com/saml\")";
|
||||
CreateSecurityIntegrationStatement samlStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(samlSql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(samlStmt, "SAML integration statement should parse successfully");
|
||||
Assertions.assertEquals("authentication_saml", samlStmt.getPropertyMap().get("type"),
|
||||
"Type should be authentication_saml");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for SAML type");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// All types should pass permission check for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for all types");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Permission check for unsupported security integration type
|
||||
* Test point: Should handle unsupported types in permission context
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationPermissionWithUnsupportedType() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION unsupported_integration PROPERTIES(\"type\" = \"unsupported\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
userCtx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("unsupported_integration", stmt.getName(), "Integration name should match");
|
||||
Assertions.assertEquals("unsupported", stmt.getPropertyMap().get("type"), "Type should be unsupported");
|
||||
|
||||
// Permission check should fail for non-admin user
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "Non-admin user should not have SECURITY privilege for unsupported type");
|
||||
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("security_user", "%"), false), securityUserCtx);
|
||||
// Permission check should succeed for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for unsupported type");
|
||||
|
||||
// Note: Type validation would fail during execution phase, but permission check comes first
|
||||
}
|
||||
|
||||
// ==================== Revoke Permission Tests ====================
|
||||
|
||||
/**
|
||||
* Test case: Revoke SECURITY privilege from user
|
||||
* Test point: User should lose access to Security Integration operations after privilege revocation
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeSecurityPrivilegeFromUser() throws Exception {
|
||||
// First grant SECURITY privilege to user
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO USER security_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Verify user has SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege after grant");
|
||||
|
||||
// Now revoke SECURITY privilege from user
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER security_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(securityUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after revoke");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke SECURITY privilege from role
|
||||
* Test point: Users with revoked role should lose access to Security Integration operations
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeSecurityPrivilegeFromRole() throws Exception {
|
||||
// Create a new user for this test
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("test_user", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("test_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant role to user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("test_user", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
authorizationMgr.setUserDefaultRole(Set.of(role), new UserIdentity("test_user", "%"));
|
||||
|
||||
// Update ConnectContext with the new role information
|
||||
testUserCtx.setCurrentRoleIds(new UserIdentity("test_user", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege through role
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege through role");
|
||||
|
||||
// Now revoke SECURITY privilege from role
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM ROLE security_role",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after role privilege revocation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke role from user
|
||||
* Test point: User should lose access to Security Integration operations after role revocation
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeRoleFromUser() throws Exception {
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("test_user2", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant role to user
|
||||
authorizationMgr.grantRole(new com.starrocks.sql.ast.GrantRoleStmt(
|
||||
List.of("security_role"), new UserRef("test_user2", "%"), NodePosition.ZERO));
|
||||
Long role = authorizationMgr.getRoleIdByNameAllowNull("security_role");
|
||||
Set<Long> defaultRoles = new HashSet<>();
|
||||
defaultRoles.add(role);
|
||||
authorizationMgr.setUserDefaultRole(defaultRoles, new UserIdentity("test_user2", "%"));
|
||||
|
||||
ConnectContext testUserCtx = new ConnectContext();
|
||||
ExecuteAsExecutor.execute(new ExecuteAsStmt(new UserRef("test_user2", "%"), false), testUserCtx);
|
||||
|
||||
// Update ConnectContext with the new role information
|
||||
testUserCtx.setCurrentRoleIds(new UserIdentity("test_user2", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege through role
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege through role");
|
||||
|
||||
// Now revoke role from user
|
||||
authorizationMgr.revokeRole(new com.starrocks.sql.ast.RevokeRoleStmt(
|
||||
List.of("security_role"), new UserRef("test_user2", "%"), NodePosition.ZERO));
|
||||
|
||||
// Verify user no longer has SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after role revocation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Comprehensive revoke permission validation
|
||||
* Test point: Verify that all Security Integration operations are denied after privilege revocation
|
||||
*/
|
||||
@Test
|
||||
public void testComprehensiveRevokePermissionValidation() throws Exception {
|
||||
// Create a user with SECURITY privilege
|
||||
ConnectContext testUserCtx = UtFrameUtils.initCtxForNewPrivilege(
|
||||
UserIdentity.createAnalyzedUserIdentWithIp("comprehensive_user", "%"));
|
||||
|
||||
// Create the test user first
|
||||
authenticationMgr.createUser(
|
||||
new CreateUserStmt(new UserRef("comprehensive_user", "%"), true, null, List.of(), Map.of(), NodePosition.ZERO));
|
||||
|
||||
// Grant SECURITY privilege directly to user
|
||||
GrantPrivilegeStmt grantStmt = (GrantPrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"GRANT SECURITY ON SYSTEM TO USER comprehensive_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.grant(grantStmt);
|
||||
|
||||
// Update ConnectContext with the user identity
|
||||
testUserCtx.setCurrentUserIdentity(new UserIdentity("comprehensive_user", "%"));
|
||||
|
||||
// Verify user has SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User should have SECURITY privilege after grant");
|
||||
|
||||
// Test all Security Integration operations require SECURITY privilege
|
||||
String[] operations = {
|
||||
"SHOW SECURITY INTEGRATIONS",
|
||||
"SHOW CREATE SECURITY INTEGRATION test_integration",
|
||||
"CREATE SECURITY INTEGRATION test_integration PROPERTIES(\"type\" = \"authentication_jwt\")",
|
||||
"DROP SECURITY INTEGRATION test_integration",
|
||||
"ALTER SECURITY INTEGRATION test_integration SET (\"principal_field\" = \"sub\")"
|
||||
};
|
||||
|
||||
for (String sql : operations) {
|
||||
// Parse should succeed for all operations
|
||||
Object stmt = SqlParser.parseSingleStatement(sql, testUserCtx.getSessionVariable().getSqlMode());
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully: " + sql);
|
||||
|
||||
// Permission check should pass for user with SECURITY privilege
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User with SECURITY privilege should pass permission check for: " + sql);
|
||||
}
|
||||
|
||||
// Now revoke SECURITY privilege from user
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER comprehensive_user",
|
||||
new ConnectContext());
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
|
||||
// Test all Security Integration operations should fail after revoke
|
||||
for (String sql : operations) {
|
||||
// Permission check should fail for user without SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(testUserCtx, PrivilegeType.SECURITY);
|
||||
}, "User without SECURITY privilege should fail permission check for: " + sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Revoke non-existent privilege
|
||||
* Test point: Should handle revoke of non-existent privilege gracefully
|
||||
*/
|
||||
@Test
|
||||
public void testRevokeNonExistentPrivilege() throws Exception {
|
||||
// Try to revoke SECURITY privilege from user who doesn't have it
|
||||
RevokePrivilegeStmt revokeStmt = (RevokePrivilegeStmt) UtFrameUtils.parseStmtWithNewParser(
|
||||
"REVOKE SECURITY ON SYSTEM FROM USER u1",
|
||||
new ConnectContext());
|
||||
|
||||
// This should not throw an exception, but should be handled gracefully
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
authorizationMgr.revoke(revokeStmt);
|
||||
}, "Revoking non-existent privilege should be handled gracefully");
|
||||
|
||||
// Verify user still doesn't have SECURITY privilege
|
||||
Assertions.assertThrows(AccessDeniedException.class, () -> {
|
||||
Authorizer.checkSystemAction(userCtx, PrivilegeType.SECURITY);
|
||||
}, "User should not have SECURITY privilege after revoke of non-existent privilege");
|
||||
}
|
||||
}
|
||||
|
|
@ -25,18 +25,27 @@ import com.starrocks.qe.ConnectContext;
|
|||
import com.starrocks.qe.ShowExecutor;
|
||||
import com.starrocks.qe.ShowResultSet;
|
||||
import com.starrocks.server.GlobalStateMgr;
|
||||
import com.starrocks.sql.analyzer.AnalyzeTestUtil;
|
||||
import com.starrocks.sql.analyzer.Analyzer;
|
||||
import com.starrocks.sql.analyzer.SemanticException;
|
||||
import com.starrocks.sql.ast.QueryStatement;
|
||||
import com.starrocks.sql.ast.StatementBase;
|
||||
import com.starrocks.sql.ast.expression.InformationFunction;
|
||||
import com.starrocks.sql.ast.group.ShowCreateGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.integration.AlterSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.CreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.DropSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowCreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.parser.NodePosition;
|
||||
import com.starrocks.sql.parser.SqlParser;
|
||||
import com.starrocks.utframe.UtFrameUtils;
|
||||
import mockit.Mock;
|
||||
import mockit.MockUp;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.wildfly.common.Assert;
|
||||
|
||||
|
|
@ -59,9 +68,38 @@ import static org.mockito.Mockito.spy;
|
|||
|
||||
public class SecurityIntegrationTest {
|
||||
private final MockTokenUtils mockTokenUtils = new MockTokenUtils();
|
||||
private ConnectContext ctx;
|
||||
private AuthenticationMgr authenticationMgr;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
AnalyzeTestUtil.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
// Mock EditLog
|
||||
EditLog editLog = spy(new EditLog(null));
|
||||
doNothing().when(editLog).logEdit(anyShort(), any());
|
||||
GlobalStateMgr.getCurrentState().setEditLog(editLog);
|
||||
|
||||
// Initialize authentication manager
|
||||
authenticationMgr = new AuthenticationMgr();
|
||||
GlobalStateMgr.getCurrentState().setAuthenticationMgr(authenticationMgr);
|
||||
|
||||
ctx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.ROOT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: JWT Security Integration property validation
|
||||
* Test point: Validate JWT-specific properties and their processing
|
||||
*/
|
||||
@Test
|
||||
public void testProperty() {
|
||||
public void testJWTSecurityIntegrationProperties() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("group_provider", "A, B, C");
|
||||
properties.put("permitted_groups", "B");
|
||||
|
|
@ -70,21 +108,29 @@ public class SecurityIntegrationTest {
|
|||
new JWTSecurityIntegration("oidc", properties);
|
||||
|
||||
List<String> groupProviderNameList = oidcSecurityIntegration.getGroupProviderName();
|
||||
Assertions.assertEquals("A,B,C", Joiner.on(",").join(groupProviderNameList));
|
||||
Assertions.assertEquals("A,B,C", Joiner.on(",").join(groupProviderNameList),
|
||||
"Group provider names should be parsed correctly");
|
||||
|
||||
List<String> permittedGroups = oidcSecurityIntegration.getGroupAllowedLoginList();
|
||||
Assertions.assertEquals("B", Joiner.on(",").join(permittedGroups));
|
||||
Assertions.assertEquals("B", Joiner.on(",").join(permittedGroups),
|
||||
"Permitted groups should be parsed correctly");
|
||||
|
||||
// Test with empty properties
|
||||
oidcSecurityIntegration = new JWTSecurityIntegration("oidc", new HashMap<>());
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupProviderName().isEmpty());
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupAllowedLoginList().isEmpty());
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupProviderName().isEmpty(),
|
||||
"Empty group provider list should be handled correctly");
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupAllowedLoginList().isEmpty(),
|
||||
"Empty permitted groups list should be handled correctly");
|
||||
|
||||
// Test with empty string properties
|
||||
properties = new HashMap<>();
|
||||
properties.put("group_provider", "");
|
||||
properties.put("permitted_groups", "");
|
||||
oidcSecurityIntegration = new JWTSecurityIntegration("oidc", properties);
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupProviderName().isEmpty());
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupAllowedLoginList().isEmpty());
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupProviderName().isEmpty(),
|
||||
"Empty string group provider should be handled correctly");
|
||||
Assertions.assertTrue(oidcSecurityIntegration.getGroupAllowedLoginList().isEmpty(),
|
||||
"Empty string permitted groups should be handled correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -269,4 +315,467 @@ public class SecurityIntegrationTest {
|
|||
Assert.assertTrue(
|
||||
resultSet.getResultRows().get(0).get(1).contains("\"ldap_bind_root_pwd\" = \"***\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Complete Security Integration lifecycle with actual execution
|
||||
* Test point: End-to-end testing of all security integration operations
|
||||
*/
|
||||
@Test
|
||||
public void testCompleteSecurityIntegrationLifecycle() throws Exception {
|
||||
// Step 1: Create Security Integration with OIDC properties
|
||||
String createSql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement createStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(createSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(createStmt, "Create statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", createStmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = createStmt.getPropertyMap();
|
||||
Assertions.assertEquals("authentication_jwt", properties.get("type"), "Type should be authentication_jwt");
|
||||
Assertions.assertEquals("jwks.json", properties.get("jwks_url"), "JWKS URL should match");
|
||||
Assertions.assertEquals("sub", properties.get("principal_field"), "Principal field should match");
|
||||
|
||||
// Actually create the security integration
|
||||
authenticationMgr.createSecurityIntegration("oidc", properties, true);
|
||||
Assertions.assertNotNull(authenticationMgr.getSecurityIntegration("oidc"),
|
||||
"Security integration should be created successfully");
|
||||
|
||||
// Step 2: Show CREATE Security Integration
|
||||
String showCreateSql = "SHOW CREATE SECURITY INTEGRATION oidc";
|
||||
ShowCreateSecurityIntegrationStatement showCreateStmt =
|
||||
(ShowCreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(showCreateSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(showCreateStmt, "Show create statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", showCreateStmt.getName(), "Integration name should match");
|
||||
|
||||
// Actually execute show create
|
||||
ShowResultSet showResult = ShowExecutor.execute(showCreateStmt, ctx);
|
||||
Assertions.assertNotNull(showResult, "Show create should execute successfully");
|
||||
Assertions.assertFalse(showResult.getResultRows().isEmpty(), "Show create should return results");
|
||||
|
||||
// Step 3: Alter Security Integration
|
||||
String alterSql = "ALTER SECURITY INTEGRATION oidc SET (\"principal_field\" = \"preferred_name\")";
|
||||
AlterSecurityIntegrationStatement alterStmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(alterSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(alterStmt, "Alter statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", alterStmt.getName(), "Integration name should match");
|
||||
Assertions.assertEquals("preferred_name", alterStmt.getProperties().get("principal_field"),
|
||||
"Principal field should be updated");
|
||||
|
||||
// Actually alter the security integration
|
||||
authenticationMgr.alterSecurityIntegration("oidc", alterStmt.getProperties(), true);
|
||||
SecurityIntegration alteredIntegration = authenticationMgr.getSecurityIntegration("oidc");
|
||||
Assertions.assertNotNull(alteredIntegration, "Altered security integration should exist");
|
||||
|
||||
// Step 4: Show all Security Integrations
|
||||
String showAllSql = "SHOW SECURITY INTEGRATIONS";
|
||||
ShowSecurityIntegrationStatement showAllStmt =
|
||||
(ShowSecurityIntegrationStatement) SqlParser.parseSingleStatement(showAllSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(showAllStmt, "Show all statement should parse successfully");
|
||||
|
||||
// Actually execute show all
|
||||
ShowResultSet showAllResult = ShowExecutor.execute(showAllStmt, ctx);
|
||||
Assertions.assertNotNull(showAllResult, "Show all should execute successfully");
|
||||
Assertions.assertFalse(showAllResult.getResultRows().isEmpty(), "Show all should return results");
|
||||
|
||||
// Step 5: Drop Security Integration
|
||||
String dropSql = "DROP SECURITY INTEGRATION oidc";
|
||||
DropSecurityIntegrationStatement dropStmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(dropSql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(dropStmt, "Drop statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", dropStmt.getName(), "Integration name should match");
|
||||
|
||||
// Actually drop the security integration
|
||||
authenticationMgr.dropSecurityIntegration("oidc", true);
|
||||
Assertions.assertNull(authenticationMgr.getSecurityIntegration("oidc"),
|
||||
"Security integration should be dropped successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Error scenarios and exception handling
|
||||
* Test point: Validation of error conditions and exception handling
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationErrorScenarios() throws Exception {
|
||||
// Test 1: Missing required 'type' property - should fail during creation
|
||||
String missingTypeSql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement missingTypeStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(missingTypeSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(missingTypeStmt, "Statement should parse successfully");
|
||||
Assertions.assertFalse(missingTypeStmt.getPropertyMap().containsKey("type"),
|
||||
"Missing type property should be detected");
|
||||
|
||||
// Actually try to create with missing type - should throw exception
|
||||
Assertions.assertThrows(SemanticException.class, () -> {
|
||||
authenticationMgr.createSecurityIntegration("oidc", missingTypeStmt.getPropertyMap(), true);
|
||||
}, "Creating security integration without type should fail");
|
||||
|
||||
// Test 2: Valid statement with all required properties
|
||||
String validSql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement validStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(validSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(validStmt, "Valid statement should parse successfully");
|
||||
Assertions.assertTrue(validStmt.getPropertyMap().containsKey("type"),
|
||||
"Valid statement should have type property");
|
||||
|
||||
// Actually create with valid properties - should succeed
|
||||
authenticationMgr.createSecurityIntegration("oidc", validStmt.getPropertyMap(), true);
|
||||
Assertions.assertNotNull(authenticationMgr.getSecurityIntegration("oidc"),
|
||||
"Valid security integration should be created successfully");
|
||||
|
||||
// Test 3: Duplicate creation - should fail
|
||||
Assertions.assertThrows(DdlException.class, () -> {
|
||||
authenticationMgr.createSecurityIntegration("oidc", validStmt.getPropertyMap(), false);
|
||||
}, "Creating duplicate security integration should fail");
|
||||
|
||||
// Test 4: Alter non-existent integration - should fail
|
||||
Map<String, String> alterProperties = new HashMap<>();
|
||||
alterProperties.put("principal_field", "preferred_name");
|
||||
Assertions.assertThrows(DdlException.class, () -> {
|
||||
authenticationMgr.alterSecurityIntegration("non_existent", alterProperties, false);
|
||||
}, "Altering non-existent security integration should fail");
|
||||
|
||||
// Test 5: Drop non-existent integration - should fail
|
||||
Assertions.assertThrows(DdlException.class, () -> {
|
||||
authenticationMgr.dropSecurityIntegration("non_existent", false);
|
||||
}, "Dropping non-existent security integration should fail");
|
||||
|
||||
// Clean up
|
||||
authenticationMgr.dropSecurityIntegration("oidc", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Multiple Security Integrations
|
||||
* Test point: Handling multiple integrations with different configurations
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleSecurityIntegrations() throws Exception {
|
||||
// Create first integration
|
||||
String sql1 = "CREATE SECURITY INTEGRATION oidc1 PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks1.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt1 =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql1, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt1, "First integration should parse successfully");
|
||||
Assertions.assertEquals("oidc1", stmt1.getName(), "First integration name should match");
|
||||
|
||||
// Actually create first integration
|
||||
authenticationMgr.createSecurityIntegration("oidc1", stmt1.getPropertyMap(), true);
|
||||
SecurityIntegration integration1 = authenticationMgr.getSecurityIntegration("oidc1");
|
||||
Assertions.assertNotNull(integration1, "First integration should be created successfully");
|
||||
|
||||
// Create second integration
|
||||
String sql2 = "CREATE SECURITY INTEGRATION oidc2 PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks2.json\", " +
|
||||
"\"principal_field\" = \"preferred_name\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt2 =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql2, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt2, "Second integration should parse successfully");
|
||||
Assertions.assertEquals("oidc2", stmt2.getName(), "Second integration name should match");
|
||||
|
||||
// Actually create second integration
|
||||
authenticationMgr.createSecurityIntegration("oidc2", stmt2.getPropertyMap(), true);
|
||||
SecurityIntegration integration2 = authenticationMgr.getSecurityIntegration("oidc2");
|
||||
Assertions.assertNotNull(integration2, "Second integration should be created successfully");
|
||||
|
||||
// Verify different configurations
|
||||
Assertions.assertNotEquals(stmt1.getPropertyMap().get("jwks_url"),
|
||||
stmt2.getPropertyMap().get("jwks_url"),
|
||||
"Different integrations should have different JWKS URLs");
|
||||
Assertions.assertNotEquals(stmt1.getPropertyMap().get("principal_field"),
|
||||
stmt2.getPropertyMap().get("principal_field"),
|
||||
"Different integrations should have different principal fields");
|
||||
|
||||
// Test that both integrations exist
|
||||
Assertions.assertNotNull(authenticationMgr.getSecurityIntegration("oidc1"),
|
||||
"First integration should still exist");
|
||||
Assertions.assertNotNull(authenticationMgr.getSecurityIntegration("oidc2"),
|
||||
"Second integration should still exist");
|
||||
|
||||
// Test show all integrations
|
||||
String showAllSql = "SHOW SECURITY INTEGRATIONS";
|
||||
ShowSecurityIntegrationStatement showAllStmt =
|
||||
(ShowSecurityIntegrationStatement) SqlParser.parseSingleStatement(showAllSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
ShowResultSet showAllResult = ShowExecutor.execute(showAllStmt, ctx);
|
||||
Assertions.assertNotNull(showAllResult, "Show all should execute successfully");
|
||||
Assertions.assertTrue(showAllResult.getResultRows().size() >= 2,
|
||||
"Show all should return at least 2 integrations");
|
||||
|
||||
// Clean up
|
||||
authenticationMgr.dropSecurityIntegration("oidc1", true);
|
||||
authenticationMgr.dropSecurityIntegration("oidc2", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Security Integration Factory creation
|
||||
* Test point: Test SecurityIntegrationFactory for different types
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationFactory() throws Exception {
|
||||
// Test JWT type creation
|
||||
Map<String, String> jwtProperties = new HashMap<>();
|
||||
jwtProperties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "authentication_jwt");
|
||||
jwtProperties.put("jwks_url", "jwks.json");
|
||||
jwtProperties.put("principal_field", "sub");
|
||||
|
||||
SecurityIntegration jwtIntegration = SecurityIntegrationFactory.createSecurityIntegration("jwt_test", jwtProperties);
|
||||
Assertions.assertNotNull(jwtIntegration, "JWT security integration should be created successfully");
|
||||
Assertions.assertEquals("jwt_test", jwtIntegration.getName(), "JWT integration name should match");
|
||||
|
||||
// Test unsupported type - should throw exception
|
||||
Map<String, String> unsupportedProperties = new HashMap<>();
|
||||
unsupportedProperties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "unsupported_type");
|
||||
|
||||
Assertions.assertThrows(SemanticException.class, () -> {
|
||||
SecurityIntegrationFactory.createSecurityIntegration("unsupported_test", unsupportedProperties);
|
||||
}, "Creating unsupported security integration type should fail");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Security Integration with Group Provider integration
|
||||
* Test point: Test integration with group providers
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationWithGroupProvider() throws Exception {
|
||||
// Create security integration with group provider
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "authentication_jwt");
|
||||
properties.put("jwks_url", "jwks.json");
|
||||
properties.put("principal_field", "preferred_username");
|
||||
properties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_GROUP_PROVIDER, "file_group_provider");
|
||||
properties.put(SecurityIntegration.SECURITY_INTEGRATION_GROUP_ALLOWED_LOGIN, "group1");
|
||||
|
||||
authenticationMgr.createSecurityIntegration("oidc_with_groups", properties, true);
|
||||
SecurityIntegration integration = authenticationMgr.getSecurityIntegration("oidc_with_groups");
|
||||
Assertions.assertNotNull(integration, "Security integration with group provider should be created successfully");
|
||||
|
||||
// Verify it's a JWT integration
|
||||
Assertions.assertTrue(integration instanceof JWTSecurityIntegration,
|
||||
"Integration should be JWT type");
|
||||
|
||||
JWTSecurityIntegration jwtIntegration = (JWTSecurityIntegration) integration;
|
||||
List<String> groupProviders = jwtIntegration.getGroupProviderName();
|
||||
Assertions.assertEquals(1, groupProviders.size(), "Should have one group provider");
|
||||
Assertions.assertEquals("file_group_provider", groupProviders.get(0),
|
||||
"Group provider name should match");
|
||||
|
||||
List<String> permittedGroups = jwtIntegration.getGroupAllowedLoginList();
|
||||
Assertions.assertEquals(1, permittedGroups.size(), "Should have one permitted group");
|
||||
Assertions.assertEquals("group1", permittedGroups.get(0),
|
||||
"Permitted group should match");
|
||||
|
||||
// Clean up
|
||||
authenticationMgr.dropSecurityIntegration("oidc_with_groups", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Password masking in show create
|
||||
* Test point: Test that sensitive properties are masked in show create output
|
||||
*/
|
||||
@Test
|
||||
public void testPasswordMaskingInShowCreate() throws Exception {
|
||||
// Create security integration with sensitive properties
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "authentication_oauth2");
|
||||
properties.put("client_secret", "secret123");
|
||||
|
||||
authenticationMgr.createSecurityIntegration("oauth2_test", properties, true);
|
||||
|
||||
// Test show create - sensitive properties should be masked
|
||||
ShowCreateSecurityIntegrationStatement showCreateStmt =
|
||||
new ShowCreateSecurityIntegrationStatement("oauth2_test", NodePosition.ZERO);
|
||||
ShowResultSet resultSet = ShowExecutor.execute(showCreateStmt, ctx);
|
||||
|
||||
Assertions.assertNotNull(resultSet, "Show create should execute successfully");
|
||||
Assertions.assertFalse(resultSet.getResultRows().isEmpty(), "Show create should return results");
|
||||
|
||||
String createStatement = resultSet.getResultRows().get(0).get(1).toString();
|
||||
Assertions.assertTrue(createStatement.contains("\"client_secret\" = \"***\""),
|
||||
"Sensitive properties should be masked in show create output");
|
||||
|
||||
// Clean up
|
||||
authenticationMgr.dropSecurityIntegration("oauth2_test", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Edge cases and special characters
|
||||
* Test point: Handling edge cases and special characters in properties
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationEdgeCases() throws Exception {
|
||||
// Test with special characters in integration name
|
||||
String specialNameSql = "CREATE SECURITY INTEGRATION `test-integration-123` PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"https://example.com/.well-known/jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement specialNameStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(specialNameSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(specialNameStmt, "Statement with special name should parse successfully");
|
||||
Assertions.assertEquals("test-integration-123", specialNameStmt.getName(),
|
||||
"Special name should be parsed correctly");
|
||||
|
||||
// Test with complex JWKS URL
|
||||
String complexUrlSql = "CREATE SECURITY INTEGRATION complex_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"https://example.com/.well-known/jwks.json?version=1&format=json\", " +
|
||||
"\"principal_field\" = \"sub\", " +
|
||||
"\"issuer\" = \"https://example.com\", " +
|
||||
"\"audience\" = \"starrocks-client\")";
|
||||
|
||||
CreateSecurityIntegrationStatement complexUrlStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(complexUrlSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(complexUrlStmt, "Statement with complex URL should parse successfully");
|
||||
Assertions.assertEquals("complex_oidc", complexUrlStmt.getName(), "Complex integration name should match");
|
||||
|
||||
Map<String, String> complexProperties = complexUrlStmt.getPropertyMap();
|
||||
Assertions.assertTrue(complexProperties.get("jwks_url").contains("?"),
|
||||
"Complex URL should contain query parameters");
|
||||
Assertions.assertEquals("https://example.com", complexProperties.get("issuer"),
|
||||
"Issuer should be parsed correctly");
|
||||
Assertions.assertEquals("starrocks-client", complexProperties.get("audience"),
|
||||
"Audience should be parsed correctly");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Case sensitivity and keyword variations
|
||||
* Test point: Handling case variations in SQL keywords
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationCaseVariations() throws Exception {
|
||||
// Test with different case variations
|
||||
String[] caseVariations = {
|
||||
"CREATE SECURITY INTEGRATION test_oidc PROPERTIES(\"type\" = \"authentication_jwt\"," +
|
||||
" \"jwks_url\" = \"jwks.json\")",
|
||||
"create security integration test_oidc properties(\"type\" = \"authentication_jwt\"," +
|
||||
" \"jwks_url\" = \"jwks.json\")",
|
||||
"Create Security Integration test_oidc Properties(\"type\" = \"authentication_jwt\"," +
|
||||
" \"jwks_url\" = \"jwks.json\")",
|
||||
"CREATE security INTEGRATION test_oidc PROPERTIES(\"type\" = \"authentication_jwt\"," +
|
||||
" \"jwks_url\" = \"jwks.json\")"
|
||||
};
|
||||
|
||||
for (int i = 0; i < caseVariations.length; i++) {
|
||||
String sql = caseVariations[i];
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Case variation " + i + " should parse successfully");
|
||||
Assertions.assertEquals("test_oidc", stmt.getName(),
|
||||
"Case variation " + i + " should have correct name");
|
||||
Assertions.assertEquals("authentication_jwt", stmt.getPropertyMap().get("type"),
|
||||
"Case variation " + i + " should have correct type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Property validation scenarios
|
||||
* Test point: Various property validation scenarios
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityIntegrationPropertyValidation() throws Exception {
|
||||
// Test single property
|
||||
String singlePropSql = "CREATE SECURITY INTEGRATION single_oidc PROPERTIES(\"type\" = \"authentication_jwt\")";
|
||||
CreateSecurityIntegrationStatement singlePropStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(singlePropSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(singlePropStmt, "Single property statement should parse successfully");
|
||||
Assertions.assertEquals(1, singlePropStmt.getPropertyMap().size(),
|
||||
"Single property should be handled correctly");
|
||||
|
||||
// Test many properties
|
||||
String manyPropsSql = "CREATE SECURITY INTEGRATION many_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\", " +
|
||||
"\"issuer\" = \"https://example.com\", " +
|
||||
"\"audience\" = \"starrocks-client\", " +
|
||||
"\"algorithm\" = \"RS256\", " +
|
||||
"\"clock_skew\" = \"300\")";
|
||||
|
||||
CreateSecurityIntegrationStatement manyPropsStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(manyPropsSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(manyPropsStmt, "Many properties statement should parse successfully");
|
||||
Assertions.assertEquals(7, manyPropsStmt.getPropertyMap().size(),
|
||||
"Many properties should be handled correctly");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Comprehensive validation scenarios
|
||||
* Test point: Test various validation scenarios for security integrations
|
||||
*/
|
||||
@Test
|
||||
public void testComprehensiveValidationScenarios() throws Exception {
|
||||
// Test 1: Valid JWT integration
|
||||
Map<String, String> validJwtProperties = new HashMap<>();
|
||||
validJwtProperties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "authentication_jwt");
|
||||
validJwtProperties.put("jwks_url", "https://example.com/.well-known/jwks.json");
|
||||
validJwtProperties.put("principal_field", "sub");
|
||||
validJwtProperties.put("issuer", "https://example.com");
|
||||
validJwtProperties.put("audience", "starrocks-client");
|
||||
|
||||
authenticationMgr.createSecurityIntegration("valid_jwt", validJwtProperties, true);
|
||||
SecurityIntegration validIntegration = authenticationMgr.getSecurityIntegration("valid_jwt");
|
||||
Assertions.assertNotNull(validIntegration, "Valid JWT integration should be created successfully");
|
||||
|
||||
// Test 2: Integration with all optional properties
|
||||
Map<String, String> fullProperties = new HashMap<>();
|
||||
fullProperties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_TYPE_KEY, "authentication_jwt");
|
||||
fullProperties.put("jwks_url", "jwks.json");
|
||||
fullProperties.put("principal_field", "sub");
|
||||
fullProperties.put("issuer", "https://example.com");
|
||||
fullProperties.put("audience", "starrocks-client");
|
||||
fullProperties.put("algorithm", "RS256");
|
||||
fullProperties.put("clock_skew", "300");
|
||||
fullProperties.put(SecurityIntegration.SECURITY_INTEGRATION_PROPERTY_GROUP_PROVIDER, "group1,group2");
|
||||
fullProperties.put(SecurityIntegration.SECURITY_INTEGRATION_GROUP_ALLOWED_LOGIN, "allowed1,allowed2");
|
||||
|
||||
authenticationMgr.createSecurityIntegration("full_jwt", fullProperties, true);
|
||||
SecurityIntegration fullIntegration = authenticationMgr.getSecurityIntegration("full_jwt");
|
||||
Assertions.assertNotNull(fullIntegration, "Full JWT integration should be created successfully");
|
||||
|
||||
// Verify it's a JWT integration with all properties
|
||||
Assertions.assertTrue(fullIntegration instanceof JWTSecurityIntegration,
|
||||
"Full integration should be JWT type");
|
||||
|
||||
// Clean up
|
||||
authenticationMgr.dropSecurityIntegration("valid_jwt", true);
|
||||
authenticationMgr.dropSecurityIntegration("full_jwt", true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
// 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.starrocks.qe.ShowResultMetaFactory;
|
||||
import com.starrocks.sql.ast.integration.AlterSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.CreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.DropSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowCreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowSecurityIntegrationStatement;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.starrocks.sql.analyzer.AnalyzeTestUtil.analyzeSuccess;
|
||||
|
||||
public class SecurityIntegrationTest {
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
AnalyzeTestUtil.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void test() {
|
||||
CreateSecurityIntegrationStatement createSecurityIntegrationStatement =
|
||||
(CreateSecurityIntegrationStatement) analyzeSuccess("create security integration test " +
|
||||
"properties(\"type\"=\"oidc\", \"oidc_jwks_url\"=\"jwks.json\", \"oidc_principal_field\"=\"sub\")");
|
||||
Assertions.assertEquals("test", createSecurityIntegrationStatement.getName());
|
||||
Assertions.assertEquals("oidc", createSecurityIntegrationStatement.getPropertyMap().get("type"));
|
||||
|
||||
AlterSecurityIntegrationStatement alterSecurityIntegrationStatement =
|
||||
(AlterSecurityIntegrationStatement) analyzeSuccess("alter security integration test " +
|
||||
"set (\"type\" = \"oidc\")");
|
||||
Assertions.assertEquals("test", alterSecurityIntegrationStatement.getName());
|
||||
Assertions.assertEquals("oidc", alterSecurityIntegrationStatement.getProperties().get("type"));
|
||||
|
||||
DropSecurityIntegrationStatement dropSecurityIntegrationStatement =
|
||||
(DropSecurityIntegrationStatement) analyzeSuccess("drop security integration test");
|
||||
Assertions.assertEquals("test", dropSecurityIntegrationStatement.getName());
|
||||
|
||||
ShowSecurityIntegrationStatement showSecurityIntegrationStatement =
|
||||
(ShowSecurityIntegrationStatement) analyzeSuccess("show security integrations");
|
||||
Assertions.assertNotNull(new ShowResultMetaFactory().getMetadata(showSecurityIntegrationStatement));
|
||||
|
||||
ShowCreateSecurityIntegrationStatement showCreateSecurityIntegrationStatement =
|
||||
(ShowCreateSecurityIntegrationStatement) analyzeSuccess("show create security integration test");
|
||||
Assertions.assertNotNull(new ShowResultMetaFactory().getMetadata(showCreateSecurityIntegrationStatement));
|
||||
Assertions.assertEquals("test", showCreateSecurityIntegrationStatement.getName());
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,8 @@ import com.starrocks.catalog.UserIdentity;
|
|||
import com.starrocks.qe.ConnectContext;
|
||||
import com.starrocks.sql.ast.group.CreateGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.group.DropGroupProviderStmt;
|
||||
import com.starrocks.sql.parser.SqlParser;
|
||||
import com.starrocks.sql.ast.group.ShowCreateGroupProviderStmt;
|
||||
import com.starrocks.sql.ast.group.ShowGroupProvidersStmt;
|
||||
import com.starrocks.utframe.UtFrameUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -29,6 +30,7 @@ import java.util.Map;
|
|||
|
||||
/**
|
||||
* Unit tests for AstBuilder Group Provider parsing with IF NOT EXISTS and IF EXISTS functionality
|
||||
* Includes validation test cases for comprehensive coverage
|
||||
*/
|
||||
public class GroupProviderAstBuilderTest {
|
||||
private ConnectContext ctx;
|
||||
|
|
@ -248,4 +250,368 @@ public class GroupProviderAstBuilderTest {
|
|||
Assertions.assertEquals("case_test", dropStmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(dropStmt.isIfExists(), "IF EXISTS should be true");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse SHOW CREATE GROUP PROVIDER
|
||||
* Test point: Should correctly parse show create statement
|
||||
* Based on: show create group provider unix_group_provider
|
||||
*/
|
||||
@Test
|
||||
public void testParseShowCreateGroupProvider() throws Exception {
|
||||
String sql = "SHOW CREATE GROUP PROVIDER unix_group_provider";
|
||||
|
||||
ShowCreateGroupProviderStmt stmt =
|
||||
(ShowCreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("unix_group_provider", stmt.getName(), "Provider name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse SHOW GROUP PROVIDERS
|
||||
* Test point: Should correctly parse show all providers statement
|
||||
* Based on: show group providers
|
||||
*/
|
||||
@Test
|
||||
public void testParseShowGroupProviders() throws Exception {
|
||||
String sql = "SHOW GROUP PROVIDERS";
|
||||
|
||||
ShowGroupProvidersStmt stmt =
|
||||
(ShowGroupProvidersStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE GROUP PROVIDER with unsupported type
|
||||
* Test point: Should parse but validation will fail later
|
||||
* Based on: create group provider if not exists foo properties("type" = "foo")
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateGroupProviderUnsupportedType() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS foo PROPERTIES(\"type\" = \"foo\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("foo", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(stmt.isIfNotExists(), "IF NOT EXISTS should be true");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("foo", properties.get("type"), "Type should be foo (unsupported)");
|
||||
}
|
||||
|
||||
// ========== Validation Test Cases ==========
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with missing required 'type' property
|
||||
* Test point: Should fail with missing required property error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderMissingTypeProperty() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(" +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Missing type property should cause validation error
|
||||
Assertions.assertFalse(stmt.getPropertyMap().containsKey("type"),
|
||||
"Missing type property should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with empty type property
|
||||
* Test point: Should fail with empty type validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderEmptyTypeProperty() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(" +
|
||||
"\"type\" = \"\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Empty type should cause validation error
|
||||
Assertions.assertEquals("", stmt.getPropertyMap().get("type"),
|
||||
"Empty type should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with invalid LDAP URL format
|
||||
* Test point: Should fail with invalid URL format validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderInvalidLdapUrl() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"invalid-ldap-url\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("ldap_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Invalid LDAP URL format should cause validation error
|
||||
Assertions.assertEquals("invalid-ldap-url", stmt.getPropertyMap().get("ldap_conn_url"),
|
||||
"Invalid LDAP URL format should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with missing required LDAP properties
|
||||
* Test point: Should fail with missing required LDAP properties error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderMissingLdapProperties() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("ldap_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Missing required LDAP properties should cause validation error
|
||||
Assertions.assertFalse(stmt.getPropertyMap().containsKey("ldap_bind_root_dn"),
|
||||
"Missing ldap_bind_root_dn should cause validation error");
|
||||
Assertions.assertFalse(stmt.getPropertyMap().containsKey("ldap_bind_base_dn"),
|
||||
"Missing ldap_bind_base_dn should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with invalid file URL format
|
||||
* Test point: Should fail with invalid file URL format validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderInvalidFileUrl() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS file_provider PROPERTIES(" +
|
||||
"\"type\" = \"file\", " +
|
||||
"\"file_url\" = \"invalid-file-path\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("file_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Invalid file URL format should cause validation error
|
||||
Assertions.assertEquals("invalid-file-path", stmt.getPropertyMap().get("file_url"),
|
||||
"Invalid file URL format should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with invalid timeout values
|
||||
* Test point: Should fail with invalid timeout validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderInvalidTimeoutValues() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\", " +
|
||||
"\"ldap_conn_timeout\" = \"invalid-timeout\", " +
|
||||
"\"ldap_conn_read_timeout\" = \"-1\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("ldap_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Invalid timeout values should cause validation error
|
||||
Assertions.assertEquals("invalid-timeout", stmt.getPropertyMap().get("ldap_conn_timeout"),
|
||||
"Invalid timeout value should cause validation error");
|
||||
Assertions.assertEquals("-1", stmt.getPropertyMap().get("ldap_conn_read_timeout"),
|
||||
"Negative timeout value should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with invalid boolean values
|
||||
* Test point: Should fail with invalid boolean validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderInvalidBooleanValues() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://localhost:389\", " +
|
||||
"\"ldap_ssl_conn_allow_insecure\" = \"maybe\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("ldap_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Invalid boolean value should cause validation error
|
||||
Assertions.assertEquals("maybe", stmt.getPropertyMap().get("ldap_ssl_conn_allow_insecure"),
|
||||
"Invalid boolean value should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with duplicate properties
|
||||
* Test point: Should handle duplicate properties correctly
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderDuplicateProperties() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(" +
|
||||
"\"type\" = \"unix\", " +
|
||||
"\"type\" = \"ldap\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Duplicate properties should be handled (last one wins or validation error)
|
||||
Assertions.assertEquals("ldap", stmt.getPropertyMap().get("type"),
|
||||
"Duplicate type property should be handled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with reserved property names
|
||||
* Test point: Should validate against reserved property names
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderReservedPropertyNames() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(" +
|
||||
"\"type\" = \"unix\", " +
|
||||
"\"name\" = \"reserved_property\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Reserved property names should cause validation error
|
||||
Assertions.assertTrue(stmt.getPropertyMap().containsKey("name"),
|
||||
"Reserved property 'name' should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with invalid provider name
|
||||
* Test point: Should validate provider name format
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderInvalidName() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS `invalid-name-with-special-chars!@#` PROPERTIES(" +
|
||||
"\"type\" = \"unix\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("invalid-name-with-special-chars!@#", stmt.getName(),
|
||||
"Invalid name should be parsed but cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with too long property values
|
||||
* Test point: Should validate property value length limits
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderLongPropertyValues() throws Exception {
|
||||
// Create a very long LDAP URL
|
||||
StringBuilder longUrl = new StringBuilder("ldap://example.com:389");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
longUrl.append("/dc=example").append(i).append(",dc=com");
|
||||
}
|
||||
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS ldap_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"" + longUrl.toString() + "\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("ldap_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Very long property values should cause validation error
|
||||
Assertions.assertTrue(stmt.getPropertyMap().get("ldap_conn_url").length() > 1000,
|
||||
"Very long ldap_conn_url should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE GROUP PROVIDER with case-sensitive type validation
|
||||
* Test point: Should validate type case sensitivity
|
||||
*/
|
||||
@Test
|
||||
public void testCreateGroupProviderCaseSensitiveType() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS test_provider PROPERTIES(" +
|
||||
"\"type\" = \"UNIX\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("test_provider", stmt.getName(), "Provider name should match");
|
||||
|
||||
// Case-sensitive type should cause validation error
|
||||
Assertions.assertEquals("UNIX", stmt.getPropertyMap().get("type"),
|
||||
"Case-sensitive type 'UNIX' should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE GROUP PROVIDER with Windows type
|
||||
* Test point: Should correctly parse Windows group provider properties
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateGroupProviderWindowsType() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS windows_provider PROPERTIES(" +
|
||||
"\"type\" = \"windows\", " +
|
||||
"\"domain\" = \"example.com\", " +
|
||||
"\"ldap_server\" = \"ldap.example.com\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("windows_provider", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(stmt.isIfNotExists(), "IF NOT EXISTS should be true");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("windows", properties.get("type"), "Type should be windows");
|
||||
Assertions.assertEquals("example.com", properties.get("domain"), "Domain should match");
|
||||
Assertions.assertEquals("ldap.example.com", properties.get("ldap_server"), "LDAP server should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE GROUP PROVIDER with special characters in properties
|
||||
* Test point: Should correctly handle special characters in property values
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateGroupProviderWithSpecialCharacters() throws Exception {
|
||||
String sql = "CREATE GROUP PROVIDER IF NOT EXISTS special_provider PROPERTIES(" +
|
||||
"\"type\" = \"ldap\", " +
|
||||
"\"ldap_conn_url\" = \"ldap://server:389/dc=example,dc=com?cn?sub?(objectClass=person)\", " +
|
||||
"\"ldap_bind_root_dn\" = \"cn=admin,ou=users,dc=example,dc=com\")";
|
||||
|
||||
CreateGroupProviderStmt stmt =
|
||||
(CreateGroupProviderStmt) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("special_provider", stmt.getName(), "Provider name should match");
|
||||
Assertions.assertTrue(stmt.isIfNotExists(), "IF NOT EXISTS should be true");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("ldap", properties.get("type"), "Type should be ldap");
|
||||
Assertions.assertEquals("ldap://server:389/dc=example,dc=com?cn?sub?(objectClass=person)",
|
||||
properties.get("ldap_conn_url"), "LDAP URL with special characters should match");
|
||||
Assertions.assertEquals("cn=admin,ou=users,dc=example,dc=com",
|
||||
properties.get("ldap_bind_root_dn"), "Root DN with special characters should match");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,518 @@
|
|||
// 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.parser;
|
||||
|
||||
import com.starrocks.catalog.UserIdentity;
|
||||
import com.starrocks.qe.ConnectContext;
|
||||
import com.starrocks.sql.ast.integration.AlterSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.CreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.DropSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowCreateSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.ast.integration.ShowSecurityIntegrationStatement;
|
||||
import com.starrocks.sql.parser.SqlParser;
|
||||
import com.starrocks.utframe.UtFrameUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Unit tests for Security Integration AST parsing and validation functionality
|
||||
* Based on test cases from test/sql/test_security_integration
|
||||
* <p>
|
||||
* This class covers:
|
||||
* 1. AST parsing functionality for all Security Integration operations
|
||||
* 2. Validation logic and error scenarios
|
||||
* 3. Edge cases and boundary conditions
|
||||
* 4. Property validation and format checking
|
||||
*/
|
||||
public class SecurityIntegrationAstBuilderTest {
|
||||
private ConnectContext ctx;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
ctx = UtFrameUtils.initCtxForNewPrivilege(UserIdentity.ROOT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE SECURITY INTEGRATION with OIDC properties
|
||||
* Test point: Should correctly parse JWT authentication properties
|
||||
* Based on: create security integration oidc properties("type"="authentication_jwt", "jwks_url"="jwks.json",
|
||||
* "principal_field"="sub")
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateSecurityIntegrationOIDC() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("authentication_jwt", properties.get("type"), "Type should be authentication_jwt");
|
||||
Assertions.assertEquals("jwks.json", properties.get("jwks_url"), "JWKS URL should match");
|
||||
Assertions.assertEquals("sub", properties.get("principal_field"), "Principal field should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE SECURITY INTEGRATION with quoted identifier
|
||||
* Test point: Should correctly handle quoted integration names
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateSecurityIntegrationWithQuotedIdentifier() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION `test-integration` PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("test-integration", stmt.getName(), "Integration name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse ALTER SECURITY INTEGRATION
|
||||
* Test point: Should correctly parse property modification
|
||||
* Based on: alter security integration oidc set ("principal_field"="preferred_name")
|
||||
*/
|
||||
@Test
|
||||
public void testParseAlterSecurityIntegration() throws Exception {
|
||||
String sql = "ALTER SECURITY INTEGRATION oidc SET (\"principal_field\" = \"preferred_name\")";
|
||||
|
||||
AlterSecurityIntegrationStatement stmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = stmt.getProperties();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("preferred_name", properties.get("principal_field"), "Principal field should be updated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse ALTER SECURITY INTEGRATION with multiple properties
|
||||
* Test point: Should correctly parse multiple property modifications
|
||||
*/
|
||||
@Test
|
||||
public void testParseAlterSecurityIntegrationMultipleProperties() throws Exception {
|
||||
String sql = "ALTER SECURITY INTEGRATION oidc SET (" +
|
||||
"\"principal_field\" = \"preferred_name\", " +
|
||||
"\"jwks_url\" = \"new_jwks.json\")";
|
||||
|
||||
AlterSecurityIntegrationStatement stmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = stmt.getProperties();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("preferred_name", properties.get("principal_field"), "Principal field should be updated");
|
||||
Assertions.assertEquals("new_jwks.json", properties.get("jwks_url"), "JWKS URL should be updated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse DROP SECURITY INTEGRATION
|
||||
* Test point: Should correctly parse drop statement
|
||||
* Based on: drop security integration oidc
|
||||
*/
|
||||
@Test
|
||||
public void testParseDropSecurityIntegration() throws Exception {
|
||||
String sql = "DROP SECURITY INTEGRATION oidc";
|
||||
|
||||
DropSecurityIntegrationStatement stmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse DROP SECURITY INTEGRATION with quoted identifier
|
||||
* Test point: Should correctly handle quoted integration names
|
||||
*/
|
||||
@Test
|
||||
public void testParseDropSecurityIntegrationWithQuotedIdentifier() throws Exception {
|
||||
String sql = "DROP SECURITY INTEGRATION `test-integration`";
|
||||
|
||||
DropSecurityIntegrationStatement stmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("test-integration", stmt.getName(), "Integration name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse SHOW CREATE SECURITY INTEGRATION
|
||||
* Test point: Should correctly parse show create statement
|
||||
* Based on: show create security integration oidc
|
||||
*/
|
||||
@Test
|
||||
public void testParseShowCreateSecurityIntegration() throws Exception {
|
||||
String sql = "SHOW CREATE SECURITY INTEGRATION oidc";
|
||||
|
||||
ShowCreateSecurityIntegrationStatement stmt =
|
||||
(ShowCreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse SHOW SECURITY INTEGRATIONS
|
||||
* Test point: Should correctly parse show all integrations statement
|
||||
* Based on: show security integrations
|
||||
*/
|
||||
@Test
|
||||
public void testParseShowSecurityIntegrations() throws Exception {
|
||||
String sql = "SHOW SECURITY INTEGRATIONS";
|
||||
|
||||
ShowSecurityIntegrationStatement stmt =
|
||||
(ShowSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE SECURITY INTEGRATION with complex properties
|
||||
* Test point: Should correctly parse multiple properties with various types
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateSecurityIntegrationWithComplexProperties() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION complex_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"https://example.com/.well-known/jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\", " +
|
||||
"\"issuer\" = \"https://example.com\", " +
|
||||
"\"audience\" = \"starrocks-client\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("complex_oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("authentication_jwt", properties.get("type"), "Type should be authentication_jwt");
|
||||
Assertions.assertEquals("https://example.com/.well-known/jwks.json", properties.get("jwks_url"), "JWKS URL should match");
|
||||
Assertions.assertEquals("sub", properties.get("principal_field"), "Principal field should match");
|
||||
Assertions.assertEquals("https://example.com", properties.get("issuer"), "Issuer should match");
|
||||
Assertions.assertEquals("starrocks-client", properties.get("audience"), "Audience should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse statements with case variations
|
||||
* Test point: Should handle case-insensitive keywords correctly
|
||||
*/
|
||||
@Test
|
||||
public void testParseWithCaseVariations() throws Exception {
|
||||
// Test CREATE with different case
|
||||
String createSql =
|
||||
"create security integration case_test PROPERTIES(\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
CreateSecurityIntegrationStatement createStmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(createSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(createStmt, "Create statement should not be null");
|
||||
Assertions.assertEquals("case_test", createStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test ALTER with different case
|
||||
String alterSql = "alter security integration case_test set (\"principal_field\" = \"preferred_name\")";
|
||||
AlterSecurityIntegrationStatement alterStmt =
|
||||
(AlterSecurityIntegrationStatement) SqlParser.parseSingleStatement(alterSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(alterStmt, "Alter statement should not be null");
|
||||
Assertions.assertEquals("case_test", alterStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test DROP with different case
|
||||
String dropSql = "drop security integration case_test";
|
||||
DropSecurityIntegrationStatement dropStmt =
|
||||
(DropSecurityIntegrationStatement) SqlParser.parseSingleStatement(dropSql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(dropStmt, "Drop statement should not be null");
|
||||
Assertions.assertEquals("case_test", dropStmt.getName(), "Integration name should match");
|
||||
|
||||
// Test SHOW with different case
|
||||
String showSql = "show create security integration case_test";
|
||||
ShowCreateSecurityIntegrationStatement showStmt =
|
||||
(ShowCreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(showSql,
|
||||
ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(showStmt, "Show statement should not be null");
|
||||
Assertions.assertEquals("case_test", showStmt.getName(), "Integration name should match");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: Parse CREATE SECURITY INTEGRATION with minimal required properties
|
||||
* Test point: Should handle minimal property sets correctly
|
||||
*/
|
||||
@Test
|
||||
public void testParseCreateSecurityIntegrationMinimalProperties() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION minimal_oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should not be null");
|
||||
Assertions.assertEquals("minimal_oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
Map<String, String> properties = stmt.getPropertyMap();
|
||||
Assertions.assertNotNull(properties, "Properties should not be null");
|
||||
Assertions.assertEquals("authentication_jwt", properties.get("type"), "Type should be authentication_jwt");
|
||||
Assertions.assertEquals(1, properties.size(), "Should have only one property");
|
||||
}
|
||||
|
||||
// ==================== Validation Tests ====================
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION without required 'type' property
|
||||
* Test point: Should fail with missing required property error
|
||||
* Based on: create security integration oidc properties("jwks_url"="jwks.json", "principal_field"="sub")
|
||||
* Expected error: (1064, 'Getting analyzing error. Detail message: missing required property: type.')
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationMissingTypeProperty() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
// This should parse successfully but fail during validation
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// The validation logic would check for required 'type' property
|
||||
// In a real implementation, this would be validated in the analyzer/validator phase
|
||||
Assertions.assertFalse(stmt.getPropertyMap().containsKey("type"),
|
||||
"Type property should be missing, causing validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with empty type property
|
||||
* Test point: Should fail with empty type validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationEmptyTypeProperty() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Empty type should cause validation error
|
||||
Assertions.assertEquals("", stmt.getPropertyMap().get("type"),
|
||||
"Empty type should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with invalid type value
|
||||
* Test point: Should fail with invalid type validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationInvalidTypeProperty() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"invalid_type\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Invalid type should cause validation error
|
||||
Assertions.assertEquals("invalid_type", stmt.getPropertyMap().get("type"),
|
||||
"Invalid type should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with missing jwks_url for JWT type
|
||||
* Test point: Should fail with missing required property error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationMissingJwksUrl() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Missing jwks_url for JWT type should cause validation error
|
||||
Assertions.assertFalse(stmt.getPropertyMap().containsKey("jwks_url"),
|
||||
"Missing jwks_url should cause validation error for JWT type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with invalid jwks_url format
|
||||
* Test point: Should fail with invalid URL format validation error
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationInvalidJwksUrl() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"invalid-url\", " +
|
||||
"\"principal_field\" = \"sub\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Invalid jwks_url format should cause validation error
|
||||
Assertions.assertEquals("invalid-url", stmt.getPropertyMap().get("jwks_url"),
|
||||
"Invalid jwks_url format should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with duplicate properties
|
||||
* Test point: Should handle duplicate properties correctly
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationDuplicateProperties() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"type\" = \"authentication_jwt\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Duplicate properties should be handled (last one wins or validation error)
|
||||
Assertions.assertEquals("authentication_jwt", stmt.getPropertyMap().get("type"),
|
||||
"Duplicate type property should be handled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with null property values
|
||||
* Test point: Should handle null property values correctly
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationNullPropertyValues() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"principal_field\" = \"\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Empty principal_field should cause validation error
|
||||
Assertions.assertEquals("", stmt.getPropertyMap().get("principal_field"),
|
||||
"Empty principal_field should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with reserved property names
|
||||
* Test point: Should validate against reserved property names
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationReservedPropertyNames() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\", " +
|
||||
"\"name\" = \"reserved_property\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Reserved property names should cause validation error
|
||||
Assertions.assertTrue(stmt.getPropertyMap().containsKey("name"),
|
||||
"Reserved property 'name' should cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with invalid integration name
|
||||
* Test point: Should validate integration name format
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationInvalidName() throws Exception {
|
||||
String sql = "CREATE SECURITY INTEGRATION `invalid-name-with-special-chars!@#` PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"jwks.json\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("invalid-name-with-special-chars!@#", stmt.getName(),
|
||||
"Invalid name should be parsed but cause validation error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case: CREATE SECURITY INTEGRATION with too long property values
|
||||
* Test point: Should validate property value length limits
|
||||
*/
|
||||
@Test
|
||||
public void testCreateSecurityIntegrationLongPropertyValues() throws Exception {
|
||||
// Create a very long URL
|
||||
StringBuilder longUrl = new StringBuilder("https://example.com/.well-known/jwks.json");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
longUrl.append("?param").append(i).append("=value").append(i);
|
||||
}
|
||||
|
||||
String sql = "CREATE SECURITY INTEGRATION oidc PROPERTIES(" +
|
||||
"\"type\" = \"authentication_jwt\", " +
|
||||
"\"jwks_url\" = \"" + longUrl.toString() + "\")";
|
||||
|
||||
CreateSecurityIntegrationStatement stmt =
|
||||
(CreateSecurityIntegrationStatement) SqlParser.parseSingleStatement(sql, ctx.getSessionVariable().getSqlMode());
|
||||
|
||||
Assertions.assertNotNull(stmt, "Statement should parse successfully");
|
||||
Assertions.assertEquals("oidc", stmt.getName(), "Integration name should match");
|
||||
|
||||
// Very long property values should cause validation error
|
||||
Assertions.assertTrue(stmt.getPropertyMap().get("jwks_url").length() > 1000,
|
||||
"Very long jwks_url should cause validation error");
|
||||
}
|
||||
}
|
||||
|
|
@ -1856,7 +1856,7 @@ privilegeType
|
|||
| CREATE (
|
||||
DATABASE| TABLE| VIEW| FUNCTION| GLOBAL FUNCTION| MATERIALIZED VIEW|
|
||||
RESOURCE| RESOURCE GROUP| EXTERNAL CATALOG | STORAGE VOLUME | WAREHOUSE | CNGROUP | PIPE )
|
||||
| DELETE | DROP | EXPORT | FILE | IMPERSONATE | INSERT | GRANT | NODE | OPERATE
|
||||
| DELETE | DROP | EXPORT | FILE | IMPERSONATE | INSERT | GRANT | NODE | OPERATE | SECURITY
|
||||
| PLUGIN | REPOSITORY| REFRESH | SELECT | UPDATE | USAGE
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,24 @@ create user test_impersonate;
|
|||
grant impersonate on user root to test_impersonate;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user if exists u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u1;
|
||||
-- result:
|
||||
-- !result
|
||||
grant impersonate on user root to u1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user if exists u2;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u2;
|
||||
-- result:
|
||||
-- !result
|
||||
grant impersonate on user root to u2;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists g1_role;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -23,18 +41,6 @@ create role g1_role;
|
|||
grant g1_role to external group r1_group;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists r1;
|
||||
-- result:
|
||||
-- !result
|
||||
create role r1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user if exists u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u1 default role r1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists g2_role;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -44,18 +50,6 @@ create role g2_role;
|
|||
grant g2_role to external group r2_group;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists r2;
|
||||
-- result:
|
||||
-- !result
|
||||
create role r2;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user if exists u2;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u2 default role r2;
|
||||
-- result:
|
||||
-- !result
|
||||
grant impersonate on user u1 to test_impersonate;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -92,7 +86,7 @@ r1_group
|
|||
-- !result
|
||||
select current_role();
|
||||
-- result:
|
||||
g1_role, r1
|
||||
g1_role
|
||||
-- !result
|
||||
execute as test_impersonate with no revert;
|
||||
-- result:
|
||||
|
|
@ -118,7 +112,7 @@ r2_group
|
|||
-- !result
|
||||
select current_role();
|
||||
-- result:
|
||||
g2_role, r2
|
||||
g2_role
|
||||
-- !result
|
||||
execute as test_impersonate with no revert;
|
||||
-- result:
|
||||
|
|
@ -137,12 +131,6 @@ execute as root with no revert;
|
|||
drop user if exists test_impersonate;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists r1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists r2;
|
||||
-- result:
|
||||
-- !result
|
||||
drop role if exists g1_role;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
|
|||
|
|
@ -6,24 +6,22 @@ drop user if exists test_impersonate;
|
|||
create user test_impersonate;
|
||||
grant impersonate on user root to test_impersonate;
|
||||
|
||||
drop user if exists u1;
|
||||
create user u1;
|
||||
grant impersonate on user root to u1;
|
||||
|
||||
drop user if exists u2;
|
||||
create user u2;
|
||||
grant impersonate on user root to u2;
|
||||
|
||||
drop role if exists g1_role;
|
||||
create role g1_role;
|
||||
grant g1_role to external group r1_group;
|
||||
|
||||
drop role if exists r1;
|
||||
create role r1;
|
||||
drop user if exists u1;
|
||||
create user u1 default role r1;
|
||||
|
||||
drop role if exists g2_role;
|
||||
create role g2_role;
|
||||
grant g2_role to external group r2_group;
|
||||
|
||||
drop role if exists r2;
|
||||
create role r2;
|
||||
drop user if exists u2;
|
||||
create user u2 default role r2;
|
||||
|
||||
grant impersonate on user u1 to test_impersonate;
|
||||
grant impersonate on user u2 to test_impersonate;
|
||||
grant impersonate on user test_impersonate to u1;
|
||||
|
|
@ -53,8 +51,6 @@ select current_role();
|
|||
|
||||
execute as root with no revert;
|
||||
drop user if exists test_impersonate;
|
||||
drop role if exists r1;
|
||||
drop role if exists r2;
|
||||
drop role if exists g1_role;
|
||||
drop role if exists g2_role;
|
||||
drop user if exists u1;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,4 @@
|
|||
-- name: test_group_provider
|
||||
drop user if exists u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u1;
|
||||
-- result:
|
||||
-- !result
|
||||
grant impersonate on user root to u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create group provider if not exists foo properties("type" = "foo");
|
||||
-- result:
|
||||
E: (1064, "Getting analyzing error. Detail message: unsupported group provider type 'foo'.")
|
||||
-- !result
|
||||
create group provider if not exists unix_group_provider properties("type" = "unix");
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -21,31 +8,6 @@ unix_group_provider CREATE GROUP PROVIDER `unix_group_provider` PROPERTIES (
|
|||
"type" = "unix"
|
||||
)
|
||||
-- !result
|
||||
execute as u1 with no revert;
|
||||
-- result:
|
||||
-- !result
|
||||
show group providers;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
show create group provider unix_group_provider;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
drop group provider if exists unix_group_provider;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
create group provider if not exists unix_group_provider2 properties("type" = "unix");
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
execute as root with no revert;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user u1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop group provider if exists unix_group_provider;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -1,17 +1,4 @@
|
|||
-- name: test_security_integration
|
||||
drop user if exists u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create user u1;
|
||||
-- result:
|
||||
-- !result
|
||||
grant impersonate on user root to u1;
|
||||
-- result:
|
||||
-- !result
|
||||
create security integration oidc properties("jwks_url"="jwks.json", "principal_field"="sub");
|
||||
-- result:
|
||||
E: (1064, 'Getting analyzing error. Detail message: missing required property: type.')
|
||||
-- !result
|
||||
create security integration oidc properties("type"="authentication_jwt", "jwks_url"="jwks.json", "principal_field"="sub");
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -30,35 +17,6 @@ oidc CREATE SECURITY INTEGRATION `oidc` PROPERTIES (
|
|||
"type" = "authentication_jwt", "principal_field" = "preferred_name", "jwks_url" = "jwks.json"
|
||||
)
|
||||
-- !result
|
||||
execute as u1 with no revert;
|
||||
-- result:
|
||||
-- !result
|
||||
show security integrations;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
show create security integration oidc;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
drop security integration oidc;
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
alter security integration oidc set ("principal_field"="preferred_name");
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
create security integration oidc2 properties("type"="authentication_jwt", "jwks_url"="jwks.json", "principal_field"="sub");
|
||||
-- result:
|
||||
E: (5203, 'Access denied; you need (at least one of) the SECURITY privilege(s) on SYSTEM for this operation. Please ask the admin to grant permission(s) or try activating existing roles using <set [default] role>. Current role(s): NONE. Inactivated role(s): NONE.')
|
||||
-- !result
|
||||
execute as root with no revert;
|
||||
-- result:
|
||||
-- !result
|
||||
drop user u1;
|
||||
-- result:
|
||||
-- !result
|
||||
drop security integration oidc;
|
||||
-- result:
|
||||
-- !result
|
||||
|
|
@ -1,22 +1,4 @@
|
|||
-- name: test_group_provider
|
||||
|
||||
drop user if exists u1;
|
||||
create user u1;
|
||||
grant impersonate on user root to u1;
|
||||
|
||||
create group provider if not exists foo properties("type" = "foo");
|
||||
|
||||
create group provider if not exists unix_group_provider properties("type" = "unix");
|
||||
show create group provider unix_group_provider;
|
||||
|
||||
execute as u1 with no revert;
|
||||
show group providers;
|
||||
show create group provider unix_group_provider;
|
||||
|
||||
drop group provider if exists unix_group_provider;
|
||||
create group provider if not exists unix_group_provider2 properties("type" = "unix");
|
||||
|
||||
execute as root with no revert;
|
||||
drop user u1;
|
||||
|
||||
drop group provider if exists unix_group_provider;
|
||||
|
|
@ -1,24 +1,6 @@
|
|||
-- name: test_security_integration
|
||||
|
||||
drop user if exists u1;
|
||||
create user u1;
|
||||
grant impersonate on user root to u1;
|
||||
|
||||
create security integration oidc properties("jwks_url"="jwks.json", "principal_field"="sub");
|
||||
|
||||
create security integration oidc properties("type"="authentication_jwt", "jwks_url"="jwks.json", "principal_field"="sub");
|
||||
show create security integration oidc;
|
||||
alter security integration oidc set ("principal_field"="preferred_name");
|
||||
show create security integration oidc;
|
||||
|
||||
execute as u1 with no revert;
|
||||
show security integrations;
|
||||
show create security integration oidc;
|
||||
drop security integration oidc;
|
||||
alter security integration oidc set ("principal_field"="preferred_name");
|
||||
create security integration oidc2 properties("type"="authentication_jwt", "jwks_url"="jwks.json", "principal_field"="sub");
|
||||
|
||||
execute as root with no revert;
|
||||
drop user u1;
|
||||
|
||||
drop security integration oidc;
|
||||
Loading…
Reference in New Issue