mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +08:00
fix(datasource): re-encrypt password on connection test and discover PostgreSQL views
This commit is contained in:
parent
b23cc32f33
commit
19bd612c9a
@ -109,6 +109,7 @@ public class DatasourceService {
|
|||||||
// 更新测试结果
|
// 更新测试结果
|
||||||
entity.setLastTestTime(LocalDateTime.now());
|
entity.setLastTestTime(LocalDateTime.now());
|
||||||
entity.setLastTestOk(ok);
|
entity.setLastTestOk(ok);
|
||||||
|
encryptPassword(entity);
|
||||||
datasourceMapper.updateById(entity);
|
datasourceMapper.updateById(entity);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,9 +110,11 @@ public class DatasourceTool {
|
|||||||
"SELECT TABLE_NAME, TABLE_COMMENT, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = '%s' ORDER BY TABLE_NAME",
|
"SELECT TABLE_NAME, TABLE_COMMENT, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = '%s' ORDER BY TABLE_NAME",
|
||||||
sanitizeIdentifier(entity.getDatabaseName()));
|
sanitizeIdentifier(entity.getDatabaseName()));
|
||||||
case "postgresql" -> String.format(
|
case "postgresql" -> String.format(
|
||||||
"SELECT tablename AS table_name, obj_description(c.oid) AS table_comment " +
|
"SELECT t.table_name, obj_description(c.oid) AS table_comment " +
|
||||||
"FROM pg_tables t LEFT JOIN pg_class c ON c.relname = t.tablename " +
|
"FROM information_schema.tables t " +
|
||||||
"WHERE t.schemaname = '%s' ORDER BY tablename",
|
"LEFT JOIN pg_namespace n ON n.nspname = t.table_schema " +
|
||||||
|
"LEFT JOIN pg_class c ON c.relnamespace = n.oid AND c.relname = t.table_name " +
|
||||||
|
"WHERE t.table_schema = '%s' ORDER BY t.table_name",
|
||||||
sanitizeIdentifier(entity.getSchemaName() != null ? entity.getSchemaName() : "public"));
|
sanitizeIdentifier(entity.getSchemaName() != null ? entity.getSchemaName() : "public"));
|
||||||
case "clickhouse" -> "SHOW TABLES";
|
case "clickhouse" -> "SHOW TABLES";
|
||||||
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
|
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package vip.mate.datasource.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import vip.mate.datasource.model.DatasourceEntity;
|
||||||
|
import vip.mate.datasource.repository.DatasourceMapper;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class DatasourceServicePasswordPersistenceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("connection test never persists a decrypted datasource password")
|
||||||
|
void testConnectionReencryptsPasswordBeforeUpdate() {
|
||||||
|
// Given a datasource whose password is encrypted at creation time.
|
||||||
|
String plaintext = "reader-password";
|
||||||
|
DatasourceMapper mapper = mock(DatasourceMapper.class);
|
||||||
|
DatasourceConnectionManager connectionManager = mock(DatasourceConnectionManager.class);
|
||||||
|
DatasourceService service = new DatasourceService(mapper, connectionManager);
|
||||||
|
ReflectionTestUtils.setField(service, "encryptKey", "test-datasource-key");
|
||||||
|
|
||||||
|
DatasourceEntity datasource = new DatasourceEntity();
|
||||||
|
datasource.setId(1L);
|
||||||
|
datasource.setName("DUCHA");
|
||||||
|
datasource.setPassword(plaintext);
|
||||||
|
service.create(datasource);
|
||||||
|
assertNotEquals(plaintext, datasource.getPassword());
|
||||||
|
when(mapper.selectById(1L)).thenReturn(datasource);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
assertTrue(plaintext.equals(datasource.getPassword()));
|
||||||
|
return true;
|
||||||
|
}).when(connectionManager).testConnection(datasource);
|
||||||
|
|
||||||
|
// When MateClaw tests the JDBC connection.
|
||||||
|
assertTrue(service.testConnection(1L));
|
||||||
|
|
||||||
|
// Then the entity sent back to MyBatis must be encrypted again.
|
||||||
|
assertNotEquals(plaintext, datasource.getPassword());
|
||||||
|
assertTrue(datasource.getPassword().matches("[0-9a-f]+"));
|
||||||
|
verify(mapper).updateById(datasource);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package vip.mate.tool.builtin;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import vip.mate.datasource.model.DatasourceEntity;
|
||||||
|
import vip.mate.datasource.service.DatasourceConnectionManager;
|
||||||
|
import vip.mate.datasource.service.DatasourceService;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class DatasourceToolPostgresqlViewDiscoveryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("list_tables discovers PostgreSQL views as queryable relations")
|
||||||
|
void listTablesIncludesPostgresqlViews() throws Exception {
|
||||||
|
// Given a PostgreSQL datasource whose allowed schema exposes a view.
|
||||||
|
DatasourceEntity datasource = new DatasourceEntity();
|
||||||
|
datasource.setId(1L);
|
||||||
|
datasource.setDbType("postgresql");
|
||||||
|
datasource.setSchemaName("serving");
|
||||||
|
|
||||||
|
DatasourceService service = mock(DatasourceService.class);
|
||||||
|
DatasourceConnectionManager connectionManager = mock(DatasourceConnectionManager.class);
|
||||||
|
Connection connection = mock(Connection.class);
|
||||||
|
Statement statement = mock(Statement.class);
|
||||||
|
ResultSet resultSet = mock(ResultSet.class);
|
||||||
|
ResultSetMetaData metadata = mock(ResultSetMetaData.class);
|
||||||
|
when(service.getDecrypted(1L)).thenReturn(datasource);
|
||||||
|
when(connectionManager.getConnection(datasource)).thenReturn(connection);
|
||||||
|
when(connection.createStatement()).thenReturn(statement);
|
||||||
|
when(statement.executeQuery(anyString())).thenReturn(resultSet);
|
||||||
|
when(resultSet.getMetaData()).thenReturn(metadata);
|
||||||
|
when(metadata.getColumnCount()).thenReturn(2);
|
||||||
|
when(metadata.getColumnLabel(1)).thenReturn("table_name");
|
||||||
|
when(metadata.getColumnLabel(2)).thenReturn("table_comment");
|
||||||
|
when(resultSet.next()).thenReturn(true, false);
|
||||||
|
when(resultSet.getString(1)).thenReturn("v_plan_enriched");
|
||||||
|
when(resultSet.getString(2)).thenReturn("read-only serving view");
|
||||||
|
|
||||||
|
DatasourceTool tool = new DatasourceTool(service, connectionManager, JsonMapper.builder().build());
|
||||||
|
|
||||||
|
// When the agent asks MateClaw to discover available relations.
|
||||||
|
String output = tool.query_datasource("list_tables", 1L, null);
|
||||||
|
|
||||||
|
// Then the metadata query must include views and return the discovered view.
|
||||||
|
ArgumentCaptor<String> sql = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(statement).executeQuery(sql.capture());
|
||||||
|
assertTrue(sql.getValue().contains("information_schema.tables"));
|
||||||
|
assertFalse(sql.getValue().contains("FROM pg_tables"));
|
||||||
|
assertTrue(output.contains("v_plan_enriched"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user