mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(plugin): persist plugin connection secrets safely
This commit is contained in:
@@ -15,7 +15,7 @@ use crate::ai::AiConfigItem;
|
||||
use crate::connection_secrets::{
|
||||
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, MQ_AUTH_API_KEY_VALUE_KEY,
|
||||
MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY,
|
||||
NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
|
||||
NACOS_RNACOS_CONSOLE_PASSWORD_KEY, PLUGIN_CONNECTION_SECRET_PREFIX,
|
||||
};
|
||||
use crate::models::connection::{ConnectionConfig, DatabaseType, TransportLayerConfig};
|
||||
use crate::saved_sql::SavedSqlLibrary;
|
||||
@@ -973,6 +973,7 @@ fn scrub_connection_secrets(config: &mut ConnectionConfig) {
|
||||
scrub_mq_external_config_secrets(config);
|
||||
scrub_nacos_auth_secrets(config);
|
||||
scrub_cassandra_tls_secrets(config);
|
||||
config.connection_secrets.clear();
|
||||
}
|
||||
|
||||
fn scrub_mqtt_auth_secrets(config: &mut ConnectionConfig) {
|
||||
@@ -1008,6 +1009,14 @@ async fn build_sensitive_payload(
|
||||
if config.save_password {
|
||||
push_secret(&mut connection_secrets, &config.id, "password", &config.password);
|
||||
}
|
||||
for (key, secret) in &config.connection_secrets {
|
||||
push_secret(
|
||||
&mut connection_secrets,
|
||||
&config.id,
|
||||
&format!("{PLUGIN_CONNECTION_SECRET_PREFIX}{key}"),
|
||||
secret,
|
||||
);
|
||||
}
|
||||
push_secret(&mut connection_secrets, &config.id, "init_script", config.init_script.as_deref().unwrap_or(""));
|
||||
for (index, layer) in config.transport_layers.iter().enumerate() {
|
||||
match layer {
|
||||
@@ -1225,6 +1234,7 @@ async fn apply_sensitive_payload(
|
||||
if !SECRET_KEYS.contains(&secret.key.as_str())
|
||||
&& !secret.key.starts_with(SSH_TUNNEL_SECRET_PREFIX)
|
||||
&& !secret.key.starts_with(TRANSPORT_LAYER_SECRET_PREFIX)
|
||||
&& !secret.key.starts_with(PLUGIN_CONNECTION_SECRET_PREFIX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1264,6 +1274,7 @@ async fn clear_connection_secrets(storage: &Storage, connections: &[ConnectionCo
|
||||
for key in SECRET_KEYS {
|
||||
storage.delete_secret(&config.id, key).await?;
|
||||
}
|
||||
storage.delete_secret_prefix(&config.id, PLUGIN_CONNECTION_SECRET_PREFIX).await?;
|
||||
for (index, layer) in config.transport_layers.iter().enumerate() {
|
||||
match layer {
|
||||
TransportLayerConfig::Ssh(_) => {
|
||||
@@ -1654,7 +1665,7 @@ mod tests {
|
||||
use crate::ai::{AiApiStyle, AiAuthMethod, AiConfig, AiConfigItem};
|
||||
use crate::connection_secrets::{
|
||||
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, NACOS_AUTH_PASSWORD_KEY,
|
||||
NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
|
||||
NACOS_RNACOS_CONSOLE_PASSWORD_KEY, PLUGIN_CONNECTION_SECRET_PREFIX,
|
||||
};
|
||||
use crate::models::connection::{
|
||||
default_redis_key_separator, ConnectionConfig, DatabaseType, SshTunnelConfig, TransportLayerConfig,
|
||||
@@ -1847,6 +1858,10 @@ mod tests {
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
@@ -1934,6 +1949,10 @@ mod tests {
|
||||
"password": password
|
||||
}
|
||||
})),
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
@@ -2187,6 +2206,10 @@ mod tests {
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
@@ -2649,6 +2672,28 @@ mod tests {
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_connection_secrets_are_scrubbed_from_public_sync_metadata() {
|
||||
let storage = Storage::open(&temp_db_path("plugin-public-sync")).await.unwrap();
|
||||
let mut config = postgres_connection("plugin", "");
|
||||
config.db_type = DatabaseType::Plugin;
|
||||
config.plugin_id = Some("example.plugin".to_string());
|
||||
config.plugin_connection_provider = Some("example.connection".to_string());
|
||||
config.connection_secrets.insert("api_token".to_string(), "plugin-secret".to_string());
|
||||
storage.save_connections(std::slice::from_ref(&config)).await.unwrap();
|
||||
|
||||
let snapshot = build_sync_snapshot(&storage, "test-version", None, Some("sync-pass")).await.unwrap();
|
||||
let public_json = serde_json::to_string(&snapshot.connections).unwrap();
|
||||
assert!(!public_json.contains("plugin-secret"));
|
||||
let encrypted = snapshot.encrypted_secrets.as_ref().expect("encrypted secrets");
|
||||
let decrypted = decrypt_sensitive_payload(encrypted, "sync-pass").unwrap();
|
||||
assert!(decrypted.connection_secrets.iter().any(|secret| {
|
||||
secret.connection_id == "plugin"
|
||||
&& secret.key == format!("{PLUGIN_CONNECTION_SECRET_PREFIX}api_token")
|
||||
&& secret.secret == "plugin-secret"
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_restore_does_not_revive_password_when_connection_disables_saving() {
|
||||
let source = Storage::open(&temp_db_path("sync-no-save-password-source")).await.unwrap();
|
||||
|
||||
@@ -3835,10 +3835,10 @@ impl AppState {
|
||||
PoolKind::Sqlite(_)
|
||||
| PoolKind::DuckDbWorker(_)
|
||||
| PoolKind::ExternalDriver { .. }
|
||||
| PoolKind::PluginConnection(_)
|
||||
| PoolKind::MessageQueue
|
||||
| PoolKind::Nacos
|
||||
| PoolKind::Consul(_) => false,
|
||||
PoolKind::PluginConnection(handle) => !handle.is_running(),
|
||||
#[cfg(feature = "mq-admin")]
|
||||
PoolKind::Mqtt(_) => false,
|
||||
}
|
||||
@@ -4870,10 +4870,10 @@ impl AppState {
|
||||
PoolKind::Sqlite(_)
|
||||
| PoolKind::DuckDbWorker(_)
|
||||
| PoolKind::ExternalDriver { .. }
|
||||
| PoolKind::PluginConnection(_)
|
||||
| PoolKind::MessageQueue
|
||||
| PoolKind::Nacos
|
||||
| PoolKind::Consul(_) => true,
|
||||
PoolKind::PluginConnection(handle) => handle.is_running(),
|
||||
#[cfg(feature = "mq-admin")]
|
||||
PoolKind::Mqtt(_) => true,
|
||||
PoolKind::Redis(redis) => match db::redis_driver::test_connection(redis).await {
|
||||
@@ -6164,6 +6164,10 @@ mod tests {
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
|
||||
@@ -27,6 +27,7 @@ pub const MQTT_AUTH_PASSWORD_KEY: &str = "mqtt.auth.password";
|
||||
pub const CASSANDRA_TLS_SECRET_PREFIX: &str = "cassandra.tls.";
|
||||
pub const CASSANDRA_TRUSTSTORE_PASSWORD_KEY: &str = "cassandra.tls.truststore_password";
|
||||
pub const CASSANDRA_KEYSTORE_PASSWORD_KEY: &str = "cassandra.tls.keystore_password";
|
||||
pub const PLUGIN_CONNECTION_SECRET_PREFIX: &str = "plugin_connection.";
|
||||
|
||||
pub trait ConnectionSecretStore {
|
||||
fn set_secret(&self, connection_id: &str, key: &str, secret: &str) -> Result<(), String>;
|
||||
@@ -947,6 +948,10 @@ mod tests {
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
|
||||
@@ -843,6 +843,7 @@ fn open_install_lock(root_dir: &Path) -> Result<File, String> {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(root_dir.join(INSTALL_LOCK_FILE))
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ impl PluginRepositoryStore {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(self.root_dir.join(REPOSITORIES_LOCK_FILE))
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
@@ -6480,7 +6480,8 @@ mod tests {
|
||||
use crate::models::connection::{default_redis_key_separator, ConnectionConfig, DatabaseType};
|
||||
#[cfg(unix)]
|
||||
use crate::plugins::{
|
||||
InstalledPlugin, PluginDriverManifest, PluginDriverSession, PluginManifest, PluginRuntimeEnv,
|
||||
InstalledPlugin, PluginCompatibility, PluginDriverManifest, PluginDriverSession, PluginManifest,
|
||||
PluginRuntimeEnv,
|
||||
};
|
||||
use crate::storage::Storage;
|
||||
|
||||
@@ -6807,6 +6808,10 @@ for line in sys.stdin:
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
@@ -8598,8 +8603,10 @@ for line in sys.stdin:
|
||||
kind: "external".to_string(),
|
||||
database_type: Some("jdbc".to_string()),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
path: dir.clone(),
|
||||
compatibility: PluginCompatibility { compatible: true, ..Default::default() },
|
||||
};
|
||||
let session = PluginDriverSession::start_for_test(plugin, "jdbc".to_string(), PluginRuntimeEnv::default())
|
||||
.await
|
||||
@@ -8698,8 +8705,10 @@ for line in sys.stdin:
|
||||
kind: "external".to_string(),
|
||||
database_type: Some("jdbc".to_string()),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
path: dir.clone(),
|
||||
compatibility: PluginCompatibility { compatible: true, ..Default::default() },
|
||||
};
|
||||
let session = Arc::new(
|
||||
PluginDriverSession::start_for_test(plugin, "jdbc".to_string(), PluginRuntimeEnv::default())
|
||||
@@ -8808,8 +8817,10 @@ for line in sys.stdin:
|
||||
kind: "external".to_string(),
|
||||
database_type: Some("jdbc".to_string()),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
path: dir.clone(),
|
||||
compatibility: PluginCompatibility { compatible: true, ..Default::default() },
|
||||
};
|
||||
let session = PluginDriverSession::start_for_test(plugin, "jdbc".to_string(), PluginRuntimeEnv::default())
|
||||
.await
|
||||
@@ -8866,8 +8877,10 @@ for line in sys.stdin:
|
||||
kind: "external".to_string(),
|
||||
database_type: Some("jdbc".to_string()),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
path: dir.clone(),
|
||||
compatibility: PluginCompatibility { compatible: true, ..Default::default() },
|
||||
};
|
||||
let session = PluginDriverSession::start_for_test(plugin, "jdbc".to_string(), PluginRuntimeEnv::default())
|
||||
.await
|
||||
@@ -9327,6 +9340,10 @@ for line in sys.stdin:
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
|
||||
@@ -3640,8 +3640,10 @@ done
|
||||
kind: "external".to_string(),
|
||||
database_type: Some("jdbc".to_string()),
|
||||
}],
|
||||
..PluginManifest::default()
|
||||
},
|
||||
path: dir.clone(),
|
||||
compatibility: crate::plugins::PluginCompatibility { compatible: true, ..Default::default() },
|
||||
};
|
||||
let session = std::sync::Arc::new(
|
||||
PluginDriverSession::start_for_test(plugin, "jdbc".to_string(), PluginRuntimeEnv::default()).await.unwrap(),
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::connection_secrets::{
|
||||
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TLS_SECRET_PREFIX, CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
|
||||
MQ_AUTH_API_KEY_VALUE_KEY, MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_SECRET_PREFIX,
|
||||
MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, MQ_TOKEN_SIGNING_SECRET_PREFIX, NACOS_AUTH_PASSWORD_KEY,
|
||||
NACOS_AUTH_SECRET_PREFIX, NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
|
||||
NACOS_AUTH_SECRET_PREFIX, NACOS_RNACOS_CONSOLE_PASSWORD_KEY, PLUGIN_CONNECTION_SECRET_PREFIX,
|
||||
};
|
||||
use crate::db::sqlite::{connect_path_create_if_missing, SqliteHandle};
|
||||
use crate::history::{
|
||||
@@ -3203,6 +3203,7 @@ fn sanitized_connection_config(config: &ConnectionConfig) -> ConnectionConfig {
|
||||
scrub_mq_token_signing_secret(&mut sanitized);
|
||||
scrub_nacos_auth_secrets(&mut sanitized);
|
||||
scrub_cassandra_tls_secrets(&mut sanitized);
|
||||
sanitized.connection_secrets.clear();
|
||||
sanitized
|
||||
}
|
||||
|
||||
@@ -3280,9 +3281,41 @@ fn persist_connection_in_tx(tx: &rusqlite::Transaction<'_>, config: &ConnectionC
|
||||
persist_mq_token_signing_secret_in_tx(tx, &config)?;
|
||||
persist_nacos_auth_secrets_in_tx(tx, &config)?;
|
||||
persist_cassandra_tls_secrets_in_tx(tx, &config)?;
|
||||
delete_secret_prefix_in_tx(tx, &config.id, PLUGIN_CONNECTION_SECRET_PREFIX)?;
|
||||
for (key, secret) in &config.connection_secrets {
|
||||
if !key.is_empty() {
|
||||
persist_secret_in_tx(tx, &config.id, &format!("{PLUGIN_CONNECTION_SECRET_PREFIX}{key}"), secret)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_plugin_connection_secrets(
|
||||
storage: &Storage,
|
||||
connection_id: &str,
|
||||
) -> Result<HashMap<String, String>, String> {
|
||||
let connection_id = connection_id.to_string();
|
||||
storage
|
||||
.with_conn(move |conn| {
|
||||
let like = format!("{PLUGIN_CONNECTION_SECRET_PREFIX}%");
|
||||
let mut statement = conn
|
||||
.prepare("SELECT key, secret FROM connection_secrets WHERE connection_id = ?1 AND key LIKE ?2")
|
||||
.map_err(|error| error.to_string())?;
|
||||
let rows = statement
|
||||
.query_map(params![connection_id, like], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
|
||||
.map_err(|error| error.to_string())?;
|
||||
let mut secrets = HashMap::new();
|
||||
for row in rows {
|
||||
let (key, secret) = row.map_err(|error| error.to_string())?;
|
||||
if let Some(key) = key.strip_prefix(PLUGIN_CONNECTION_SECRET_PREFIX) {
|
||||
secrets.insert(key.to_string(), secret);
|
||||
}
|
||||
}
|
||||
Ok(secrets)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn insert_connection_copy_next_to_source(entries: &mut Vec<serde_json::Value>, source_id: &str, copy_id: &str) -> bool {
|
||||
let mut index = 0;
|
||||
while index < entries.len() {
|
||||
@@ -3416,6 +3449,7 @@ impl Storage {
|
||||
scrub_mq_token_signing_secret(&mut sanitized);
|
||||
scrub_nacos_auth_secrets(&mut sanitized);
|
||||
scrub_cassandra_tls_secrets(&mut sanitized);
|
||||
sanitized.connection_secrets.clear();
|
||||
let json = serde_json::to_string(&sanitized).map_err(|e| e.to_string())?;
|
||||
|
||||
tx.execute("INSERT INTO connections (id, config_json) VALUES (?1, ?2)", params![config_id, json])
|
||||
@@ -3734,6 +3768,10 @@ impl Storage {
|
||||
config.redis_sentinel_password = self.get_secret(&id, "redis_sentinel_password").await?.unwrap_or_default();
|
||||
config.connection_string = self.get_secret(&id, "connection_string").await?;
|
||||
config.init_script = self.get_secret(&id, "init_script").await?;
|
||||
let stored_plugin_secrets = load_plugin_connection_secrets(self, &id).await?;
|
||||
if !stored_plugin_secrets.is_empty() {
|
||||
config.connection_secrets = stored_plugin_secrets;
|
||||
}
|
||||
let needs_mq_auth_rewrite = self.hydrate_mq_auth_secrets(&id, &mut config).await?;
|
||||
let needs_mq_token_signing_rewrite = self.hydrate_mq_token_signing_secret(&id, &mut config).await?;
|
||||
let needs_nacos_auth_rewrite = self.hydrate_nacos_auth_secret(&id, &mut config).await?;
|
||||
@@ -4277,6 +4315,21 @@ impl Storage {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_secret_prefix(&self, connection_id: &str, key_prefix: &str) -> Result<(), String> {
|
||||
let connection_id = connection_id.to_string();
|
||||
let key_prefix = key_prefix.to_string();
|
||||
self.with_conn(move |conn| {
|
||||
let like = format!("{key_prefix}%");
|
||||
conn.execute(
|
||||
"DELETE FROM connection_secrets WHERE connection_id = ?1 AND key LIKE ?2",
|
||||
params![connection_id, like],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// MQ token records
|
||||
@@ -5301,7 +5354,7 @@ mod tests {
|
||||
use crate::connection_secrets::NACOS_RNACOS_CONSOLE_PASSWORD_KEY;
|
||||
use crate::connection_secrets::{
|
||||
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY,
|
||||
MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY,
|
||||
MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY, PLUGIN_CONNECTION_SECRET_PREFIX,
|
||||
};
|
||||
use crate::history::{HistoryConnectionFilter, HistoryDatabaseFilter, HistoryEntry, HistorySearchRequest};
|
||||
use crate::models::connection::{
|
||||
@@ -6030,6 +6083,41 @@ mod tests {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_connections_moves_plugin_secrets_to_secret_table_and_restores_them() {
|
||||
let path = temp_db_path("plugin-secrets");
|
||||
let storage = Storage::open(&path).await.unwrap();
|
||||
let mut config = plain_connection("plugin", "");
|
||||
config.db_type = DatabaseType::Plugin;
|
||||
config.plugin_id = Some("example.plugin".to_string());
|
||||
config.plugin_connection_provider = Some("example.connection".to_string());
|
||||
config.connection_secrets.insert("api_token".to_string(), "plugin-secret".to_string());
|
||||
|
||||
storage.save_connections(std::slice::from_ref(&config)).await.unwrap();
|
||||
|
||||
let raw_json = raw_connection_json(&storage, &config.id).await;
|
||||
assert!(!raw_json.contains("plugin-secret"));
|
||||
assert_eq!(
|
||||
storage
|
||||
.get_secret(&config.id, &format!("{PLUGIN_CONNECTION_SECRET_PREFIX}api_token"))
|
||||
.await
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some("plugin-secret")
|
||||
);
|
||||
assert_eq!(storage.load_connections().await.unwrap()[0].connection_secrets, config.connection_secrets);
|
||||
|
||||
let mut cleared = config;
|
||||
cleared.connection_secrets.clear();
|
||||
storage.save_connections(std::slice::from_ref(&cleared)).await.unwrap();
|
||||
assert_eq!(
|
||||
storage.get_secret(&cleared.id, &format!("{PLUGIN_CONNECTION_SECRET_PREFIX}api_token")).await.unwrap(),
|
||||
None
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn switching_save_password_off_removes_stored_password() {
|
||||
let path = temp_db_path("save-password-switch-off");
|
||||
@@ -6108,6 +6196,10 @@ mod tests {
|
||||
"token": token
|
||||
}
|
||||
})),
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
@@ -6179,6 +6271,10 @@ mod tests {
|
||||
"password": password
|
||||
}
|
||||
})),
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
|
||||
@@ -60,6 +60,10 @@ fn live_sqlserver_config(id: &str, database: &str) -> ConnectionConfig {
|
||||
gbase_server: String::new(),
|
||||
informix_server: String::new(),
|
||||
external_config: None,
|
||||
plugin_id: None,
|
||||
plugin_connection_provider: None,
|
||||
plugin_connection_type: None,
|
||||
connection_secrets: Default::default(),
|
||||
jdbc_driver_class: None,
|
||||
jdbc_driver_paths: Vec::new(),
|
||||
one_time: false,
|
||||
|
||||
Reference in New Issue
Block a user