From c4f4cd86cd259371d9476f3cf47bf9fc591704cc Mon Sep 17 00:00:00 2001 From: Charles Gauthereau Date: Fri, 9 Oct 2026 16:58:12 +0200 Subject: [PATCH] fix(mssql): refuse restore drop when the login cannot recreate the database --- src/domain/mssql/restore.rs | 2 ++ src/tests/domain/mssql.rs | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/domain/mssql/restore.rs b/src/domain/mssql/restore.rs index 9cc084a..21ea161 100644 --- a/src/domain/mssql/restore.rs +++ b/src/domain/mssql/restore.rs @@ -10,6 +10,8 @@ use std::time::Instant; const DROP_DATABASE_SQL: &str = "DECLARE @q nvarchar(258) = QUOTENAME(@P1); IF DB_ID(@P1) IS NOT NULL BEGIN + IF HAS_PERMS_BY_NAME(NULL, 'DATABASE', 'CREATE DATABASE') = 0 + THROW 50000, N'Restore needs CREATE DATABASE permission (dbcreator) to recreate the target database; nothing was dropped', 1; IF CAST(SERVERPROPERTY('EngineEdition') AS int) <> 5 EXEC(N'ALTER DATABASE ' + @q + N' SET SINGLE_USER WITH ROLLBACK IMMEDIATE'); EXEC(N'DROP DATABASE ' + @q); diff --git a/src/tests/domain/mssql.rs b/src/tests/domain/mssql.rs index 61670f5..68db97c 100644 --- a/src/tests/domain/mssql.rs +++ b/src/tests/domain/mssql.rs @@ -332,6 +332,47 @@ async fn mssql_restore_rejects_unknown_extension_test() { ); } +#[tokio::test] +async fn mssql_restore_refuses_without_create_database_test() { + init_tracing_for_test(); + let (_container, host, port) = start().await; + create_user_database(&host, port, "ownerdb").await; + exec(&host, port, "master", "CREATE LOGIN owner_login WITH PASSWORD = 'Owner!Str0ng1'").await; + exec( + &host, + port, + "ownerdb", + "CREATE TABLE dbo.users (id INT PRIMARY KEY, email NVARCHAR(100) NOT NULL); \ + INSERT dbo.users VALUES (1, 'a@x'), (2, 'b@x'), (3, 'c@x'); \ + CREATE USER owner_user FOR LOGIN owner_login; \ + ALTER ROLE db_owner ADD MEMBER owner_user;", + ) + .await; + + let config = make_config(host.clone(), port, "ownerdb", "5a445eb4-c2c6-4bde-a423-ee1385dcf6db"); + let temp_dir = TempDir::new().unwrap(); + let file_path = DatabaseFactory::create_for_backup(config.clone()) + .await + .backup(temp_dir.path(), Arc::new(JobLogger::new())) + .await + .unwrap(); + + let mut owner_config = config; + owner_config.username = "owner_login".to_string(); + owner_config.password = "Owner!Str0ng1".to_string(); + let result = DatabaseFactory::create_for_restore(owner_config, &file_path) + .await + .restore(&file_path, Arc::new(JobLogger::new())) + .await; + + assert!(format!("{:#}", result.unwrap_err()).contains("CREATE DATABASE")); + assert_eq!( + scalar(&host, port, "master", "sa", SA_PASSWORD, "SELECT CASE WHEN DB_ID('ownerdb') IS NULL THEN 0 ELSE 1 END").await, + 1 + ); + assert_eq!(scalar(&host, port, "ownerdb", "sa", SA_PASSWORD, "SELECT COUNT(*) FROM dbo.users").await, 3); +} + mod format_tests { use super::{make_config, with_backup_format}; use crate::domain::mssql::format::MssqlFormat;