Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(connection): supports query connections by username #1981

Merged
merged 15 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class ConnectionConfigRepositoryTest extends ServiceTestEnv {
private static final String NAME = "TEST_C1";
private static final String CLUSTER_NAME = "C1";
private static final String TENANT_NAME = "T1";
private static final String USERNAME = "odcTest";

@Autowired
private ConnectionConfigRepository repository;
Expand Down Expand Up @@ -148,7 +149,8 @@ public void findAll_BySpecsMatch_Found() {
.and(ConnectionSpecs.idLike(saved.getId().toString()))
.and(ConnectionSpecs.hostLike(connection.getHost()))
.and(ConnectionSpecs.portLike(connection.getPort().toString()))
.and(ConnectionSpecs.isNotTemp());
.and(ConnectionSpecs.isNotTemp())
.and(ConnectionSpecs.usernameEqual(USERNAME));

List<ConnectionEntity> all = repository.findAll(spec);

Expand Down Expand Up @@ -280,6 +282,7 @@ private ConnectionEntity createEntity(ConnectionVisibleScope visibleScope) {
entity.setCreateTime(null);
entity.setUpdateTime(null);
entity.setTemp(false);
entity.setUsername(USERNAME);
if (visibleScope == ConnectionVisibleScope.PRIVATE) {
entity.setOwnerId(CREATOR_ID);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,6 @@ public void setUp() throws Exception {
statusManager.clear();
}

@Test
public void getAndRefreshStatus_PasswordSavedFalse_Return_NONPASSWORD() {
ConnectionConfig connection = newConnection();
connection.setPasswordSaved(false);
CheckState checkState = statusManager.getAndRefreshStatus(connection);
Assert.assertEquals(ConnectionStatus.NOPASSWORD, checkState.getStatus());
}

@Test
public void getAndRefreshStatus_EnabledFalse_Return_DISABLED() {
ConnectionConfig connection = newConnection();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class ConnectionTestingTest {
@Mock
private ConnectionSSLAdaptor sslAdaptor;
@Mock
private ConnectionEnvironmentAdapter environmentAdapter;
private DefaultConnectionAdapter environmentAdapter;

@Before
public void setUp() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ public enum ConnectionStatus {
* 检测中
*/
TESTING,
/**
* 密码未保存,不进行检测
*/
NOPASSWORD,
/**
* 连接已禁用,不进行检测
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ public static Specification<ConnectionEntity> userIdEqual(Long userId) {
return columnEqual("visibleScope", ConnectionVisibleScope.PRIVATE).and(columnEqual("ownerId", userId));
}

public static Specification<ConnectionEntity> usernameEqual(String username) {
PeachThinking marked this conversation as resolved.
Show resolved Hide resolved
return columnEqual("username", username);
}

public static Specification<ConnectionEntity> dialectTypeIn(List<DialectType> dialectTypes) {
return in("dialectType", dialectTypes, DialectType.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 OceanBase.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.oceanbase.odc.service.connection;

import com.oceanbase.odc.service.connection.model.CloudConnectionConfig;

/**
* @author jingtian
* @date 2024/4/2
*/
public interface ConnectionAdapter {
public <T extends CloudConnectionConfig> T adaptConfig(T connectionConfig);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class ConnectionEncryption {
@Autowired
private EncryptionFacade encryptionFacade;

ConnectionConfig encryptPasswords(ConnectionConfig connection) {
public ConnectionConfig encryptPasswords(ConnectionConfig connection) {
if (Objects.isNull(connection.getCipher())) {
connection.setCipher(Cipher.AES256SALT);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public class ConnectionService {
private ConnectProperties connectProperties;

@Autowired
private ConnectionEnvironmentAdapter environmentAdapter;
private DefaultConnectionAdapter environmentAdapter;

@Autowired
private ConnectionSSLAdaptor connectionSSLAdaptor;
Expand Down Expand Up @@ -294,6 +294,7 @@ public ConnectionConfig innerCreate(@NotNull @Valid ConnectionConfig connection,
PreConditions.notNull(connection.getPassword(), "connection.password");
} else {
connection.setPassword(null);
connection.setPasswordEncrypted(null);
}
connectionEncryption.encryptPasswords(connection);

Expand Down Expand Up @@ -588,6 +589,9 @@ public ConnectionConfig update(@NotNull Long id, @NotNull @Valid ConnectionConfi
new Object[] {connection.getName()}, "same datasource name exists");
}
});
if (Boolean.FALSE.equals(connection.getPasswordSaved())) {
connection.setPassword(null);
}
connectionEncryption.encryptPasswords(connection);
connection.fillEncryptedPasswordFromSavedIfNull(saved);

Expand Down Expand Up @@ -748,6 +752,9 @@ private Page<ConnectionConfig> innerList(@NotNull QueryConnectionParams params,
if (CollectionUtils.isNotEmpty(params.getIds())) {
spec = spec.and(ConnectionSpecs.idIn(params.getIds()));
}
if (Objects.nonNull(params.getUsername())) {
spec = spec.and(ConnectionSpecs.usernameEqual(params.getUsername()));
}
spec = spec.and(ConnectionSpecs.sort(pageable.getSort()));
Pageable page = pageable.equals(Pageable.unpaged()) ? pageable
: PageRequest.of(pageable.getPageNumber(), pageable.getPageSize());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ public class ConnectionStatusManager {

CheckState getAndRefreshStatus(ConnectionConfig connection) {
PreConditions.notNull(connection, "connection");
if (Objects.nonNull(connection.getPasswordSaved()) && !connection.getPasswordSaved()) {
return CheckState.of(ConnectionStatus.NOPASSWORD);
}
if (Objects.nonNull(connection.getEnabled()) && !connection.getEnabled()) {
return CheckState.of(ConnectionStatus.DISABLED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class ConnectionTesting {
@Autowired
private ConnectProperties connectProperties;
@Autowired
private ConnectionEnvironmentAdapter environmentAdapter;
private DefaultConnectionAdapter environmentAdapter;
@Autowired
private ConnectionSSLAdaptor connectionSSLAdaptor;
@Autowired
Expand All @@ -82,10 +82,9 @@ public class ConnectionTesting {

public ConnectionTestResult test(@NotNull @Valid TestConnectionReq req) {
PreConditions.notNull(req, "req");
environmentAdapter.adaptConfig(req);
PreConditions.validArgumentState(Objects.nonNull(req.getPassword()),
ErrorCodes.ConnectionPasswordMissed, null, "password required for connection without password saved");

environmentAdapter.adaptConfig(req);
cloudMetadataClient.checkPermission(OBTenant.of(req.getClusterName(),
req.getTenantName()), req.getInstanceType(), false, CloudPermissionAction.READONLY);
connectionSSLAdaptor.adapt(req);
Expand Down Expand Up @@ -232,7 +231,6 @@ private ConnectionConfig reqToConnectionConfig(TestConnectionReq req) {

OBTenantEndpoint endpoint = req.getEndpoint();
if (Objects.nonNull(endpoint) && OceanBaseAccessMode.IC_PROXY == endpoint.getAccessMode()) {
config.setClusterName(null);
config.setEndpoint(endpoint);
}
if (StringUtils.isNotBlank(req.getOBTenantName())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import com.oceanbase.odc.service.connection.model.OBTenantEndpoint;

@Component
public class ConnectionEnvironmentAdapter {
public class DefaultConnectionAdapter implements ConnectionAdapter {

@Autowired
private CloudMetadataClient cloudMetadataClient;
Expand Down Expand Up @@ -71,5 +71,4 @@ public <T extends CloudConnectionConfig> T adaptConfig(T connectionConfig) {
connectionConfig.setEndpoint(endpoint);
return connectionConfig;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@
package com.oceanbase.odc.service.connection.database;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.oceanbase.odc.core.authority.util.SkipAuthorize;
import com.oceanbase.odc.core.shared.exception.BadRequestException;
import com.oceanbase.odc.metadb.iam.UserEntity;
import com.oceanbase.odc.service.connection.model.ConnectionConfig;
import com.oceanbase.odc.service.iam.UserService;
import com.oceanbase.odc.service.iam.util.SecurityContextUtils;

import lombok.NonNull;
Expand All @@ -41,15 +48,27 @@
public class DatabaseSyncManager {
@Autowired
private DatabaseService databaseService;
@Autowired
private UserService userService;

@Autowired
@Qualifier("syncDatabaseTaskExecutor")
private ThreadPoolTaskExecutor executor;

LoadingCache<Long, UserEntity> id2UserEntity = CacheBuilder.newBuilder().maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<Long, UserEntity>() {
@Override
public UserEntity load(Long creatorId) {
return userService.nullSafeGet(creatorId);
}
});

@SkipAuthorize("internal usage")
public Future<Boolean> submitSyncDataSourceTask(@NonNull ConnectionConfig connection) {
return doExecute(() -> executor.submit(() -> {
SecurityContextUtils.setCurrentUser(connection.getCreatorId(), connection.getOrganizationId(), null);
Long creatorId = connection.getCreatorId();
SecurityContextUtils.setCurrentUser(creatorId, connection.getOrganizationId(), getAccountName(creatorId));
return databaseService.internalSyncDataSourceSchemas(connection.getId());
}));
}
Expand All @@ -61,4 +80,15 @@ private Future<Boolean> doExecute(Supplier<Future<Boolean>> supplier) {
throw new BadRequestException("sync database failed");
}
}

private String getAccountName(@NonNull Long creatorId) {
try {
UserEntity userEntity = id2UserEntity.get(creatorId);
Validate.notNull(userEntity, "UserEntity not found by id:" + creatorId);
return userEntity.getAccountName();
} catch (Exception e) {
log.warn("Failed to get user entity from cache, message:{}", e.getMessage());
return null;
PeachThinking marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,9 @@ public void fillPasswordFromSavedIfNull(ConnectionConfig saved) {
}

public void fillEncryptedPasswordFromSavedIfNull(ConnectionConfig saved) {
if (Boolean.FALSE.equals(this.getPasswordSaved())) {
return;
}
PreConditions.notNull(saved, "saved");
if (Objects.isNull(this.passwordEncrypted)) {
setPasswordEncrypted(saved.getPasswordEncrypted());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ public enum OBInstanceStatus {

ABNORMAL("ABNORMAL"),

OFFLINE("OFFLINE");
OFFLINE("OFFLINE"),

PENDING_STOP("PENDING_STOP"),

STOPPED("STOPPED"),

PENDING_START("PENDING_START");

private String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public enum OBInstanceType implements Translatable {
MYSQL_TENANT("mtenant"),
ORACLE_TENANT("otenant"),
MYSQL_SERVERLESS("mtenant_serverless"),
ORACLE_SERVERLESS("otenant_serverless");
ORACLE_SERVERLESS("otenant_serverless"),
DEDICATED("DEDICATED");

@Getter
private String value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,9 @@ public class QueryConnectionParams {
private List<String> permittedActions;
private String hostPort;
private String name;
/**
* Database username
*/
private String username;

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
import com.oceanbase.odc.core.shared.exception.InternalServerError;
import com.oceanbase.odc.core.shared.exception.NotFoundException;
import com.oceanbase.odc.core.shared.exception.OverLimitException;
import com.oceanbase.odc.core.shared.exception.VerifyException;
import com.oceanbase.odc.core.sql.execute.task.SqlExecuteTaskManagerFactory;
import com.oceanbase.odc.core.sql.split.SqlCommentProcessor;
import com.oceanbase.odc.core.task.DefaultTaskManager;
Expand All @@ -77,7 +76,6 @@
import com.oceanbase.odc.service.connection.database.model.Database;
import com.oceanbase.odc.service.connection.model.ConnectProperties;
import com.oceanbase.odc.service.connection.model.ConnectionConfig;
import com.oceanbase.odc.service.connection.model.ConnectionTestResult;
import com.oceanbase.odc.service.connection.model.CreateSessionReq;
import com.oceanbase.odc.service.connection.model.CreateSessionResp;
import com.oceanbase.odc.service.connection.model.DBSessionResp;
Expand Down Expand Up @@ -257,6 +255,8 @@ public ConnectionSession create(@NotNull CreateSessionReq req) {
ConnectionConfig connection = connectionService.getForConnectionSkipPermissionCheck(dataSourceId);
cloudMetadataClient.checkPermission(OBTenant.of(connection.getClusterName(),
connection.getTenantName()), connection.getInstanceType(), false, CloudPermissionAction.READONLY);
PreConditions.validArgumentState(Objects.nonNull(connection.getPassword()),
ErrorCodes.ConnectionPasswordMissed, null, "password required for connection without password saved");
if (StringUtils.isNotBlank(schemaName) && connection.getDialectType().isOracle()) {
schemaName = com.oceanbase.odc.common.util.StringUtils.quoteOracleIdentifier(schemaName);
}
Expand All @@ -265,10 +265,6 @@ public ConnectionSession create(@NotNull CreateSessionReq req) {
Set<String> actions = authorizationFacade.getAllPermittedActions(authenticationFacade.currentUser(),
ResourceType.ODC_CONNECTION, "" + dataSourceId);
connection.setPermittedActions(actions);
ConnectionTestResult result = connectionTesting.test(connection);
if (!result.isActive() && result.getErrorCode() != ErrorCodes.ConnectionInitScriptFailed) {
throw new VerifyException(result.getErrorMessage());
}
SqlExecuteTaskManagerFactory factory =
new SqlExecuteTaskManagerFactory(this.monitorTaskManager, "console", 1);
if (StringUtils.isNotEmpty(schemaName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
@DependsOn({"localObjectStorageFacade", "springContextUtil"})
// can't remove ,cause migrate is after environment properties set.
@ConditionalOnProperty(value = "odc.iam.auth.type",
havingValues = {"buc", "oauth2", "obcloud"}, collectionProperty = true)
havingValues = {"buc", "oauth2"}, collectionProperty = true)
PeachThinking marked this conversation as resolved.
Show resolved Hide resolved
public class WebModeMultiOrganizationMetaDB extends AbstractWebModeMetaDB {

@Override
Expand Down
Loading