From c83bfd9dfc7dcbb23bc3990f9d27ed454b556c86 Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Wed, 13 May 2026 09:55:37 +0800
Subject: [PATCH 01/13] Add support for Entra-only SQL Server admin creation
Introduces a method to enable Microsoft Entra-only authentication when creating SQL Servers, addressing scenarios where policies require disabling SQL authentication. Updates tests and utilities to use Entra admin exclusively, improving compliance with platform security requirements.
---
.../azure-resourcemanager-sql/CHANGELOG.md | 2 +
.../sql/implementation/SqlServerImpl.java | 13 ++
.../resourcemanager/sql/models/SqlServer.java | 14 ++
.../sql/SqlServerOperationsTests.java | 133 ++++++++----------
.../resourcemanager/sql/SqlServerTest.java | 42 ++++++
5 files changed, 127 insertions(+), 77 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
index 91d6218b963c..3547a90eb80e 100644
--- a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
+++ b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
@@ -4,6 +4,8 @@
### Features Added
+- Added a new method `withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid)` on `SqlServer.DefinitionStages.WithAdministratorLogin` to create a SQL Server with Microsoft Entra-only authentication enabled at creation time. This is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.
+
### Breaking Changes
### Bugs Fixed
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index dd6a86864e17..989fa2987282 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -22,6 +22,7 @@
import com.azure.resourcemanager.sql.models.AdministratorType;
import com.azure.resourcemanager.sql.models.IdentityType;
import com.azure.resourcemanager.sql.models.ResourceIdentity;
+import com.azure.resourcemanager.sql.models.ServerExternalAdministrator;
import com.azure.resourcemanager.sql.models.ServerMetric;
import com.azure.resourcemanager.sql.models.SqlDatabaseOperations;
import com.azure.resourcemanager.sql.models.SqlElasticPoolOperations;
@@ -358,6 +359,18 @@ public SqlServerImpl withAdministratorPassword(String administratorLoginPassword
return this;
}
+ @Override
+ public SqlServerImpl withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid) {
+ this.innerModel()
+ .withAdministrators(
+ new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY)
+ .withLogin(userLogin)
+ .withSid(UUID.fromString(sid))
+ .withTenantId(UUID.fromString(this.manager().tenantId()))
+ .withAzureADOnlyAuthentication(true));
+ return this;
+ }
+
@Override
public SqlServerImpl withoutAccessFromAzureServices() {
allowAzureServicesAccess = false;
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index 64d5188526ac..28b6149326be 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -259,6 +259,20 @@ interface WithAdministratorLogin {
* @return Next stage of the SQL Server definition
*/
WithAdministratorPassword withAdministratorLogin(String administratorLogin);
+
+ /**
+ * Configures the SQL Server to use Microsoft Entra (Azure Active Directory) only authentication, with the
+ * specified user or group as the Active Directory administrator. The SQL authentication
+ * (login/password) administrator is not configured.
+ *
+ * This method should be used when the target subscription/management group enforces a policy that
+ * requires Microsoft Entra-only authentication on Azure SQL Server creation.
+ *
+ * @param userLogin the user or group login; it can be the name or the email address
+ * @param sid the user or group object ID
+ * @return Next stage of the SQL Server definition
+ */
+ WithCreate withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid);
}
/** A SQL Server definition setting admin user password. */
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index e962324290bf..bf238f0d3f00 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -104,8 +104,7 @@ public void canCRUDSqlSyncMember() throws Exception {
.define(sqlServerName)
.withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -172,10 +171,9 @@ public void canCRUDSqlSyncGroup() throws Exception {
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_EAST2)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -204,7 +202,7 @@ public void canCRUDSqlSyncGroup() throws Exception {
Assertions.assertTrue(sqlServerManager.sqlServers()
.syncGroups()
- .listSyncDatabaseIds(Region.US_EAST)
+ .listSyncDatabaseIds(Region.US_EAST2)
.stream()
.findAny()
.isPresent());
@@ -224,16 +222,13 @@ public void canCopySqlDatabase() throws Exception {
final String sqlSecondaryServerName = generateRandomResourceName("sqlsec", 22);
final String epName = "epSample";
final String dbName = "dbSample";
- final String administratorLogin = "sqladmin";
- final String administratorPassword = password();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlPrimaryServerName)
.withRegion(Region.US_EAST2)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.defineElasticPool(epName)
.withPremiumPool()
.attach()
@@ -245,10 +240,9 @@ public void canCopySqlDatabase() throws Exception {
SqlServer sqlSecondaryServer = sqlServerManager.sqlServers()
.define(sqlSecondaryServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withExistingResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
SqlDatabase dbSample = sqlPrimaryServer.databases().get(dbName);
@@ -271,16 +265,13 @@ public void canCRUDSqlFailoverGroup() throws Exception {
final String failoverGroupName = generateRandomResourceName("fg", 22);
final String failoverGroupName2 = generateRandomResourceName("fg2", 22);
final String dbName = "dbSample";
- final String administratorLogin = "sqladmin";
- final String administratorPassword = password();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlPrimaryServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -291,16 +282,14 @@ public void canCRUDSqlFailoverGroup() throws Exception {
.define(sqlSecondaryServerName)
.withRegion(Region.US_EAST2)
.withExistingResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
SqlServer sqlOtherServer = sqlServerManager.sqlServers()
.define(sqlOtherServerName)
- .withRegion(Region.US_SOUTH_CENTRAL)
+ .withRegion(Region.EUROPE_NORTH)
.withExistingResourceGroup(rgName)
- .withAdministratorLogin(administratorLogin)
- .withAdministratorPassword(administratorPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
SqlFailoverGroup failoverGroup = sqlPrimaryServer.failoverGroups()
@@ -391,8 +380,6 @@ public void canCRUDSqlFailoverGroup() throws Exception {
@Test
public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception {
- String sqlServerAdminName = "sqladmin";
- String sqlServerAdminPassword = password();
String databaseName = "db-from-sample";
String id = generateRandomUuid();
String storageName = generateRandomResourceName(sqlServerName, 22);
@@ -400,10 +387,9 @@ public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception {
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(sqlServerAdminPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withBasicEdition()
@@ -469,16 +455,13 @@ public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception {
public void canCreateAndAquireServerDnsAlias() throws Exception {
String sqlServerName1 = sqlServerName + "1";
String sqlServerName2 = sqlServerName + "2";
- String sqlServerAdminName = "sqladmin";
- String sqlServerAdminPassword = password();
// Create
SqlServer sqlServer1 = sqlServerManager.sqlServers()
.define(sqlServerName1)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(sqlServerAdminPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
Assertions.assertNotNull(sqlServer1);
@@ -497,10 +480,9 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
SqlServer sqlServer2 = sqlServerManager.sqlServers()
.define(sqlServerName2)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(sqlServerAdminPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
Assertions.assertNotNull(sqlServer2);
@@ -523,11 +505,9 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
@DoNotRecord(skipInPlayback = true)
public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
// LiveOnly because "test timing out after latest test proxy update"
- String sqlServerAdminName = "sqladmin";
- String sqlServerAdminPassword = password();
String databaseName = "db-from-sample";
- RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(Region.US_EAST);
+ RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(Region.US_WEST3);
Assertions.assertNotNull(regionCapabilities);
Assertions.assertNotNull(regionCapabilities.supportedCapabilitiesByServerVersion().get("12.0"));
Assertions.assertTrue(
@@ -539,10 +519,9 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(sqlServerAdminPassword)
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.withSystemAssignedManagedServiceIdentity()
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
@@ -575,6 +554,7 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
@Test
@DoNotRecord(skipInPlayback = true)
+ @Disabled("Not supported in AAD-only tenants: SQL auth and ADPassword (ROPC) are blocked.")
// The test makes calls to the Azure Storage data plane APIs which are not mocked at this time.
public void canCRUDSqlServerWithImportDatabase() throws Exception {
// Create
@@ -648,28 +628,23 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
@Test
public void canCRUDSqlServerWithFirewallRule() throws Exception {
// Create
- String sqlServerAdminName = "sqladmin";
- String id = azureCliSignedInUser().id();
+ String id = adminSid();
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(password())
- .withActiveDirectoryAdministrator("DSEng", id)
+ .withAdministratorAzureActiveDirectoryOnly("DSEng", id)
.withoutAccessFromAzureServices()
.defineFirewallRule("somefirewallrule1")
.withIpAddress("0.0.0.1")
.attach()
.withTag("tag1", "value1")
.create();
- Assertions.assertEquals(sqlServerAdminName, sqlServer.administratorLogin());
Assertions.assertEquals("v12.0", sqlServer.kind());
Assertions.assertEquals("12.0", sqlServer.version());
sqlServer = sqlServerManager.sqlServers().getByResourceGroup(rgName, sqlServerName);
- Assertions.assertEquals(sqlServerAdminName, sqlServer.administratorLogin());
Assertions.assertEquals("v12.0", sqlServer.kind());
Assertions.assertEquals("12.0", sqlServer.version());
@@ -684,10 +659,11 @@ public void canCRUDSqlServerWithFirewallRule() throws Exception {
Assertions.assertEquals("DSEngAll", sqlADAdmin.signInName());
Assertions.assertNotNull(sqlADAdmin.id());
Assertions.assertEquals(AdministratorType.ACTIVE_DIRECTORY, sqlADAdmin.administratorType());
- sqlServer.removeActiveDirectoryAdministrator();
- final SqlServer finalSqlServer = sqlServer;
- validateResourceNotFound(() -> finalSqlServer.getActiveDirectoryAdministrator());
+ // Cannot remove AD admin when AzureADOnlyAuthentication is enabled.
+ // Verify it is still present instead.
+ sqlADAdmin = sqlServer.getActiveDirectoryAdministrator();
+ Assertions.assertNotNull(sqlADAdmin);
SqlFirewallRule firewallRule
= sqlServerManager.sqlServers().firewallRules().getBySqlServer(rgName, sqlServerName, "somefirewallrule1");
@@ -760,7 +736,11 @@ public void canCRUDSqlServer() throws Exception {
Assertions.assertEquals(CheckNameAvailabilityReason.ALREADY_EXISTS.toString(),
checkNameResult.unavailabilityReason());
- sqlServer.update().withAdministratorPassword("P@ssword~2").apply();
+ // Server is created with Microsoft Entra-only authentication, so SQL admin password update is not applicable.
+ // Exercise an update path using system-assigned managed identity instead.
+ sqlServer.update().withSystemAssignedManagedServiceIdentity().apply();
+ Assertions.assertTrue(sqlServer.isManagedServiceIdentityEnabled());
+ Assertions.assertNotNull(sqlServer.systemAssignedManagedServiceIdentityPrincipalId());
// List
PagedIterable sqlServers = sqlServerManager.sqlServers().listByResourceGroup(rgName);
@@ -791,10 +771,9 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin("userName")
- .withAdministratorPassword("Password~1")
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.withoutAccessFromAzureServices()
.defineDatabase(SQL_DATABASE_NAME)
.attach()
@@ -913,6 +892,7 @@ public void canCRUDSqlDatabase() throws Exception {
.define(storageAccountName)
.withRegion(Region.US_EAST)
.withExistingResourceGroup(rgName)
+ .disableSharedKeyAccess()
.create();
String accountKey = storageAccount.getKeys().get(0).value();
String blobEntrypoint = storageAccount.endPoints().primary().blob();
@@ -1050,7 +1030,9 @@ public void canManageReplicationLinks() throws Exception {
replicationLinksInDb1.get(0).forceFailoverAllowDataLoss();
replicationLinksInDb1.get(0).refresh();
- ResourceManagerUtils.sleep(Duration.ofSeconds(30));
+ // Wait for the link to leave SUSPENDED state after forceFailoverAllowDataLoss before delete;
+ // otherwise delete() fails with 409 GeoReplicationCannotBecomePrimaryDuringUndo.
+ ResourceManagerUtils.sleep(Duration.ofMinutes(30));
replicationLinksInDb2.get(0).delete();
Assertions.assertEquals(databaseInServer2.listReplicationLinks().size(), 0);
@@ -1449,10 +1431,9 @@ private SqlServer createSqlServer() {
private SqlServer createSqlServer(String sqlServerName) {
return sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin("userName")
- .withAdministratorPassword("P@ssword~1")
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
}
@@ -1474,7 +1455,7 @@ private void validateSqlFirewallRule(SqlFirewallRule sqlFirewallRule, String fir
Assertions.assertEquals(END_IPADDRESS, sqlFirewallRule.endIpAddress());
Assertions.assertEquals(rgName, sqlFirewallRule.resourceGroupName());
Assertions.assertEquals(sqlServerName, sqlFirewallRule.sqlServerName());
- Assertions.assertEquals(Region.US_EAST, sqlFirewallRule.region());
+ Assertions.assertEquals(Region.US_WEST3, sqlFirewallRule.region());
}
private static void validateListSqlElasticPool(List sqlElasticPools) {
@@ -1517,7 +1498,6 @@ private void validateSqlServer(SqlServer sqlServer) {
Assertions.assertEquals(rgName, sqlServer.resourceGroupName());
Assertions.assertNotNull(sqlServer.fullyQualifiedDomainName());
// Assertions.assertEquals(ServerVersion.ONE_TWO_FULL_STOP_ZERO, sqlServer.version());
- Assertions.assertEquals("userName", sqlServer.administratorLogin());
}
private void validateSqlDatabase(SqlDatabase sqlDatabase, String databaseName) {
@@ -1537,46 +1517,46 @@ private void validateSqlDatabaseWithElasticPool(SqlDatabase sqlDatabase, String
@DoNotRecord(skipInPlayback = true)
public void testRandomSku() {
// LiveOnly because "test timing out after latest test proxy update"
- // "M" series is not supported in this region
+ // "M" series is not supported in this region; "Fsv2" series has been deprecated
+ List excludedFamilies = Arrays.asList("M", "Fsv2");
List capabilityStatusList
= Arrays.asList(CapabilityStatus.AVAILABLE, CapabilityStatus.DEFAULT);
List databaseSkus = DatabaseSku.getAll()
.stream()
- .filter(sku -> !"M".equals(sku.toSku().family()))
+ .filter(sku -> !excludedFamilies.contains(sku.toSku().family()))
.collect(Collectors.toCollection(LinkedList::new));
Collections.shuffle(databaseSkus);
List elasticPoolSkus = ElasticPoolSku.getAll()
.stream()
- .filter(sku -> !"M".equals(sku.toSku().family()))
+ .filter(sku -> !excludedFamilies.contains(sku.toSku().family()))
.collect(Collectors.toCollection(LinkedList::new));
Collections.shuffle(elasticPoolSkus);
sqlServerManager.sqlServers()
- .getCapabilitiesByRegion(Region.US_EAST)
+ .getCapabilitiesByRegion(Region.US_WEST3)
.supportedCapabilitiesByServerVersion()
.forEach((x, serverVersionCapability) -> {
serverVersionCapability.supportedEditions().forEach(edition -> {
edition.supportedServiceLevelObjectives()
.stream()
.filter(serviceObjective -> !capabilityStatusList.contains(serviceObjective.status())
- || "M".equals(serviceObjective.sku().family()))
+ || excludedFamilies.contains(serviceObjective.sku().family()))
.forEach(serviceObjective -> databaseSkus.remove(DatabaseSku.fromSku(serviceObjective.sku())));
});
serverVersionCapability.supportedElasticPoolEditions().forEach(edition -> {
edition.supportedElasticPoolPerformanceLevels()
.stream()
.filter(performance -> !capabilityStatusList.contains(performance.status())
- || "M".equals(performance.sku().family()))
+ || excludedFamilies.contains(performance.sku().family()))
.forEach(performance -> elasticPoolSkus.remove(ElasticPoolSku.fromSku(performance.sku())));
});
});
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin("userName")
- .withAdministratorPassword(password())
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.create();
// Too many elastic pools defined will hit sql server DTU quota limits.
@@ -1603,7 +1583,7 @@ public void generateSku() throws IOException {
StringBuilder databaseSkuBuilder = new StringBuilder();
StringBuilder elasticPoolSkuBuilder = new StringBuilder();
sqlServerManager.sqlServers()
- .getCapabilitiesByRegion(Region.US_EAST)
+ .getCapabilitiesByRegion(Region.US_WEST3)
.supportedCapabilitiesByServerVersion()
.forEach((x, serverVersionCapability) -> {
serverVersionCapability.supportedEditions().forEach(edition -> {
@@ -1640,7 +1620,7 @@ public void generateSku() throws IOException {
Files.write(new File("src/main/java/com/azure/resourcemanager/sql/models/ElasticPoolSku.java").toPath(),
elasticPoolSku.getBytes(StandardCharsets.UTF_8));
- sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(Region.US_EAST).create(); // for deletion
+ sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(Region.US_WEST3).create(); // for deletion
}
private byte[] readAllBytes(InputStream inputStream) throws IOException {
@@ -1691,10 +1671,9 @@ public void canCreateAndUpdatePublicNetworkAccess() {
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
+ .withRegion(Region.US_WEST3)
.withNewResourceGroup(rgName)
- .withAdministratorLogin("userName")
- .withAdministratorPassword("P@ssword~1")
+ .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
.withoutAccessFromAzureServices()
.disablePublicNetworkAccess()
.create();
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
index d0e69fa5a50b..e493ebaab66a 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
@@ -15,6 +15,7 @@
import com.azure.resourcemanager.resources.ResourceManager;
import com.azure.resourcemanager.storage.StorageManager;
import com.azure.resourcemanager.test.ResourceManagerTestProxyTestBase;
+import com.azure.resourcemanager.test.model.AzureUser;
import com.azure.resourcemanager.test.utils.TestDelayProvider;
import com.azure.resourcemanager.test.utils.TestIdentifierProvider;
@@ -28,6 +29,8 @@ public abstract class SqlServerTest extends ResourceManagerTestProxyTestBase {
protected StorageManager storageManager;
protected String rgName = "";
protected String sqlServerName = "";
+ private volatile String cachedAdminLogin;
+ private volatile String cachedAdminSid;
@Override
protected HttpPipeline buildHttpPipeline(TokenCredential credential, AzureProfile profile,
@@ -54,4 +57,43 @@ protected void cleanUpResources() {
ResourceManagerUtils.sleep(Duration.ofSeconds(1));
resourceManager.resourceGroups().beginDeleteByName(rgName);
}
+
+ /**
+ * Returns the signed-in user's principal name to use as the SQL Server Microsoft Entra administrator login.
+ * The Azure SQL platform policy requires Microsoft Entra-only authentication enabled at server creation time.
+ *
+ * @return the signed-in user's principal name (sanitized in playback)
+ */
+ protected String adminLogin() {
+ if (isPlaybackMode()) {
+ return "REDACTED";
+ }
+ ensureAdminIdentityCached();
+ return cachedAdminLogin != null ? cachedAdminLogin : "REDACTED";
+ }
+
+ /**
+ * Returns the signed-in user's object ID to use as the SQL Server Microsoft Entra administrator SID.
+ *
+ * @return the signed-in user's object ID (sanitized in playback)
+ */
+ protected String adminSid() {
+ if (isPlaybackMode()) {
+ return "00000000-0000-0000-0000-000000000000";
+ }
+ ensureAdminIdentityCached();
+ return cachedAdminSid != null ? cachedAdminSid : "00000000-0000-0000-0000-000000000000";
+ }
+
+ private synchronized void ensureAdminIdentityCached() {
+ if (cachedAdminLogin != null && cachedAdminSid != null) {
+ return;
+ }
+
+ AzureUser user = azureCliSignedInUser();
+ String login = user.userPrincipalName();
+ String id = user.id();
+ cachedAdminLogin = login != null ? login : "REDACTED";
+ cachedAdminSid = id != null ? id : "00000000-0000-0000-0000-000000000000";
+ }
}
From 9ebb164caff36acfd2423ea70a889cb97e6c5f72 Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Fri, 15 May 2026 21:32:57 +0800
Subject: [PATCH 02/13] Adds managed identity auth for SQL import/export
Enables user-assigned managed identity authentication for SQL database import/export operations, allowing secure access to storage accounts when shared-key authentication is disabled. Updates public APIs, implementation, and tests to support this authentication method. Also refines Entra-only authentication setup, streamlining interface and test usage.
Relates to customer scenarios with storage accounts enforcing identity-based access.
---
.../azure-resourcemanager-sql/CHANGELOG.md | 3 +-
.../SqlDatabaseExportRequestImpl.java | 10 +
.../SqlDatabaseForElasticPoolImpl.java | 6 +
.../sql/implementation/SqlDatabaseImpl.java | 10 +
.../SqlDatabaseImportRequestImpl.java | 10 +
.../sql/implementation/SqlServerImpl.java | 33 +-
.../sql/models/AuthenticationType.java | 5 +-
.../sql/models/SqlDatabase.java | 28 ++
.../sql/models/SqlDatabaseExportRequest.java | 14 +
.../sql/models/SqlDatabaseImportRequest.java | 14 +
.../sql/models/SqlDatabaseOperations.java | 28 ++
.../resourcemanager/sql/models/SqlServer.java | 34 +-
.../sql/SqlServerOperationsTests.java | 307 +++++++++++-------
.../resourcemanager/sql/SqlServerTest.java | 41 ---
14 files changed, 370 insertions(+), 173 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
index 3547a90eb80e..078a783c9572 100644
--- a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
+++ b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
@@ -4,7 +4,8 @@
### Features Added
-- Added a new method `withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid)` on `SqlServer.DefinitionStages.WithAdministratorLogin` to create a SQL Server with Microsoft Entra-only authentication enabled at creation time. This is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.
+- Added a new stage `WithAzureActiveDirectoryOnlyAuthentication` with the method `withAzureActiveDirectoryOnlyAuthentication()` on `SqlServer.DefinitionStages.WithAdministratorLogin`, followed by the new stage `WithExternalActiveDirectoryAdministrator` with the method `withExternalActiveDirectoryAdministrator(String userLogin, String sid)`. This enables creating a SQL Server with Microsoft Entra-only authentication enabled at creation time, which is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.
+- Added `withManagedIdentity(String managedIdentityResourceId)` to the import/export authentication stages of `SqlDatabaseImportRequest`, `SqlDatabaseExportRequest`, `SqlDatabaseOperations` (both standalone and in-elastic-pool import definitions) and `SqlDatabase` (both standalone and in-elastic-pool import definitions). This lets callers authenticate to the storage account using a user-assigned managed identity assigned to the SQL server instead of a storage access/shared key, which is required when the storage account has shared-key access disabled.
### Breaking Changes
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
index 55f1c3eb6992..c8446a1f3316 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
@@ -158,6 +158,16 @@ public SqlDatabaseExportRequestImpl withActiveDirectoryLoginAndPassword(String a
return this.withLoginAndPassword(administratorLogin, administratorPassword);
}
+ @Override
+ public SqlDatabaseExportRequestImpl withManagedIdentity(String managedIdentityResourceId) {
+ Objects.requireNonNull(managedIdentityResourceId);
+ this.inner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString());
+ this.inner.withAdministratorLogin(managedIdentityResourceId);
+ // No administrator password is required for managed identity authentication.
+ this.inner.withAdministratorLoginPassword(null);
+ return this;
+ }
+
SqlDatabaseExportRequestImpl withLoginAndPassword(String administratorLogin, String administratorPassword) {
this.inner.withAdministratorLogin(administratorLogin);
this.inner.withAdministratorLoginPassword(administratorPassword);
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java
index 301e9aeb3fc1..5ad5ce0c85e4 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java
@@ -108,6 +108,12 @@ public SqlDatabaseForElasticPoolImpl withActiveDirectoryLoginAndPassword(String
return this;
}
+ @Override
+ public SqlDatabaseForElasticPoolImpl withManagedIdentity(String managedIdentityResourceId) {
+ this.sqlDatabase.withManagedIdentity(managedIdentityResourceId);
+ return this;
+ }
+
@Override
public SqlDatabaseForElasticPoolImpl fromRestorePoint(RestorePoint restorePoint) {
this.sqlDatabase.fromRestorePoint(restorePoint);
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
index 075702b38c3b..0357afef97fe 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
@@ -756,6 +756,16 @@ public SqlDatabaseImpl withActiveDirectoryLoginAndPassword(String administratorL
return this;
}
+ @Override
+ public SqlDatabaseImpl withManagedIdentity(String managedIdentityResourceId) {
+ Objects.requireNonNull(managedIdentityResourceId);
+ this.importRequestInner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString());
+ this.importRequestInner.withAdministratorLogin(managedIdentityResourceId);
+ // No administrator password is required for managed identity authentication.
+ this.importRequestInner.withAdministratorLoginPassword(null);
+ return this;
+ }
+
@Override
public SqlDatabaseImpl fromRestorePoint(RestorePoint restorePoint) {
return fromRestorePoint(restorePoint, restorePoint.earliestRestoreDate());
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
index 4865c33a2586..28622af50773 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
@@ -117,6 +117,16 @@ public SqlDatabaseImportRequestImpl withActiveDirectoryLoginAndPassword(String a
return this.withLoginAndPassword(administratorLogin, administratorPassword);
}
+ @Override
+ public SqlDatabaseImportRequestImpl withManagedIdentity(String managedIdentityResourceId) {
+ Objects.requireNonNull(managedIdentityResourceId);
+ this.inner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString());
+ this.inner.withAdministratorLogin(managedIdentityResourceId);
+ // No administrator password is required for managed identity authentication.
+ this.inner.withAdministratorLoginPassword(null);
+ return this;
+ }
+
SqlDatabaseImportRequestImpl withLoginAndPassword(String administratorLogin, String administratorPassword) {
this.inner.withAdministratorLogin(administratorLogin);
this.inner.withAdministratorLoginPassword(administratorPassword);
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index 989fa2987282..dfbe3ec1e9ce 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -21,6 +21,7 @@
import com.azure.resourcemanager.sql.models.AdministratorName;
import com.azure.resourcemanager.sql.models.AdministratorType;
import com.azure.resourcemanager.sql.models.IdentityType;
+import com.azure.resourcemanager.sql.models.PrincipalType;
import com.azure.resourcemanager.sql.models.ResourceIdentity;
import com.azure.resourcemanager.sql.models.ServerExternalAdministrator;
import com.azure.resourcemanager.sql.models.ServerMetric;
@@ -360,14 +361,32 @@ public SqlServerImpl withAdministratorPassword(String administratorLoginPassword
}
@Override
- public SqlServerImpl withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid) {
+ public SqlServerImpl withAzureActiveDirectoryOnlyAuthentication() {
+ if (this.innerModel().administrators() == null) {
+ this.innerModel()
+ .withAdministrators(
+ new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY)
+ .withTenantId(UUID.fromString(this.manager().tenantId())));
+ }
+ this.innerModel().administrators().withAzureADOnlyAuthentication(true);
+ return this;
+ }
+
+ @Override
+ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid) {
+ if (this.innerModel().administrators() == null) {
+ this.innerModel()
+ .withAdministrators(
+ new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY)
+ .withTenantId(UUID.fromString(this.manager().tenantId())));
+ } else {
+ this.innerModel().administrators().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY);
+ }
this.innerModel()
- .withAdministrators(
- new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY)
- .withLogin(userLogin)
- .withSid(UUID.fromString(sid))
- .withTenantId(UUID.fromString(this.manager().tenantId()))
- .withAzureADOnlyAuthentication(true));
+ .administrators()
+ .withLogin(userLogin)
+ .withSid(UUID.fromString(sid))
+ .withPrincipalType(PrincipalType.USER);
return this;
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java
index dce6c2bd6f67..4ae7d2de471d 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java
@@ -9,7 +9,10 @@ public enum AuthenticationType {
SQL("SQL"),
/** Enum value ADPassword. */
- ADPASSWORD("ADPassword");
+ ADPASSWORD("ADPassword"),
+
+ /** Enum value ManagedIdentity. */
+ MANAGED_IDENTITY("ManagedIdentity");
/** The actual serialized value for a AuthenticationType instance. */
private final String value;
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
index c45a29cf3756..6a65753373c2 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
@@ -503,6 +503,20 @@ interface WithAuthentication {
*/
SqlDatabase.DefinitionStages.WithAttachAllOptions
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the source storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabase.DefinitionStages.WithAttachAllOptions
+ withManagedIdentity(String managedIdentityResourceId);
}
/**
@@ -581,6 +595,20 @@ interface WithAuthenticationAfterElasticPool {
*/
SqlDatabase.DefinitionStages.WithAttachFinal
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the source storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabase.DefinitionStages.WithAttachFinal
+ withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
index fd40a3b16d64..10201e18333d 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
@@ -97,6 +97,20 @@ interface WithAuthenticationTypeAndLoginPassword {
*/
SqlDatabaseExportRequest.DefinitionStages.WithExecute
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Sets the user-assigned managed identity used to authenticate to the SQL database for export.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the target storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabaseExportRequest.DefinitionStages.WithExecute
+ withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
index 21eac792c766..1f9f7d772f2b 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
@@ -86,6 +86,20 @@ interface WithAuthenticationTypeAndLoginPassword {
*/
SqlDatabaseImportRequest.DefinitionStages.WithExecute
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the source storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabaseImportRequest.DefinitionStages.WithExecute
+ withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
index 75edc555b91a..f5a0331c21f5 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
@@ -187,6 +187,20 @@ interface WithAuthentication {
*/
SqlDatabaseOperations.DefinitionStages.WithEditionDefaults
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the source storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabaseOperations.DefinitionStages.WithEditionDefaults
+ withManagedIdentity(String managedIdentityResourceId);
}
/** The SQL Database definition to import a BACPAC file as the source database. */
@@ -253,6 +267,20 @@ interface WithAuthenticationAfterElasticPool {
*/
SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
+
+ /**
+ * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
+ * its primary identity), the identity must be granted the appropriate role on the source storage account
+ * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
+ * privileges. When this method is used, no administrator password is sent to the service.
+ *
+ * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * @return next definition stage
+ */
+ SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions
+ withManagedIdentity(String managedIdentityResourceId);
}
/** The SQL Database definition to set a restorable dropped database as the source database. */
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index 28b6149326be..8a34964b0ca2 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -236,8 +236,8 @@ public interface SqlServer
/** Container interface for all the definitions that need to be implemented. */
interface Definition
extends DefinitionStages.Blank, DefinitionStages.WithGroup, DefinitionStages.WithAdministratorLogin,
- DefinitionStages.WithAdministratorPassword, DefinitionStages.WithElasticPool, DefinitionStages.WithDatabase,
- DefinitionStages.WithFirewallRule, DefinitionStages.WithPublicNetworkAccess, DefinitionStages.WithCreate {
+ DefinitionStages.WithAdministratorPassword, DefinitionStages.WithExternalActiveDirectoryAdministrator,
+ DefinitionStages.WithCreate {
}
/** Grouping of all the storage account definition stages. */
@@ -251,7 +251,7 @@ interface WithGroup extends GroupableResource.DefinitionStages.WithGroupWhen Microsoft Entra-only authentication is enabled, the SQL authentication (login/password)
+ * administrator is not configured on the server. This is required when the target subscription or management
+ * group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.
+ */
+ interface WithAzureActiveDirectoryOnlyAuthentication {
/**
- * Configures the SQL Server to use Microsoft Entra (Azure Active Directory) only authentication, with the
- * specified user or group as the Active Directory administrator. The SQL authentication
- * (login/password) administrator is not configured.
+ * Enables Microsoft Entra (Azure Active Directory) only authentication on the SQL Server.
*
- * This method should be used when the target subscription/management group enforces a policy that
- * requires Microsoft Entra-only authentication on Azure SQL Server creation.
+ * An external Microsoft Entra administrator must be specified on the next stage.
+ *
+ * @return Next stage of the SQL Server definition
+ */
+ WithExternalActiveDirectoryAdministrator withAzureActiveDirectoryOnlyAuthentication();
+ }
+
+ /** A SQL Server definition stage setting the external Microsoft Entra administrator on the server. */
+ interface WithExternalActiveDirectoryAdministrator {
+ /**
+ * Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server.
*
* @param userLogin the user or group login; it can be the name or the email address
* @param sid the user or group object ID
* @return Next stage of the SQL Server definition
*/
- WithCreate withAdministratorAzureActiveDirectoryOnly(String userLogin, String sid);
+ WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid);
}
/** A SQL Server definition setting admin user password. */
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index bf238f0d3f00..3e11828af5fc 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -7,6 +7,7 @@
import com.azure.core.management.Region;
import com.azure.core.management.exception.ManagementException;
import com.azure.core.test.annotation.DoNotRecord;
+import com.azure.core.util.CoreUtils;
import com.azure.resourcemanager.resources.fluentcore.model.Creatable;
import com.azure.resourcemanager.resources.fluentcore.model.Indexable;
import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils;
@@ -28,9 +29,8 @@
import com.azure.resourcemanager.sql.models.ReadWriteEndpointFailoverPolicy;
import com.azure.resourcemanager.sql.models.RegionCapabilities;
import com.azure.resourcemanager.sql.models.ReplicationLink;
+import com.azure.resourcemanager.sql.models.ReplicationState;
import com.azure.resourcemanager.sql.models.SampleName;
-import com.azure.resourcemanager.sql.models.SecurityAlertPolicyName;
-import com.azure.resourcemanager.sql.models.SecurityAlertPolicyState;
import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag;
import com.azure.resourcemanager.sql.models.ServiceObjectiveName;
import com.azure.resourcemanager.sql.models.Sku;
@@ -40,7 +40,6 @@
import com.azure.resourcemanager.sql.models.SqlDatabaseImportExportResponse;
import com.azure.resourcemanager.sql.models.SqlDatabasePremiumServiceObjective;
import com.azure.resourcemanager.sql.models.SqlDatabaseStandardServiceObjective;
-import com.azure.resourcemanager.sql.models.SqlDatabaseThreatDetectionPolicy;
import com.azure.resourcemanager.sql.models.SqlElasticPool;
import com.azure.resourcemanager.sql.models.SqlElasticPoolBasicEDTUs;
import com.azure.resourcemanager.sql.models.SqlFailoverGroup;
@@ -56,7 +55,10 @@
import com.azure.resourcemanager.sql.models.TransparentDataEncryption;
import com.azure.resourcemanager.sql.models.TransparentDataEncryptionState;
import com.azure.resourcemanager.storage.models.StorageAccount;
+import com.azure.resourcemanager.test.model.AzureUser;
+
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -85,6 +87,9 @@ public class SqlServerOperationsTests extends SqlServerTest {
private static final String SQL_FIREWALLRULE_NAME = "firewallrule1";
private static final String START_IPADDRESS = "10.102.1.10";
private static final String END_IPADDRESS = "10.102.1.12";
+ private static final Region DEFAULT_REGION = Region.US_WEST3;
+ private static final Region SECONDARY_REGION = Region.US_EAST2;
+ private static final Region TERTIARY_REGION = Region.EUROPE_NORTH;
// Only one sync database is allowed per region per subscription
// canCRUDSqlSyncMember and canCRUDSqlSyncGroup need to be in 2 different region
@@ -99,12 +104,14 @@ public void canCRUDSqlSyncMember() throws Exception {
final String administratorLogin = "sqladmin";
final String administratorPassword = password();
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -168,12 +175,14 @@ public void canCRUDSqlSyncGroup() throws Exception {
final String administratorLogin = "sqladmin";
final String administratorPassword = password();
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST2)
+ .withRegion(SECONDARY_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -202,7 +211,7 @@ public void canCRUDSqlSyncGroup() throws Exception {
Assertions.assertTrue(sqlServerManager.sqlServers()
.syncGroups()
- .listSyncDatabaseIds(Region.US_EAST2)
+ .listSyncDatabaseIds(SECONDARY_REGION)
.stream()
.findAny()
.isPresent());
@@ -223,12 +232,14 @@ public void canCopySqlDatabase() throws Exception {
final String epName = "epSample";
final String dbName = "dbSample";
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlPrimaryServerName)
- .withRegion(Region.US_EAST2)
+ .withRegion(SECONDARY_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.defineElasticPool(epName)
.withPremiumPool()
.attach()
@@ -240,9 +251,10 @@ public void canCopySqlDatabase() throws Exception {
SqlServer sqlSecondaryServer = sqlServerManager.sqlServers()
.define(sqlSecondaryServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withExistingResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
SqlDatabase dbSample = sqlPrimaryServer.databases().get(dbName);
@@ -266,12 +278,14 @@ public void canCRUDSqlFailoverGroup() throws Exception {
final String failoverGroupName2 = generateRandomResourceName("fg2", 22);
final String dbName = "dbSample";
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlPrimaryServer = sqlServerManager.sqlServers()
.define(sqlPrimaryServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -280,16 +294,18 @@ public void canCRUDSqlFailoverGroup() throws Exception {
SqlServer sqlSecondaryServer = sqlServerManager.sqlServers()
.define(sqlSecondaryServerName)
- .withRegion(Region.US_EAST2)
+ .withRegion(SECONDARY_REGION)
.withExistingResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
SqlServer sqlOtherServer = sqlServerManager.sqlServers()
.define(sqlOtherServerName)
- .withRegion(Region.EUROPE_NORTH)
+ .withRegion(TERTIARY_REGION)
.withExistingResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
SqlFailoverGroup failoverGroup = sqlPrimaryServer.failoverGroups()
@@ -381,15 +397,15 @@ public void canCRUDSqlFailoverGroup() throws Exception {
@Test
public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception {
String databaseName = "db-from-sample";
- String id = generateRandomUuid();
- String storageName = generateRandomResourceName(sqlServerName, 22);
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withBasicEdition()
@@ -456,12 +472,14 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
String sqlServerName1 = sqlServerName + "1";
String sqlServerName2 = sqlServerName + "2";
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlServer1 = sqlServerManager.sqlServers()
.define(sqlServerName1)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
Assertions.assertNotNull(sqlServer1);
@@ -480,9 +498,10 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
SqlServer sqlServer2 = sqlServerManager.sqlServers()
.define(sqlServerName2)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
Assertions.assertNotNull(sqlServer2);
@@ -506,8 +525,9 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
// LiveOnly because "test timing out after latest test proxy update"
String databaseName = "db-from-sample";
+ AzureUser user = azureCliSignedInUser();
- RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(Region.US_WEST3);
+ RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(DEFAULT_REGION);
Assertions.assertNotNull(regionCapabilities);
Assertions.assertNotNull(regionCapabilities.supportedCapabilitiesByServerVersion().get("12.0"));
Assertions.assertTrue(
@@ -519,9 +539,10 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.withSystemAssignedManagedServiceIdentity()
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
@@ -554,58 +575,62 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
@Test
@DoNotRecord(skipInPlayback = true)
- @Disabled("Not supported in AAD-only tenants: SQL auth and ADPassword (ROPC) are blocked.")
// The test makes calls to the Azure Storage data plane APIs which are not mocked at this time.
public void canCRUDSqlServerWithImportDatabase() throws Exception {
// Create
- String sqlServerAdminName = "sqladmin";
- String sqlServerAdminPassword = password();
- String id = generateRandomUuid();
String storageName = generateRandomResourceName(sqlServerName, 22);
+ AzureUser user = azureCliSignedInUser();
+
+ // Pre-create the resource group explicitly so that downstream resource providers (Storage in
+ // particular) observe it consistently. Creating the RG as a dependency of the SQL server can
+ // race with Storage RP and surface as 404 "ResourceGroupNotFound" when the storage account is
+ // subsequently created or fetched in that RG.
+ resourceManager.resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create();
+
+ // Import/export against an AAD-only SQL server requires Managed Identity authentication
+ // (SQL auth and ADPassword/ROPC are blocked in AAD-only tenants). The user-assigned managed
+ // identity must be:
+ // 1. Assigned to the SQL server (and typically set as its primary identity).
+ // 2. Granted "Storage Blob Data Contributor" on the storage account.
+ // 3. Mapped to a database user with the required privileges (db_owner or equivalent).
+ //
+ // The UAMI resource ID is read from the AZURE_SQL_IMPORT_EXPORT_UAMI_ID environment variable.
+ // If it is not provided the test is skipped instead of failing.
+ String uamiResourceId = System.getenv("AZURE_SQL_IMPORT_EXPORT_UAMI_ID");
+ Assumptions.assumeFalse(CoreUtils.isNullOrEmpty(uamiResourceId),
+ "AZURE_SQL_IMPORT_EXPORT_UAMI_ID is not set; skipping import/export Managed Identity test.");
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_EAST)
- .withNewResourceGroup(rgName)
- .withAdministratorLogin(sqlServerAdminName)
- .withAdministratorPassword(sqlServerAdminPassword)
- .withActiveDirectoryAdministrator("DSEng", id)
+ .withRegion(DEFAULT_REGION)
+ .withExistingResourceGroup(rgName)
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
SqlDatabase dbFromSample = sqlServer.databases()
.define("db-from-sample")
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withBasicEdition()
- .withTag("tag1", "value1")
.create();
Assertions.assertNotNull(dbFromSample);
Assertions.assertEquals(DatabaseEdition.BASIC, dbFromSample.edition());
- SqlDatabaseImportExportResponse exportedDB;
- StorageAccount storageAccount = null;
- try {
- storageAccount
- = storageManager.storageAccounts().getByResourceGroup(sqlServer.resourceGroupName(), storageName);
- } catch (ManagementException e) {
- Assertions.assertEquals(404, e.getResponse().getStatusCode());
- }
- if (storageAccount == null) {
- Creatable storageAccountCreatable = storageManager.storageAccounts()
- .define(storageName)
- .withRegion(sqlServer.regionName())
- .withExistingResourceGroup(sqlServer.resourceGroupName());
-
- exportedDB = dbFromSample.exportTo(storageAccountCreatable, "from-sample", "dbfromsample.bacpac")
- .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword)
- .execute();
- storageAccount
- = storageManager.storageAccounts().getByResourceGroup(sqlServer.resourceGroupName(), storageName);
- } else {
- exportedDB = dbFromSample.exportTo(storageAccount, "from-sample", "dbfromsample.bacpac")
- .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword)
+ // Pre-create the storage account synchronously (rather than wiring it as a Creatable dependency
+ // of the export request) so that we have a fully provisioned StorageAccount instance to reuse
+ // for both export and import, and any provisioning failure surfaces here rather than deep in
+ // the export pipeline.
+ StorageAccount storageAccount = storageManager.storageAccounts()
+ .define(storageName)
+ .withRegion(sqlServer.regionName())
+ .withExistingResourceGroup(sqlServer.resourceGroupName())
+ .create();
+
+ SqlDatabaseImportExportResponse exportedDB
+ = dbFromSample.exportTo(storageAccount, "from-sample", "dbfromsample.bacpac")
+ .withManagedIdentity(uamiResourceId)
.execute();
- }
SqlDatabase dbFromImport = sqlServer.databases()
.define("db-from-import")
@@ -613,7 +638,7 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
.withBasicPool()
.attach()
.importFrom(storageAccount, "from-sample", "dbfromsample.bacpac")
- .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword)
+ .withManagedIdentity(uamiResourceId)
.withTag("tag2", "value2")
.create();
Assertions.assertNotNull(dbFromImport);
@@ -628,13 +653,14 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
@Test
public void canCRUDSqlServerWithFirewallRule() throws Exception {
// Create
- String id = adminSid();
+ AzureUser user = azureCliSignedInUser();
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly("DSEng", id)
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator("DSEng", user.id())
.withoutAccessFromAzureServices()
.defineFirewallRule("somefirewallrule1")
.withIpAddress("0.0.0.1")
@@ -654,7 +680,7 @@ public void canCRUDSqlServerWithFirewallRule() throws Exception {
Assertions.assertNotNull(sqlADAdmin.id());
Assertions.assertEquals(AdministratorType.ACTIVE_DIRECTORY, sqlADAdmin.administratorType());
- sqlADAdmin = sqlServer.setActiveDirectoryAdministrator("DSEngAll", id);
+ sqlADAdmin = sqlServer.setActiveDirectoryAdministrator("DSEngAll", user.id());
Assertions.assertNotNull(sqlADAdmin);
Assertions.assertEquals("DSEngAll", sqlADAdmin.signInName());
Assertions.assertNotNull(sqlADAdmin.id());
@@ -767,13 +793,15 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
String elasticPool2Name = "elasticPool2";
String elasticPool3Name = "elasticPool3";
String elasticPool1Name = SQL_ELASTIC_POOL_NAME;
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.withoutAccessFromAzureServices()
.defineDatabase(SQL_DATABASE_NAME)
.attach()
@@ -885,36 +913,57 @@ public void canCRUDSqlDatabase() throws Exception {
validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME);
Assertions.assertTrue(sqlServer.databases().list().size() > 0);
- // Test security alert policy settings.
-
- final String storageAccountName = generateRandomResourceName("sqlsa", 20);
- StorageAccount storageAccount = storageManager.storageAccounts()
- .define(storageAccountName)
- .withRegion(Region.US_EAST)
- .withExistingResourceGroup(rgName)
- .disableSharedKeyAccess()
- .create();
- String accountKey = storageAccount.getKeys().get(0).value();
- String blobEntrypoint = storageAccount.endPoints().primary().blob();
-
- List disabledAlerts = Collections.singletonList("Sql_Injection");
-
- sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
- .withPolicyEnabled()
- .withStorageEndpoint(blobEntrypoint)
- .withStorageAccountAccessKey(accountKey)
- .withAlertsFilter(disabledAlerts)
- .create();
-
- sqlDatabase.refresh();
-
- SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
- Assertions.assertNotNull(alertPolicy);
- Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
- Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
- Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
-
- // Done testing security alert policy
+ // Threat detection policy test is disabled.
+ //
+ // The storage account used here is provisioned with shared-key access disabled
+ // (`disableSharedKeyAccess()`) because the target test subscription is an AAD-only tenant:
+ // storage accounts created there must reject shared-key authentication and only accept
+ // Microsoft Entra (Azure AD) credentials. As a result, the access-key path returns a key
+ // value that the storage data plane will not accept.
+ // The database-level "securityAlertPolicies" ARM resource does not offer a Managed
+ // Identity alternative either: the latest schema (api-version 2025-01-01) defines only
+ // state / disabledAlerts / emailAccountAdmins / emailAddresses / retentionDays /
+ // storageAccountAccessKey / storageEndpoint, with no identity / isManagedIdentityInUse
+ // field. Compare with "auditingSettings", which on the same api-version explicitly
+ // exposes isManagedIdentityInUse and documents the "omit access key to use SAMI" path.
+ // The service enforces this: omitting storageAccountAccessKey when storageEndpoint is
+ // set fails with "DataSecurityInvalidUserSuppliedParameter: storageAccountAccessKey
+ // parameter can not be null when storageEndpoint parameter is not null".
+ //
+ // References:
+ // https://learn.microsoft.com/en-us/rest/api/sql/database-security-alert-policies/create-or-update?view=rest-sql-2025-01-01
+ // https://learn.microsoft.com/en-us/rest/api/sql/database-blob-auditing-policies/create-or-update?view=rest-sql-2025-01-01
+ //
+ // Re-enable when either (a) the storage account in this test is recreated with shared
+ // key access enabled, or (b) the SQL securityAlertPolicies resource adds first-class
+ // Managed Identity support.
+
+ // final String storageAccountName = generateRandomResourceName("sqlsa", 20);
+ // StorageAccount storageAccount = storageManager.storageAccounts()
+ // .define(storageAccountName)
+ // .withRegion(DEFAULT_REGION)
+ // .withExistingResourceGroup(rgName)
+ // .disableSharedKeyAccess()
+ // .create();
+ // String accountKey = storageAccount.getKeys().get(0).value();
+ // String blobEntrypoint = storageAccount.endPoints().primary().blob();
+
+ // List disabledAlerts = Collections.singletonList("Sql_Injection");
+
+ // sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
+ // .withPolicyEnabled()
+ // .withStorageEndpoint(blobEntrypoint)
+ // .withStorageAccountAccessKey(accountKey)
+ // .withAlertsFilter(disabledAlerts)
+ // .create();
+
+ // sqlDatabase.refresh();
+
+ // SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
+ // Assertions.assertNotNull(alertPolicy);
+ // Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
+ // Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
+ // Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
// Test transparent data encryption settings.
TransparentDataEncryption transparentDataEncryption = sqlDatabase.getTransparentDataEncryption();
@@ -1004,6 +1053,13 @@ public void canManageReplicationLinks() throws Exception {
.define(SQL_DATABASE_NAME)
.withSourceDatabase(databaseInServer1.id())
.withMode(CreateMode.ONLINE_SECONDARY)
+ // Explicitly pin the secondary to Standard S0 (DTU-based) to match the primary.
+ // Without an explicit edition, Azure SQL applies the region's default tier —currently
+ // GeneralPurpose / GP_S_Gen5 (serverless, vCore-based), which counts against the
+ // subscription's vCore quota and can fail with
+ // "subscription would exceed the allowed vCore quota of 500" when the quota is near
+ // its limit (e.g. due to leftover resources from earlier test runs).
+ .withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
.create();
ResourceManagerUtils.sleep(Duration.ofSeconds(2));
List replicationLinksInDb1
@@ -1030,11 +1086,23 @@ public void canManageReplicationLinks() throws Exception {
replicationLinksInDb1.get(0).forceFailoverAllowDataLoss();
replicationLinksInDb1.get(0).refresh();
- // Wait for the link to leave SUSPENDED state after forceFailoverAllowDataLoss before delete;
- // otherwise delete() fails with 409 GeoReplicationCannotBecomePrimaryDuringUndo.
- ResourceManagerUtils.sleep(Duration.ofMinutes(30));
+ // Wait until the relationship has entered and then left SUSPENDED after
+ // forceFailoverAllowDataLoss; otherwise delete() fails with
+ // 409 GeoReplicationCannotBecomePrimaryDuringUndo.
+ ReplicationLink secondaryLink = replicationLinksInDb2.get(0);
+ int maxAttempts = 300;
+ boolean sawSuspended = false;
+ while (maxAttempts-- > 0) {
+ secondaryLink.refresh();
+ if (secondaryLink.replicationState() == ReplicationState.SUSPENDED) {
+ sawSuspended = true;
+ } else if (sawSuspended) {
+ break;
+ }
+ ResourceManagerUtils.sleep(Duration.ofSeconds(10));
+ }
- replicationLinksInDb2.get(0).delete();
+ secondaryLink.delete();
Assertions.assertEquals(databaseInServer2.listReplicationLinks().size(), 0);
sqlServer1.databases().delete(databaseInServer1.name());
@@ -1059,7 +1127,12 @@ public void canDoOperationsOnDataWarehouse() throws Exception {
Mono resourceStream = sqlServer.databases()
.define(SQL_DATABASE_NAME)
.withCollation(COLLATION)
- .withSku(DatabaseSku.DATAWAREHOUSE_DW1000C)
+ // Use DW100c (the smallest DataWarehouse SKU) instead of DW1000c.
+ // DW1000c provisions ~5 compute nodes and consumes a large share of the subscription's
+ // vCore quota (500). The test only exercises pause/resume/list operations, none of
+ // which require a large warehouse, so DW100c is sufficient and avoids the
+ // "subscription would exceed the allowed vCore quota of 500" failure.
+ .withSku(DatabaseSku.DATAWAREHOUSE_DW100C)
.createAsync();
SqlDatabase sqlDatabase = resourceStream.block();
@@ -1429,11 +1502,13 @@ private SqlServer createSqlServer() {
}
private SqlServer createSqlServer(String sqlServerName) {
+ AzureUser user = azureCliSignedInUser();
return sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
}
@@ -1455,7 +1530,7 @@ private void validateSqlFirewallRule(SqlFirewallRule sqlFirewallRule, String fir
Assertions.assertEquals(END_IPADDRESS, sqlFirewallRule.endIpAddress());
Assertions.assertEquals(rgName, sqlFirewallRule.resourceGroupName());
Assertions.assertEquals(sqlServerName, sqlFirewallRule.sqlServerName());
- Assertions.assertEquals(Region.US_WEST3, sqlFirewallRule.region());
+ Assertions.assertEquals(DEFAULT_REGION, sqlFirewallRule.region());
}
private static void validateListSqlElasticPool(List sqlElasticPools) {
@@ -1533,7 +1608,7 @@ public void testRandomSku() {
Collections.shuffle(elasticPoolSkus);
sqlServerManager.sqlServers()
- .getCapabilitiesByRegion(Region.US_WEST3)
+ .getCapabilitiesByRegion(DEFAULT_REGION)
.supportedCapabilitiesByServerVersion()
.forEach((x, serverVersionCapability) -> {
serverVersionCapability.supportedEditions().forEach(edition -> {
@@ -1552,11 +1627,13 @@ public void testRandomSku() {
});
});
+ AzureUser user = azureCliSignedInUser();
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.create();
// Too many elastic pools defined will hit sql server DTU quota limits.
@@ -1583,7 +1660,7 @@ public void generateSku() throws IOException {
StringBuilder databaseSkuBuilder = new StringBuilder();
StringBuilder elasticPoolSkuBuilder = new StringBuilder();
sqlServerManager.sqlServers()
- .getCapabilitiesByRegion(Region.US_WEST3)
+ .getCapabilitiesByRegion(DEFAULT_REGION)
.supportedCapabilitiesByServerVersion()
.forEach((x, serverVersionCapability) -> {
serverVersionCapability.supportedEditions().forEach(edition -> {
@@ -1620,7 +1697,7 @@ public void generateSku() throws IOException {
Files.write(new File("src/main/java/com/azure/resourcemanager/sql/models/ElasticPoolSku.java").toPath(),
elasticPoolSku.getBytes(StandardCharsets.UTF_8));
- sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(Region.US_WEST3).create(); // for deletion
+ sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create(); // for deletion
}
private byte[] readAllBytes(InputStream inputStream) throws IOException {
@@ -1668,12 +1745,14 @@ private void addStaticSkuDefinition(StringBuilder builder, String edition, Strin
@Test
public void canCreateAndUpdatePublicNetworkAccess() {
+ AzureUser user = azureCliSignedInUser();
// Create
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
- .withRegion(Region.US_WEST3)
+ .withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
- .withAdministratorAzureActiveDirectoryOnly(adminLogin(), adminSid())
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
.withoutAccessFromAzureServices()
.disablePublicNetworkAccess()
.create();
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
index e493ebaab66a..528b4731dd5b 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
@@ -15,7 +15,6 @@
import com.azure.resourcemanager.resources.ResourceManager;
import com.azure.resourcemanager.storage.StorageManager;
import com.azure.resourcemanager.test.ResourceManagerTestProxyTestBase;
-import com.azure.resourcemanager.test.model.AzureUser;
import com.azure.resourcemanager.test.utils.TestDelayProvider;
import com.azure.resourcemanager.test.utils.TestIdentifierProvider;
@@ -29,8 +28,6 @@ public abstract class SqlServerTest extends ResourceManagerTestProxyTestBase {
protected StorageManager storageManager;
protected String rgName = "";
protected String sqlServerName = "";
- private volatile String cachedAdminLogin;
- private volatile String cachedAdminSid;
@Override
protected HttpPipeline buildHttpPipeline(TokenCredential credential, AzureProfile profile,
@@ -58,42 +55,4 @@ protected void cleanUpResources() {
resourceManager.resourceGroups().beginDeleteByName(rgName);
}
- /**
- * Returns the signed-in user's principal name to use as the SQL Server Microsoft Entra administrator login.
- * The Azure SQL platform policy requires Microsoft Entra-only authentication enabled at server creation time.
- *
- * @return the signed-in user's principal name (sanitized in playback)
- */
- protected String adminLogin() {
- if (isPlaybackMode()) {
- return "REDACTED";
- }
- ensureAdminIdentityCached();
- return cachedAdminLogin != null ? cachedAdminLogin : "REDACTED";
- }
-
- /**
- * Returns the signed-in user's object ID to use as the SQL Server Microsoft Entra administrator SID.
- *
- * @return the signed-in user's object ID (sanitized in playback)
- */
- protected String adminSid() {
- if (isPlaybackMode()) {
- return "00000000-0000-0000-0000-000000000000";
- }
- ensureAdminIdentityCached();
- return cachedAdminSid != null ? cachedAdminSid : "00000000-0000-0000-0000-000000000000";
- }
-
- private synchronized void ensureAdminIdentityCached() {
- if (cachedAdminLogin != null && cachedAdminSid != null) {
- return;
- }
-
- AzureUser user = azureCliSignedInUser();
- String login = user.userPrincipalName();
- String id = user.id();
- cachedAdminLogin = login != null ? login : "REDACTED";
- cachedAdminSid = id != null ? id : "00000000-0000-0000-0000-000000000000";
- }
}
From 56426fde7c6ca634221259da226f82b30d14c9e2 Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Fri, 15 May 2026 21:36:57 +0800
Subject: [PATCH 03/13] Removes unnecessary blank line from test class
Cleans up formatting by deleting a redundant empty line,
improving code readability and maintaining style consistency.
---
.../test/java/com/azure/resourcemanager/sql/SqlServerTest.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
index 528b4731dd5b..d0e69fa5a50b 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
@@ -54,5 +54,4 @@ protected void cleanUpResources() {
ResourceManagerUtils.sleep(Duration.ofSeconds(1));
resourceManager.resourceGroups().beginDeleteByName(rgName);
}
-
}
From b3d54a696af3a4451b60c88936725f7dbaa7674c Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Sat, 16 May 2026 16:59:48 +0800
Subject: [PATCH 04/13] Simplifies interface declarations for readability
Removes unnecessary line breaks and indentation in several interface
method and type declarations to improve code readability and
maintain consistency across model definitions.
---
sdk/sql/azure-resourcemanager-sql/assets.json | 2 +-
.../com/azure/resourcemanager/sql/models/SqlDatabase.java | 3 +--
.../sql/models/SqlDatabaseExportRequest.java | 3 +--
.../sql/models/SqlDatabaseImportRequest.java | 3 +--
.../com/azure/resourcemanager/sql/models/SqlServer.java | 7 +++----
5 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/assets.json b/sdk/sql/azure-resourcemanager-sql/assets.json
index 74882f72acc8..3291fb81a1e9 100644
--- a/sdk/sql/azure-resourcemanager-sql/assets.json
+++ b/sdk/sql/azure-resourcemanager-sql/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/sql/azure-resourcemanager-sql",
- "Tag": "java/sql/azure-resourcemanager-sql_2dccdeacd3"
+ "Tag": "java/sql/azure-resourcemanager-sql_dbed07f81b"
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
index 6a65753373c2..7c4b17557e9b 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
@@ -607,8 +607,7 @@ interface WithAuthenticationAfterElasticPool {
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabase.DefinitionStages.WithAttachFinal
- withManagedIdentity(String managedIdentityResourceId);
+ SqlDatabase.DefinitionStages.WithAttachFinal withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
index 10201e18333d..c65aac7de02a 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
@@ -109,8 +109,7 @@ interface WithAuthenticationTypeAndLoginPassword {
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseExportRequest.DefinitionStages.WithExecute
- withManagedIdentity(String managedIdentityResourceId);
+ SqlDatabaseExportRequest.DefinitionStages.WithExecute withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
index 1f9f7d772f2b..f0e1ff4dffe7 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
@@ -98,8 +98,7 @@ interface WithAuthenticationTypeAndLoginPassword {
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseImportRequest.DefinitionStages.WithExecute
- withManagedIdentity(String managedIdentityResourceId);
+ SqlDatabaseImportRequest.DefinitionStages.WithExecute withManagedIdentity(String managedIdentityResourceId);
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index 8a34964b0ca2..e72f0676dc02 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -234,10 +234,9 @@ public interface SqlServer
**************************************************************/
/** Container interface for all the definitions that need to be implemented. */
- interface Definition
- extends DefinitionStages.Blank, DefinitionStages.WithGroup, DefinitionStages.WithAdministratorLogin,
- DefinitionStages.WithAdministratorPassword, DefinitionStages.WithExternalActiveDirectoryAdministrator,
- DefinitionStages.WithCreate {
+ interface Definition extends DefinitionStages.Blank, DefinitionStages.WithGroup,
+ DefinitionStages.WithAdministratorLogin, DefinitionStages.WithAdministratorPassword,
+ DefinitionStages.WithExternalActiveDirectoryAdministrator, DefinitionStages.WithCreate {
}
/** Grouping of all the storage account definition stages. */
From 1d944f320fd05c35b286bc9b94ed8dcef136fe96 Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Sat, 16 May 2026 23:16:09 +0800
Subject: [PATCH 05/13] Documents new managed identity auth in SQL db fluent
APIs
Allows user-assigned managed identity authentication for SQL database import, export, and creation stages in the fluent API, clarifying that these enhancements impact only SDK-implemented interfaces and do not break user implementations.
---
eng/lintingconfigs/revapi/track2/revapi.json | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/eng/lintingconfigs/revapi/track2/revapi.json b/eng/lintingconfigs/revapi/track2/revapi.json
index 4bf3cceba77c..08f451706aae 100644
--- a/eng/lintingconfigs/revapi/track2/revapi.json
+++ b/eng/lintingconfigs/revapi/track2/revapi.json
@@ -1516,6 +1516,14 @@
},
"justification": "Output-only immutable models' constructors are now private."
},
+ {
+ "code": "java.method.addedToInterface",
+ "new" : {
+ "matcher": "regex",
+ "match": "method .* com\\.azure\\.resourcemanager\\.sql\\.models\\.(SqlDatabase|SqlDatabaseOperations|SqlDatabaseExportRequest|SqlDatabaseImportRequest)\\.DefinitionStages\\.(WithAuthentication|WithAuthenticationAfterElasticPool|WithAuthenticationTypeAndLoginPassword)()?::withManagedIdentity\\(java\\.lang\\.String\\)"
+ },
+ "justification": "Adds user-assigned managed identity authentication option to SQL database import/export and creation fluent stages. These DefinitionStages interfaces are only meant to be implemented by the SDK itself."
+ },
{
"code": "java.method.visibilityReduced",
"old" : {
From 769d1f09be2a5e4220292051fb3eefb0bd6f287e Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Sun, 24 May 2026 19:08:35 +0800
Subject: [PATCH 06/13] Enhance SQL Database and Server APIs with user-assigned
managed identity support and update dependencies
---
eng/lintingconfigs/revapi/track2/revapi.json | 8 ---
sdk/sql/azure-resourcemanager-sql/assets.json | 2 +-
sdk/sql/azure-resourcemanager-sql/pom.xml | 12 ++++
.../sql/implementation/SqlServerImpl.java | 45 ++++++++++++--
.../sql/models/SqlDatabase.java | 39 ++++++++----
.../sql/models/SqlDatabaseExportRequest.java | 19 ++++--
.../sql/models/SqlDatabaseImportRequest.java | 19 ++++--
.../sql/models/SqlDatabaseOperations.java | 40 ++++++++-----
.../resourcemanager/sql/models/SqlServer.java | 35 ++++++++++-
.../sql/SqlServerOperationsTests.java | 60 +++++++++----------
.../resourcemanager/sql/SqlServerTest.java | 6 ++
11 files changed, 197 insertions(+), 88 deletions(-)
diff --git a/eng/lintingconfigs/revapi/track2/revapi.json b/eng/lintingconfigs/revapi/track2/revapi.json
index 08f451706aae..4bf3cceba77c 100644
--- a/eng/lintingconfigs/revapi/track2/revapi.json
+++ b/eng/lintingconfigs/revapi/track2/revapi.json
@@ -1516,14 +1516,6 @@
},
"justification": "Output-only immutable models' constructors are now private."
},
- {
- "code": "java.method.addedToInterface",
- "new" : {
- "matcher": "regex",
- "match": "method .* com\\.azure\\.resourcemanager\\.sql\\.models\\.(SqlDatabase|SqlDatabaseOperations|SqlDatabaseExportRequest|SqlDatabaseImportRequest)\\.DefinitionStages\\.(WithAuthentication|WithAuthenticationAfterElasticPool|WithAuthenticationTypeAndLoginPassword)()?::withManagedIdentity\\(java\\.lang\\.String\\)"
- },
- "justification": "Adds user-assigned managed identity authentication option to SQL database import/export and creation fluent stages. These DefinitionStages interfaces are only meant to be implemented by the SDK itself."
- },
{
"code": "java.method.visibilityReduced",
"old" : {
diff --git a/sdk/sql/azure-resourcemanager-sql/assets.json b/sdk/sql/azure-resourcemanager-sql/assets.json
index 3291fb81a1e9..2a5656d0c5a3 100644
--- a/sdk/sql/azure-resourcemanager-sql/assets.json
+++ b/sdk/sql/azure-resourcemanager-sql/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/sql/azure-resourcemanager-sql",
- "Tag": "java/sql/azure-resourcemanager-sql_dbed07f81b"
+ "Tag": "java/sql/azure-resourcemanager-sql_3f522469a6"
}
diff --git a/sdk/sql/azure-resourcemanager-sql/pom.xml b/sdk/sql/azure-resourcemanager-sql/pom.xml
index 7a8efaf5b219..9a094ea57323 100644
--- a/sdk/sql/azure-resourcemanager-sql/pom.xml
+++ b/sdk/sql/azure-resourcemanager-sql/pom.xml
@@ -70,6 +70,18 @@
azure-resourcemanager-storage
2.55.5
+
+ com.azure.resourcemanager
+ azure-resourcemanager-msi
+ 2.53.8
+ test
+
+
+ com.azure.resourcemanager
+ azure-resourcemanager-authorization
+ 2.53.9
+ test
+
org.junit.jupiter
junit-jupiter-engine
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index dfbe3ec1e9ce..df4ff7d7de59 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -20,8 +20,8 @@
import com.azure.resourcemanager.sql.fluent.models.ServerUsageInner;
import com.azure.resourcemanager.sql.models.AdministratorName;
import com.azure.resourcemanager.sql.models.AdministratorType;
-import com.azure.resourcemanager.sql.models.IdentityType;
import com.azure.resourcemanager.sql.models.PrincipalType;
+import com.azure.resourcemanager.sql.models.IdentityType;
import com.azure.resourcemanager.sql.models.ResourceIdentity;
import com.azure.resourcemanager.sql.models.ServerExternalAdministrator;
import com.azure.resourcemanager.sql.models.ServerMetric;
@@ -40,11 +40,15 @@
import com.azure.resourcemanager.sql.models.SqlServerSecurityAlertPolicyOperations;
import com.azure.resourcemanager.sql.models.SqlVirtualNetworkRule;
import com.azure.resourcemanager.sql.models.SqlVirtualNetworkRuleOperations;
+import com.azure.resourcemanager.sql.models.UserIdentity;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
/** Implementation for SqlServer and its parent interfaces. */
@@ -382,11 +386,15 @@ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin,
} else {
this.innerModel().administrators().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY);
}
- this.innerModel()
- .administrators()
- .withLogin(userLogin)
- .withSid(UUID.fromString(sid))
- .withPrincipalType(PrincipalType.USER);
+ this.innerModel().administrators().withLogin(userLogin).withSid(UUID.fromString(sid));
+ return this;
+ }
+
+ @Override
+ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid,
+ PrincipalType principalType) {
+ withExternalActiveDirectoryAdministrator(userLogin, sid);
+ this.innerModel().administrators().withPrincipalType(principalType);
return this;
}
@@ -520,6 +528,31 @@ public SqlServerImpl withSystemAssignedManagedServiceIdentity() {
return this;
}
+ @Override
+ public SqlServerImpl withPrimaryUserAssignedManagedServiceIdentity(String identityResourceId) {
+ Objects.requireNonNull(identityResourceId, "'identityResourceId' cannot be null.");
+ ResourceIdentity existing = this.innerModel().identity();
+ IdentityType type;
+ Map identities;
+ if (existing == null || existing.type() == null || existing.type() == IdentityType.NONE) {
+ type = IdentityType.USER_ASSIGNED;
+ identities = new HashMap<>();
+ } else if (existing.type() == IdentityType.SYSTEM_ASSIGNED) {
+ type = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED;
+ identities = new HashMap<>();
+ } else {
+ type = existing.type();
+ identities = existing.userAssignedIdentities() == null
+ ? new HashMap<>()
+ : new HashMap<>(existing.userAssignedIdentities());
+ }
+ identities.put(identityResourceId, new UserIdentity());
+ this.innerModel()
+ .withIdentity(new ResourceIdentity().withType(type).withUserAssignedIdentities(identities))
+ .withPrimaryUserAssignedIdentityId(identityResourceId);
+ return this;
+ }
+
@Override
public SqlServerImpl enablePublicNetworkAccess() {
this.innerModel().withPublicNetworkAccess(ServerNetworkAccessFlag.ENABLED);
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
index 7c4b17557e9b..ba9d730a1a55 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java
@@ -505,18 +505,24 @@ interface WithAuthentication {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the source storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
+ *
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabase.DefinitionStages.WithAttachAllOptions
- withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabase.DefinitionStages.WithAttachAllOptions
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/**
@@ -597,17 +603,24 @@ interface WithAuthenticationAfterElasticPool {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import.
+ *
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the source storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabase.DefinitionStages.WithAttachFinal withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabase.DefinitionStages.WithAttachFinal
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
index c65aac7de02a..8d18780e0b31 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
@@ -99,17 +99,24 @@ interface WithAuthenticationTypeAndLoginPassword {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Sets the user-assigned managed identity used to authenticate to the SQL database for export.
+ * Sets the user-assigned managed identity (UAMI) used to authenticate to the SQL database for export.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the target storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the target storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
+ *
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseExportRequest.DefinitionStages.WithExecute withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabaseExportRequest.DefinitionStages.WithExecute
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
index f0e1ff4dffe7..c684bbeb6700 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
@@ -88,17 +88,24 @@ interface WithAuthenticationTypeAndLoginPassword {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the source storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
+ *
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseImportRequest.DefinitionStages.WithExecute withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabaseImportRequest.DefinitionStages.WithExecute
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/**
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
index f5a0331c21f5..9003f4b66525 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
@@ -189,18 +189,24 @@ interface WithAuthentication {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the source storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
+ *
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseOperations.DefinitionStages.WithEditionDefaults
- withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabaseOperations.DefinitionStages.WithEditionDefaults
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/** The SQL Database definition to import a BACPAC file as the source database. */
@@ -269,18 +275,24 @@ interface WithAuthenticationAfterElasticPool {
withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword);
/**
- * Specifies the user-assigned managed identity used to authenticate to the SQL database for import.
+ * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import.
*
- * The SQL server must have the specified user-assigned managed identity assigned (and typically set as
- * its primary identity), the identity must be granted the appropriate role on the source storage account
- * (e.g. {@code Storage Blob Data Contributor}), and it must be mapped to a database user with the required
- * privileges. When this method is used, no administrator password is sent to the service.
+ * The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the
+ * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data
+ * Contributor}), and it must be mapped to a database user with the required privileges. When this method
+ * is used, no administrator password is sent to the service.
+ *
+ * This method is for user-assigned managed identity. To use the server's system-assigned managed
+ * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
+ * user's object ID is not a managed identity and cannot be used here.
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
* @return next definition stage
*/
- SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions
- withManagedIdentity(String managedIdentityResourceId);
+ default SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions
+ withManagedIdentity(String managedIdentityResourceId) {
+ throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass());
+ }
}
/** The SQL Database definition to set a restorable dropped database as the source database. */
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index e72f0676dc02..87f52f30dac2 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -283,11 +283,25 @@ interface WithExternalActiveDirectoryAdministrator {
/**
* Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server.
*
+ * The principal type is inferred by the service from the SID.
+ *
* @param userLogin the user or group login; it can be the name or the email address
* @param sid the user or group object ID
* @return Next stage of the SQL Server definition
*/
WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid);
+
+ /**
+ * Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server
+ * with an explicit principal type.
+ *
+ * @param userLogin the user, group, or application login name
+ * @param sid the user, group, or application object ID
+ * @param principalType the principal type (User, Group, or Application)
+ * @return Next stage of the SQL Server definition
+ */
+ WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid,
+ PrincipalType principalType);
}
/** A SQL Server definition setting admin user password. */
@@ -326,6 +340,22 @@ interface WithSystemAssignedManagedServiceIdentity {
WithCreate withSystemAssignedManagedServiceIdentity();
}
+ /** A SQL Server definition setting a user-assigned managed service identity as the primary identity. */
+ interface WithUserAssignedManagedServiceIdentity {
+ /**
+ * Sets the specified user-assigned managed identity (UAMI) on the SQL server and marks it as the
+ * primary identity. If a system-assigned identity is also enabled, the resulting identity type is
+ * {@code SystemAssigned,UserAssigned}; otherwise it is {@code UserAssigned}.
+ *
+ * @param identityResourceId the Azure resource ID of the user-assigned managed identity
+ * @return Next stage of the SQL Server definition
+ */
+ default WithCreate withPrimaryUserAssignedManagedServiceIdentity(String identityResourceId) {
+ throw new UnsupportedOperationException(
+ "[withPrimaryUserAssignedManagedServiceIdentity] is not supported in " + getClass());
+ }
+ }
+
/** A SQL Server definition for specifying elastic pool. */
interface WithElasticPool {
/**
@@ -395,8 +425,9 @@ interface WithPublicNetworkAccess {
* A SQL Server definition with sufficient inputs to create a new SQL Server in the cloud, but exposing
* additional optional inputs to specify.
*/
- interface WithCreate extends Creatable, WithActiveDirectoryAdministrator,
- WithSystemAssignedManagedServiceIdentity, WithElasticPool, WithDatabase, WithFirewallRule,
+ interface WithCreate
+ extends Creatable, WithActiveDirectoryAdministrator, WithSystemAssignedManagedServiceIdentity,
+ WithUserAssignedManagedServiceIdentity, WithElasticPool, WithDatabase, WithFirewallRule,
WithVirtualNetworkRule, WithPublicNetworkAccess, DefinitionWithTags {
}
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 3e11828af5fc..7dcb5e9452bb 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -7,7 +7,8 @@
import com.azure.core.management.Region;
import com.azure.core.management.exception.ManagementException;
import com.azure.core.test.annotation.DoNotRecord;
-import com.azure.core.util.CoreUtils;
+import com.azure.resourcemanager.authorization.models.BuiltInRole;
+import com.azure.resourcemanager.msi.models.Identity;
import com.azure.resourcemanager.resources.fluentcore.model.Creatable;
import com.azure.resourcemanager.resources.fluentcore.model.Indexable;
import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils;
@@ -58,7 +59,6 @@
import com.azure.resourcemanager.test.model.AzureUser;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -577,29 +577,33 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
@DoNotRecord(skipInPlayback = true)
// The test makes calls to the Azure Storage data plane APIs which are not mocked at this time.
public void canCRUDSqlServerWithImportDatabase() throws Exception {
- // Create
-
String storageName = generateRandomResourceName(sqlServerName, 22);
+ String uamiName = generateRandomResourceName("uami", 18);
AzureUser user = azureCliSignedInUser();
- // Pre-create the resource group explicitly so that downstream resource providers (Storage in
- // particular) observe it consistently. Creating the RG as a dependency of the SQL server can
- // race with Storage RP and surface as 404 "ResourceGroupNotFound" when the storage account is
- // subsequently created or fetched in that RG.
resourceManager.resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create();
- // Import/export against an AAD-only SQL server requires Managed Identity authentication
- // (SQL auth and ADPassword/ROPC are blocked in AAD-only tenants). The user-assigned managed
- // identity must be:
- // 1. Assigned to the SQL server (and typically set as its primary identity).
- // 2. Granted "Storage Blob Data Contributor" on the storage account.
- // 3. Mapped to a database user with the required privileges (db_owner or equivalent).
- //
- // The UAMI resource ID is read from the AZURE_SQL_IMPORT_EXPORT_UAMI_ID environment variable.
- // If it is not provided the test is skipped instead of failing.
- String uamiResourceId = System.getenv("AZURE_SQL_IMPORT_EXPORT_UAMI_ID");
- Assumptions.assumeFalse(CoreUtils.isNullOrEmpty(uamiResourceId),
- "AZURE_SQL_IMPORT_EXPORT_UAMI_ID is not set; skipping import/export Managed Identity test.");
+ Identity uami = msiManager.identities()
+ .define(uamiName)
+ .withRegion(DEFAULT_REGION)
+ .withExistingResourceGroup(rgName)
+ .create();
+
+ StorageAccount storageAccount = storageManager.storageAccounts()
+ .define(storageName)
+ .withRegion(DEFAULT_REGION)
+ .withExistingResourceGroup(rgName)
+ .disableSharedKeyAccess()
+ .create();
+
+ authorizationManager.roleAssignments()
+ .define(generateRandomUuid())
+ .forObjectId(uami.principalId())
+ .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR)
+ .withResourceScope(storageAccount)
+ .create();
+
+ ResourceManagerUtils.sleep(Duration.ofMinutes(1));
SqlServer sqlServer = sqlServerManager.sqlServers()
.define(sqlServerName)
@@ -607,6 +611,7 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
.withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withPrimaryUserAssignedManagedServiceIdentity(uami.id())
.create();
SqlDatabase dbFromSample = sqlServer.databases()
@@ -617,20 +622,11 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
Assertions.assertNotNull(dbFromSample);
Assertions.assertEquals(DatabaseEdition.BASIC, dbFromSample.edition());
- // Pre-create the storage account synchronously (rather than wiring it as a Creatable dependency
- // of the export request) so that we have a fully provisioned StorageAccount instance to reuse
- // for both export and import, and any provisioning failure surfaces here rather than deep in
- // the export pipeline.
- StorageAccount storageAccount = storageManager.storageAccounts()
- .define(storageName)
- .withRegion(sqlServer.regionName())
- .withExistingResourceGroup(sqlServer.resourceGroupName())
- .create();
-
SqlDatabaseImportExportResponse exportedDB
= dbFromSample.exportTo(storageAccount, "from-sample", "dbfromsample.bacpac")
- .withManagedIdentity(uamiResourceId)
+ .withManagedIdentity(uami.id())
.execute();
+ Assertions.assertNotNull(exportedDB);
SqlDatabase dbFromImport = sqlServer.databases()
.define("db-from-import")
@@ -638,7 +634,7 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
.withBasicPool()
.attach()
.importFrom(storageAccount, "from-sample", "dbfromsample.bacpac")
- .withManagedIdentity(uamiResourceId)
+ .withManagedIdentity(uami.id())
.withTag("tag2", "value2")
.create();
Assertions.assertNotNull(dbFromImport);
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
index d0e69fa5a50b..96df532f8dc3 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java
@@ -10,6 +10,8 @@
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.core.management.profile.AzureProfile;
+import com.azure.resourcemanager.authorization.AuthorizationManager;
+import com.azure.resourcemanager.msi.MsiManager;
import com.azure.resourcemanager.resources.fluentcore.utils.HttpPipelineProvider;
import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils;
import com.azure.resourcemanager.resources.ResourceManager;
@@ -26,6 +28,8 @@ public abstract class SqlServerTest extends ResourceManagerTestProxyTestBase {
protected ResourceManager resourceManager;
protected SqlServerManager sqlServerManager;
protected StorageManager storageManager;
+ protected MsiManager msiManager;
+ protected AuthorizationManager authorizationManager;
protected String rgName = "";
protected String sqlServerName = "";
@@ -45,6 +49,8 @@ protected void initializeClients(HttpPipeline httpPipeline, AzureProfile profile
internalContext.setIdentifierFunction(name -> new TestIdentifierProvider(testResourceNamer));
sqlServerManager = buildManager(SqlServerManager.class, httpPipeline, profile);
storageManager = buildManager(StorageManager.class, httpPipeline, profile);
+ msiManager = buildManager(MsiManager.class, httpPipeline, profile);
+ authorizationManager = buildManager(AuthorizationManager.class, httpPipeline, profile);
resourceManager = sqlServerManager.resourceManager();
setInternalContext(internalContext, sqlServerManager);
}
From c0bd6947661edfb725e6095cb44a9bb711edb5db Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Sun, 24 May 2026 21:20:53 +0800
Subject: [PATCH 07/13] Add module read permissions for authorization and
managed identity in pom.xml
---
sdk/sql/azure-resourcemanager-sql/pom.xml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/sdk/sql/azure-resourcemanager-sql/pom.xml b/sdk/sql/azure-resourcemanager-sql/pom.xml
index 9a094ea57323..523e118f5156 100644
--- a/sdk/sql/azure-resourcemanager-sql/pom.xml
+++ b/sdk/sql/azure-resourcemanager-sql/pom.xml
@@ -41,6 +41,9 @@
0.10
+ --add-reads com.azure.resourcemanager.sql=com.azure.resourcemanager.authorization
+ --add-reads com.azure.resourcemanager.sql=com.azure.resourcemanager.msi
+
--add-opens com.azure.resourcemanager.sql/com.azure.resourcemanager.sql=ALL-UNNAMED
--add-opens com.azure.resourcemanager.resources/com.azure.resourcemanager.resources=ALL-UNNAMED
--add-opens com.azure.resourcemanager.resources/com.azure.resourcemanager.resources.fluentcore.arm=ALL-UNNAMED
From 24958d09796fa75d367bdbf2dcdf2a01e3b816ee Mon Sep 17 00:00:00 2001
From: Hui Zhu
Date: Tue, 26 May 2026 13:56:15 +0800
Subject: [PATCH 08/13] Add support for explicit principal type in external
Active Directory administrator configuration
---
.../azure-resourcemanager-sql/CHANGELOG.md | 10 +++---
.../sql/implementation/SqlServerImpl.java | 17 ++++-----
.../resourcemanager/sql/models/SqlServer.java | 15 ++------
.../sql/SqlServerOperationsTests.java | 35 ++++++++++---------
4 files changed, 32 insertions(+), 45 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
index 36cdfff3864d..109933b3bc2d 100644
--- a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
+++ b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
@@ -4,7 +4,7 @@
### Features Added
-- Added a new stage `WithAzureActiveDirectoryOnlyAuthentication` with the method `withAzureActiveDirectoryOnlyAuthentication()` on `SqlServer.DefinitionStages.WithAdministratorLogin`, followed by the new stage `WithExternalActiveDirectoryAdministrator` with the method `withExternalActiveDirectoryAdministrator(String userLogin, String sid)`. This enables creating a SQL Server with Microsoft Entra-only authentication enabled at creation time, which is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.
+- Added a new stage `WithAzureActiveDirectoryOnlyAuthentication` with the method `withAzureActiveDirectoryOnlyAuthentication()` on `SqlServer.DefinitionStages.WithAdministratorLogin`, followed by the new stage `WithExternalActiveDirectoryAdministrator` with the method `withExternalActiveDirectoryAdministrator(String userLogin, String sid, PrincipalType principalType)`. This enables creating a SQL Server with Microsoft Entra-only authentication enabled at creation time, which is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation. The `principalType` must be specified explicitly because the service does not reliably infer it from the SID.
- Added `withManagedIdentity(String managedIdentityResourceId)` to the import/export authentication stages of `SqlDatabaseImportRequest`, `SqlDatabaseExportRequest`, `SqlDatabaseOperations` (both standalone and in-elastic-pool import definitions) and `SqlDatabase` (both standalone and in-elastic-pool import definitions). This lets callers authenticate to the storage account using a user-assigned managed identity assigned to the SQL server instead of a storage access/shared key, which is required when the storage account has shared-key access disabled.
### Breaking Changes
@@ -366,21 +366,21 @@
### Breaking Changes
-- Removed `NEW` from `SecurityAlertPolicyState`. The constant is non-functional.
+- Removed `NEW` from `SecurityAlertPolicyState`. The constant is non-functional.
- Removed `withPolicyNew` method from `SqlDatabaseThreatDetectionPolicy` since `NEW` is no longer supported in `SecurityAlertPolicyState`.
- Removed `nextResetTime` and `resourceName` methods from `ServerMetric` and `SqlDatabase`. The methods are non-functional.
- Removed `listMetricsDefinitions` and `listMetrics` methods from `SqlDatabase`. Metrics in SQL have been replaced by the Azure monitor shoebox metrics API. Not in SQL any more.
- Removed `listServiceTierAdvisors` method from `SqlDatabase`. It's no longer supported.
- Removed class `ElasticPoolDatabaseActivity`. It's removed from service definition.
-- Removed `listDatabaseActivities`, `listDatabaseMetricDefinitions` and `listDatabaseMetrics` methods from `SqlElasticPool`.
-- Removed `elasticPoolName` and `serviceLevelObjective` methods from `SqlRestorableDroppedDatabase`.
+- Removed `listDatabaseActivities`, `listDatabaseMetricDefinitions` and `listDatabaseMetrics` methods from `SqlElasticPool`.
+- Removed `elasticPoolName` and `serviceLevelObjective` methods from `SqlRestorableDroppedDatabase`.
- Removed `getServiceObjective`, `listRecommendedElasticPools`, `listServiceObjectives` methods from `SqlServer`.
- Removed `withCreationDate` and `withThumbprint` from SqlServerKeyOperations. The properties are no longer mutable.
- Renamed class from `TransparentDataEncryptionInner` to `LogicalDatabaseTransparentDataEncryptionInner`.
- Removed class `TransparentDataEncryptionActivity`. The class is no longer functional.
- Removed `listActivities` from `TransparentDataEncryption` since `TransparentDataEncryptionActivity` is removed.
- Renamed `TransparentDataEncryptionStatus` to `TransparentDataEncryptionState`.
-- Removed `location`, `requestedDatabaseDtuCap`, `requestedDatabaseDtuGuarantee`, `requestedDatabaseDtuMax`, `requestedDatabaseDtuMin`,
+- Removed `location`, `requestedDatabaseDtuCap`, `requestedDatabaseDtuGuarantee`, `requestedDatabaseDtuMax`, `requestedDatabaseDtuMin`,
`requestedDtu`, `requestedDtuGuarantee` and `requestedElasticPoolName`, `requestedStorageLimitInGB` and `requestedStorageLimitInMB` methods from `ElasticPoolActivity`. The properties are no longer functional.
- Renamed class from `ElasticPoolActivityInner` to `ElasticPoolOperationInner`.
- Removed `readReplicaCount` and `withReadReplicaCount` from `DatabaseUpdate`. The property is non-functional.
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index df4ff7d7de59..419d085df97e 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -377,7 +377,8 @@ public SqlServerImpl withAzureActiveDirectoryOnlyAuthentication() {
}
@Override
- public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid) {
+ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid,
+ PrincipalType principalType) {
if (this.innerModel().administrators() == null) {
this.innerModel()
.withAdministrators(
@@ -386,15 +387,11 @@ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin,
} else {
this.innerModel().administrators().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY);
}
- this.innerModel().administrators().withLogin(userLogin).withSid(UUID.fromString(sid));
- return this;
- }
-
- @Override
- public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid,
- PrincipalType principalType) {
- withExternalActiveDirectoryAdministrator(userLogin, sid);
- this.innerModel().administrators().withPrincipalType(principalType);
+ this.innerModel()
+ .administrators()
+ .withLogin(userLogin)
+ .withSid(UUID.fromString(sid))
+ .withPrincipalType(principalType);
return this;
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index 87f52f30dac2..e7f256a54493 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -283,21 +283,10 @@ interface WithExternalActiveDirectoryAdministrator {
/**
* Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server.
*
- * The principal type is inferred by the service from the SID.
- *
- * @param userLogin the user or group login; it can be the name or the email address
- * @param sid the user or group object ID
- * @return Next stage of the SQL Server definition
- */
- WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid);
-
- /**
- * Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server
- * with an explicit principal type.
- *
* @param userLogin the user, group, or application login name
* @param sid the user, group, or application object ID
- * @param principalType the principal type (User, Group, or Application)
+ * @param principalType the principal type (User, Group, or Application). Must be specified explicitly;
+ * the service does not reliably infer it from the SID
* @return Next stage of the SQL Server definition
*/
WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid,
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 7dcb5e9452bb..3d0e7c8b8436 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -26,6 +26,7 @@
import com.azure.resourcemanager.sql.models.ElasticPoolEdition;
import com.azure.resourcemanager.sql.models.ElasticPoolSku;
import com.azure.resourcemanager.sql.models.FailoverGroupReplicationRole;
+import com.azure.resourcemanager.sql.models.PrincipalType;
import com.azure.resourcemanager.sql.models.ReadOnlyEndpointFailoverPolicy;
import com.azure.resourcemanager.sql.models.ReadWriteEndpointFailoverPolicy;
import com.azure.resourcemanager.sql.models.RegionCapabilities;
@@ -111,7 +112,7 @@ public void canCRUDSqlSyncMember() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -182,7 +183,7 @@ public void canCRUDSqlSyncGroup() throws Exception {
.withRegion(SECONDARY_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -239,7 +240,7 @@ public void canCopySqlDatabase() throws Exception {
.withRegion(SECONDARY_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.defineElasticPool(epName)
.withPremiumPool()
.attach()
@@ -254,7 +255,7 @@ public void canCopySqlDatabase() throws Exception {
.withRegion(DEFAULT_REGION)
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
SqlDatabase dbSample = sqlPrimaryServer.databases().get(dbName);
@@ -285,7 +286,7 @@ public void canCRUDSqlFailoverGroup() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.defineDatabase(dbName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -297,7 +298,7 @@ public void canCRUDSqlFailoverGroup() throws Exception {
.withRegion(SECONDARY_REGION)
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
SqlServer sqlOtherServer = sqlServerManager.sqlServers()
@@ -305,7 +306,7 @@ public void canCRUDSqlFailoverGroup() throws Exception {
.withRegion(TERTIARY_REGION)
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
SqlFailoverGroup failoverGroup = sqlPrimaryServer.failoverGroups()
@@ -405,7 +406,7 @@ public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
.withBasicEdition()
@@ -479,7 +480,7 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
Assertions.assertNotNull(sqlServer1);
@@ -501,7 +502,7 @@ public void canCreateAndAquireServerDnsAlias() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
Assertions.assertNotNull(sqlServer2);
@@ -542,7 +543,7 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.withSystemAssignedManagedServiceIdentity()
.defineDatabase(databaseName)
.fromSample(SampleName.ADVENTURE_WORKS_LT)
@@ -610,7 +611,7 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
.withRegion(DEFAULT_REGION)
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.withPrimaryUserAssignedManagedServiceIdentity(uami.id())
.create();
@@ -656,7 +657,7 @@ public void canCRUDSqlServerWithFirewallRule() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator("DSEng", user.id())
+ .withExternalActiveDirectoryAdministrator("DSEng", user.id(), PrincipalType.USER)
.withoutAccessFromAzureServices()
.defineFirewallRule("somefirewallrule1")
.withIpAddress("0.0.0.1")
@@ -797,7 +798,7 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.withoutAccessFromAzureServices()
.defineDatabase(SQL_DATABASE_NAME)
.attach()
@@ -1504,7 +1505,7 @@ private SqlServer createSqlServer(String sqlServerName) {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
}
@@ -1629,7 +1630,7 @@ public void testRandomSku() {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.create();
// Too many elastic pools defined will hit sql server DTU quota limits.
@@ -1748,7 +1749,7 @@ public void canCreateAndUpdatePublicNetworkAccess() {
.withRegion(DEFAULT_REGION)
.withNewResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id())
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
.withoutAccessFromAzureServices()
.disablePublicNetworkAccess()
.create();
From ca8fabda531c5754876430cf5a3b6d12337a2158 Mon Sep 17 00:00:00 2001
From: "Xiaofei Cao (from Dev Box)"
Date: Fri, 29 May 2026 19:02:09 +0800
Subject: [PATCH 09/13] fix import/export
---
.../azure-resourcemanager-sql/CHANGELOG.md | 4 +-
.../SqlDatabaseExportRequestImpl.java | 11 +-
.../sql/implementation/SqlDatabaseImpl.java | 12 +-
.../sql/implementation/SqlServerImpl.java | 11 +-
.../sql/models/SqlDatabaseExportRequest.java | 6 +-
.../sql/models/SqlDatabaseOperations.java | 6 +-
.../resourcemanager/sql/models/SqlServer.java | 16 ++-
.../sql/SqlServerOperationsTests.java | 110 +++++++++---------
8 files changed, 104 insertions(+), 72 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
index 109933b3bc2d..d9186f7887b6 100644
--- a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
+++ b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md
@@ -4,8 +4,8 @@
### Features Added
-- Added a new stage `WithAzureActiveDirectoryOnlyAuthentication` with the method `withAzureActiveDirectoryOnlyAuthentication()` on `SqlServer.DefinitionStages.WithAdministratorLogin`, followed by the new stage `WithExternalActiveDirectoryAdministrator` with the method `withExternalActiveDirectoryAdministrator(String userLogin, String sid, PrincipalType principalType)`. This enables creating a SQL Server with Microsoft Entra-only authentication enabled at creation time, which is required when the target subscription/management group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation. The `principalType` must be specified explicitly because the service does not reliably infer it from the SID.
-- Added `withManagedIdentity(String managedIdentityResourceId)` to the import/export authentication stages of `SqlDatabaseImportRequest`, `SqlDatabaseExportRequest`, `SqlDatabaseOperations` (both standalone and in-elastic-pool import definitions) and `SqlDatabase` (both standalone and in-elastic-pool import definitions). This lets callers authenticate to the storage account using a user-assigned managed identity assigned to the SQL server instead of a storage access/shared key, which is required when the storage account has shared-key access disabled.
+- Supported `withAzureActiveDirectoryOnlyAuthentication` and `withExternalActiveDirectoryAdministrator` for `SqlServer`.
+- Supported `withManagedIdentity` for `SqlDatabase` import and export.
### Breaking Changes
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
index c8446a1f3316..7f347ddf266a 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
@@ -3,6 +3,7 @@
package com.azure.resourcemanager.sql.implementation;
import com.azure.core.management.exception.ManagementException;
+import com.azure.core.util.CoreUtils;
import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem;
import com.azure.resourcemanager.resources.fluentcore.model.Creatable;
import com.azure.resourcemanager.resources.fluentcore.model.Indexable;
@@ -71,8 +72,10 @@ private Mono getOrCreateStorageAccountContainer(final StorageAccount
.flatMap(storageAccountKey -> {
self.inner.withStorageUri(
String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
- self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
- self.inner.withStorageKey(storageAccountKey.value());
+ if (storageAccount.isSharedKeyAccessAllowed() && CoreUtils.isNullOrEmpty(self.inner.storageKey())) {
+ self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
+ self.inner.withStorageKey(storageAccountKey.value());
+ }
BlobContainers blobContainers = this.sqlServerManager.storageManager().blobContainers();
return blobContainers.getAsync(parent().resourceGroupName(), storageAccount.name(), containerName)
.onErrorResume(error -> {
@@ -165,6 +168,10 @@ public SqlDatabaseExportRequestImpl withManagedIdentity(String managedIdentityRe
this.inner.withAdministratorLogin(managedIdentityResourceId);
// No administrator password is required for managed identity authentication.
this.inner.withAdministratorLoginPassword(null);
+
+ // Use the same MI for storage account access.
+ this.inner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY);
+ this.inner.withStorageKey(managedIdentityResourceId);
return this;
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
index 0357afef97fe..74fdc4b5379e 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java
@@ -6,6 +6,7 @@
import com.azure.core.http.rest.PagedFlux;
import com.azure.core.http.rest.PagedIterable;
import com.azure.core.management.Region;
+import com.azure.core.util.CoreUtils;
import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId;
import com.azure.resourcemanager.resources.fluentcore.arm.ResourceUtils;
import com.azure.resourcemanager.resources.fluentcore.arm.models.implementation.ExternalChildResourceImpl;
@@ -717,8 +718,11 @@ public SqlDatabaseImpl importFrom(final StorageAccount storageAccount, final Str
.flatMap(storageAccountKey -> {
self.importRequestInner.withStorageUri(
String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
- self.importRequestInner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
- self.importRequestInner.withStorageKey(storageAccountKey.value());
+ if (storageAccount.isSharedKeyAccessAllowed()
+ && CoreUtils.isNullOrEmpty(self.importRequestInner.storageKey())) {
+ self.importRequestInner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
+ self.importRequestInner.withStorageKey(storageAccountKey.value());
+ }
return context.voidMono();
}));
return this;
@@ -763,6 +767,10 @@ public SqlDatabaseImpl withManagedIdentity(String managedIdentityResourceId) {
this.importRequestInner.withAdministratorLogin(managedIdentityResourceId);
// No administrator password is required for managed identity authentication.
this.importRequestInner.withAdministratorLoginPassword(null);
+
+ // Use the same MI for storage account access.
+ this.importRequestInner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY);
+ this.importRequestInner.withStorageKey(managedIdentityResourceId);
return this;
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index 419d085df97e..3998dfe1ea9b 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -12,6 +12,7 @@
import com.azure.resourcemanager.resources.fluentcore.arm.models.implementation.GroupableResourceImpl;
import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem;
import com.azure.resourcemanager.resources.fluentcore.utils.PagedConverter;
+import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils;
import com.azure.resourcemanager.sql.SqlServerManager;
import com.azure.resourcemanager.sql.fluent.models.RestorableDroppedDatabaseInner;
import com.azure.resourcemanager.sql.fluent.models.ServerAutomaticTuningInner;
@@ -327,6 +328,12 @@ public void removeActiveDirectoryAdministrator() {
.block();
}
+ @Override
+ public boolean isAzureActiveDirectoryOnlyAuthenticationEnabled() {
+ return this.innerModel().administrators() != null
+ && ResourceManagerUtils.toPrimitiveBoolean(this.innerModel().administrators().azureADOnlyAuthentication());
+ }
+
@Override
public SqlServerAutomaticTuning getServerAutomaticTuning() {
ServerAutomaticTuningInner serverAutomaticTuningInner
@@ -377,7 +384,7 @@ public SqlServerImpl withAzureActiveDirectoryOnlyAuthentication() {
}
@Override
- public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin, String sid,
+ public SqlServerImpl withExternalActiveDirectoryAdministrator(String adminLogin, String sid,
PrincipalType principalType) {
if (this.innerModel().administrators() == null) {
this.innerModel()
@@ -389,7 +396,7 @@ public SqlServerImpl withExternalActiveDirectoryAdministrator(String userLogin,
}
this.innerModel()
.administrators()
- .withLogin(userLogin)
+ .withLogin(adminLogin)
.withSid(UUID.fromString(sid))
.withPrincipalType(principalType);
return this;
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
index 8d18780e0b31..0a4d71cdcc57 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java
@@ -106,11 +106,11 @@ interface WithAuthenticationTypeAndLoginPassword {
* Contributor}), and it must be mapped to a database user with the required privileges. When this method
* is used, no administrator password is sent to the service.
*
- * This method is for user-assigned managed identity. To use the server's system-assigned managed
- * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
- * user's object ID is not a managed identity and cannot be used here.
+ * This method is for user-assigned managed identity. System-assigned managed identity is not supported.
+ * See Limitations
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * for both SQL and storage access
* @return next definition stage
*/
default SqlDatabaseExportRequest.DefinitionStages.WithExecute
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
index 9003f4b66525..a1dd28fa1016 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java
@@ -282,11 +282,11 @@ interface WithAuthenticationAfterElasticPool {
* Contributor}), and it must be mapped to a database user with the required privileges. When this method
* is used, no administrator password is sent to the service.
*
- * This method is for user-assigned managed identity. To use the server's system-assigned managed
- * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
- * user's object ID is not a managed identity and cannot be used here.
+ * This method is for user-assigned managed identity. System-assigned managed identity is not supported.
+ * See Limitations
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * for both SQL and storage access
* @return next definition stage
*/
default SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
index e7f256a54493..b270e0bbd331 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java
@@ -157,6 +157,16 @@ public interface SqlServer
/** Removes the Active Directory administrator from this server. */
void removeActiveDirectoryAdministrator();
+ /**
+ * Checks whether Azure Active Directory (AAD) only authentication enabled.
+ *
+ * @return true if Azure Active Directory (AAD) only authentication enabled
+ */
+ default boolean isAzureActiveDirectoryOnlyAuthenticationEnabled() {
+ throw new UnsupportedOperationException(
+ "[isAzureActiveDirectoryOnlyAuthenticationEnabled] is not supported in " + getClass());
+ }
+
/**
* Gets a SQL server automatic tuning state and options.
*
@@ -283,13 +293,13 @@ interface WithExternalActiveDirectoryAdministrator {
/**
* Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server.
*
- * @param userLogin the user, group, or application login name
+ * @param adminLogin the user, group, or application login name
* @param sid the user, group, or application object ID
- * @param principalType the principal type (User, Group, or Application). Must be specified explicitly;
+ * @param principalType the principal type (User, Group, or Application). Must be specified explicitly, since
* the service does not reliably infer it from the SID
* @return Next stage of the SQL Server definition
*/
- WithCreate withExternalActiveDirectoryAdministrator(String userLogin, String sid,
+ WithCreate withExternalActiveDirectoryAdministrator(String adminLogin, String sid,
PrincipalType principalType);
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 3d0e7c8b8436..1d774104fbf4 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -33,6 +33,8 @@
import com.azure.resourcemanager.sql.models.ReplicationLink;
import com.azure.resourcemanager.sql.models.ReplicationState;
import com.azure.resourcemanager.sql.models.SampleName;
+import com.azure.resourcemanager.sql.models.SecurityAlertPolicyName;
+import com.azure.resourcemanager.sql.models.SecurityAlertPolicyState;
import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag;
import com.azure.resourcemanager.sql.models.ServiceObjectiveName;
import com.azure.resourcemanager.sql.models.Sku;
@@ -42,6 +44,7 @@
import com.azure.resourcemanager.sql.models.SqlDatabaseImportExportResponse;
import com.azure.resourcemanager.sql.models.SqlDatabasePremiumServiceObjective;
import com.azure.resourcemanager.sql.models.SqlDatabaseStandardServiceObjective;
+import com.azure.resourcemanager.sql.models.SqlDatabaseThreatDetectionPolicy;
import com.azure.resourcemanager.sql.models.SqlElasticPool;
import com.azure.resourcemanager.sql.models.SqlElasticPoolBasicEDTUs;
import com.azure.resourcemanager.sql.models.SqlFailoverGroup;
@@ -577,10 +580,9 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception {
@Test
@DoNotRecord(skipInPlayback = true)
// The test makes calls to the Azure Storage data plane APIs which are not mocked at this time.
- public void canCRUDSqlServerWithImportDatabase() throws Exception {
+ public void canCRUDSqlServerWithImportDatabase() {
String storageName = generateRandomResourceName(sqlServerName, 22);
String uamiName = generateRandomResourceName("uami", 18);
- AzureUser user = azureCliSignedInUser();
resourceManager.resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create();
@@ -611,10 +613,12 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception {
.withRegion(DEFAULT_REGION)
.withExistingResourceGroup(rgName)
.withAzureActiveDirectoryOnlyAuthentication()
- .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
+ .withExternalActiveDirectoryAdministrator(uamiName, uami.principalId(), PrincipalType.APPLICATION)
.withPrimaryUserAssignedManagedServiceIdentity(uami.id())
.create();
+ Assertions.assertTrue(sqlServer.isAzureActiveDirectoryOnlyAuthenticationEnabled());
+
SqlDatabase dbFromSample = sqlServer.databases()
.define("db-from-sample")
.fromSample(SampleName.ADVENTURE_WORKS_LT)
@@ -897,9 +901,37 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
}
@Test
+ @Disabled("Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) "
+ + "does not require storage account any more. "
+ + "See Oury Ba's answer: https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp")
public void canCRUDSqlDatabase() throws Exception {
+ final String storageAccountName = generateRandomResourceName("sqlsa", 20);
+ AzureUser user = azureCliSignedInUser();
+
// Create
- SqlServer sqlServer = createSqlServer();
+ SqlServer sqlServer = sqlServerManager.sqlServers()
+ .define(sqlServerName)
+ .withRegion(DEFAULT_REGION)
+ .withNewResourceGroup(rgName)
+ .withAzureActiveDirectoryOnlyAuthentication()
+ .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER)
+ .withSystemAssignedManagedServiceIdentity()
+ .create();
+
+ StorageAccount storageAccount = storageManager.storageAccounts()
+ .define(storageAccountName)
+ .withRegion(DEFAULT_REGION)
+ .withExistingResourceGroup(rgName)
+ .disableSharedKeyAccess()
+ .create();
+
+ authorizationManager.roleAssignments()
+ .define(generateRandomUuid())
+ .forObjectId(sqlServer.systemAssignedManagedServiceIdentityPrincipalId())
+ .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR)
+ .withResourceScope(storageAccount)
+ .create();
+
Mono resourceStream = sqlServer.databases()
.define(SQL_DATABASE_NAME)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -910,57 +942,25 @@ public void canCRUDSqlDatabase() throws Exception {
validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME);
Assertions.assertTrue(sqlServer.databases().list().size() > 0);
- // Threat detection policy test is disabled.
- //
- // The storage account used here is provisioned with shared-key access disabled
- // (`disableSharedKeyAccess()`) because the target test subscription is an AAD-only tenant:
- // storage accounts created there must reject shared-key authentication and only accept
- // Microsoft Entra (Azure AD) credentials. As a result, the access-key path returns a key
- // value that the storage data plane will not accept.
- // The database-level "securityAlertPolicies" ARM resource does not offer a Managed
- // Identity alternative either: the latest schema (api-version 2025-01-01) defines only
- // state / disabledAlerts / emailAccountAdmins / emailAddresses / retentionDays /
- // storageAccountAccessKey / storageEndpoint, with no identity / isManagedIdentityInUse
- // field. Compare with "auditingSettings", which on the same api-version explicitly
- // exposes isManagedIdentityInUse and documents the "omit access key to use SAMI" path.
- // The service enforces this: omitting storageAccountAccessKey when storageEndpoint is
- // set fails with "DataSecurityInvalidUserSuppliedParameter: storageAccountAccessKey
- // parameter can not be null when storageEndpoint parameter is not null".
- //
- // References:
- // https://learn.microsoft.com/en-us/rest/api/sql/database-security-alert-policies/create-or-update?view=rest-sql-2025-01-01
- // https://learn.microsoft.com/en-us/rest/api/sql/database-blob-auditing-policies/create-or-update?view=rest-sql-2025-01-01
- //
- // Re-enable when either (a) the storage account in this test is recreated with shared
- // key access enabled, or (b) the SQL securityAlertPolicies resource adds first-class
- // Managed Identity support.
-
- // final String storageAccountName = generateRandomResourceName("sqlsa", 20);
- // StorageAccount storageAccount = storageManager.storageAccounts()
- // .define(storageAccountName)
- // .withRegion(DEFAULT_REGION)
- // .withExistingResourceGroup(rgName)
- // .disableSharedKeyAccess()
- // .create();
- // String accountKey = storageAccount.getKeys().get(0).value();
- // String blobEntrypoint = storageAccount.endPoints().primary().blob();
-
- // List disabledAlerts = Collections.singletonList("Sql_Injection");
-
- // sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
- // .withPolicyEnabled()
- // .withStorageEndpoint(blobEntrypoint)
- // .withStorageAccountAccessKey(accountKey)
- // .withAlertsFilter(disabledAlerts)
- // .create();
-
- // sqlDatabase.refresh();
-
- // SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
- // Assertions.assertNotNull(alertPolicy);
- // Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
- // Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
- // Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
+ String blobEntrypoint = storageAccount.endPoints().primary().blob();
+
+ List disabledAlerts = Collections.singletonList("Sql_Injection");
+
+ sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
+ .withPolicyEnabled()
+ .withStorageEndpoint(blobEntrypoint)
+ // use system-assigned MI
+ .withStorageAccountAccessKey(null)
+ .withAlertsFilter(disabledAlerts)
+ .create();
+
+ sqlDatabase.refresh();
+
+ SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
+ Assertions.assertNotNull(alertPolicy);
+ Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
+ Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
+ Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
// Test transparent data encryption settings.
TransparentDataEncryption transparentDataEncryption = sqlDatabase.getTransparentDataEncryption();
From 2b82ab33ee001b451b37b50efeecee6129c3e3c9 Mon Sep 17 00:00:00 2001
From: "Xiaofei Cao (from Dev Box)"
Date: Mon, 1 Jun 2026 13:31:29 +0800
Subject: [PATCH 10/13] minor fix, recording
---
sdk/sql/azure-resourcemanager-sql/assets.json | 2 +-
.../SqlDatabaseExportRequestImpl.java | 47 +++++++-----
.../SqlDatabaseImportRequestImpl.java | 12 ++-
.../sql/SqlServerOperationsTests.java | 73 +++++++++----------
4 files changed, 75 insertions(+), 59 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/assets.json b/sdk/sql/azure-resourcemanager-sql/assets.json
index 2a5656d0c5a3..da5a62b25723 100644
--- a/sdk/sql/azure-resourcemanager-sql/assets.json
+++ b/sdk/sql/azure-resourcemanager-sql/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/sql/azure-resourcemanager-sql",
- "Tag": "java/sql/azure-resourcemanager-sql_3f522469a6"
+ "Tag": "java/sql/azure-resourcemanager-sql_4145a7592c"
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
index 7f347ddf266a..b6d1dfadb82c 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java
@@ -67,28 +67,37 @@ public SqlDatabaseExportRequestImpl exportTo(String storageUri) {
private Mono getOrCreateStorageAccountContainer(final StorageAccount storageAccount,
final String containerName, final String fileName, final FunctionalTaskItem.Context context) {
final SqlDatabaseExportRequestImpl self = this;
+ self.inner.withStorageUri(
+ String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
+ BlobContainers blobContainers = this.sqlServerManager.storageManager().blobContainers();
+ Mono container
+ = blobContainers.getAsync(parent().resourceGroupName(), storageAccount.name(), containerName)
+ .map(blobContainer -> (Indexable) blobContainer)
+ .onErrorResume(error -> {
+ if (error instanceof ManagementException) {
+ if (((ManagementException) error).getResponse().getStatusCode() == 404) {
+ return blobContainers.defineContainer(containerName)
+ .withExistingStorageAccount(parent().resourceGroupName(), storageAccount.name())
+ .withPublicAccess(PublicAccess.NONE)
+ .createAsync()
+ .map(blobContainer -> (Indexable) blobContainer);
+ }
+ }
+ return Mono.error(error);
+ });
+
+ if (!storageAccount.isSharedKeyAccessAllowed()
+ // self.inner.storageKey could be set before, e.g. with managed identity ID
+ || !CoreUtils.isNullOrEmpty(self.inner.storageKey())) {
+ return container;
+ }
+
return storageAccount.getKeysAsync()
.flatMap(storageAccountKeys -> Mono.justOrEmpty(storageAccountKeys.stream().findFirst()))
.flatMap(storageAccountKey -> {
- self.inner.withStorageUri(
- String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
- if (storageAccount.isSharedKeyAccessAllowed() && CoreUtils.isNullOrEmpty(self.inner.storageKey())) {
- self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
- self.inner.withStorageKey(storageAccountKey.value());
- }
- BlobContainers blobContainers = this.sqlServerManager.storageManager().blobContainers();
- return blobContainers.getAsync(parent().resourceGroupName(), storageAccount.name(), containerName)
- .onErrorResume(error -> {
- if (error instanceof ManagementException) {
- if (((ManagementException) error).getResponse().getStatusCode() == 404) {
- return blobContainers.defineContainer(containerName)
- .withExistingStorageAccount(parent().resourceGroupName(), storageAccount.name())
- .withPublicAccess(PublicAccess.NONE)
- .createAsync();
- }
- }
- return Mono.error(error);
- });
+ self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
+ self.inner.withStorageKey(storageAccountKey.value());
+ return container;
});
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
index 28622af50773..289110bc740d 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
package com.azure.resourcemanager.sql.implementation;
+import com.azure.core.util.CoreUtils;
import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem;
import com.azure.resourcemanager.resources.fluentcore.model.Indexable;
import com.azure.resourcemanager.resources.fluentcore.model.implementation.ExecutableImpl;
@@ -55,11 +56,18 @@ public Mono executeWorkAsync() {
private Mono getOrCreateStorageAccountContainer(final StorageAccount storageAccount,
final String containerName, final String fileName, final FunctionalTaskItem.Context context) {
final SqlDatabaseImportRequestImpl self = this;
+ self.inner.withStorageUri(
+ String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
+
+ if (!storageAccount.isSharedKeyAccessAllowed()
+ // self.inner.storageKey could be set before, e.g. with managed identity ID
+ || !CoreUtils.isNullOrEmpty(self.inner.storageKey())) {
+ return context.voidMono();
+ }
+
return storageAccount.getKeysAsync()
.flatMap(storageAccountKeys -> Mono.justOrEmpty(storageAccountKeys.stream().findFirst()))
.flatMap(storageAccountKey -> {
- self.inner.withStorageUri(
- String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName));
self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY);
self.inner.withStorageKey(storageAccountKey.value());
return context.voidMono();
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 1d774104fbf4..18dfed06ed81 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -901,10 +901,7 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
}
@Test
- @Disabled("Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) "
- + "does not require storage account any more. "
- + "See Oury Ba's answer: https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp")
- public void canCRUDSqlDatabase() throws Exception {
+ public void canCRUDSqlDatabase() {
final String storageAccountName = generateRandomResourceName("sqlsa", 20);
AzureUser user = azureCliSignedInUser();
@@ -918,20 +915,6 @@ public void canCRUDSqlDatabase() throws Exception {
.withSystemAssignedManagedServiceIdentity()
.create();
- StorageAccount storageAccount = storageManager.storageAccounts()
- .define(storageAccountName)
- .withRegion(DEFAULT_REGION)
- .withExistingResourceGroup(rgName)
- .disableSharedKeyAccess()
- .create();
-
- authorizationManager.roleAssignments()
- .define(generateRandomUuid())
- .forObjectId(sqlServer.systemAssignedManagedServiceIdentityPrincipalId())
- .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR)
- .withResourceScope(storageAccount)
- .create();
-
Mono resourceStream = sqlServer.databases()
.define(SQL_DATABASE_NAME)
.withStandardEdition(SqlDatabaseStandardServiceObjective.S0)
@@ -942,25 +925,41 @@ public void canCRUDSqlDatabase() throws Exception {
validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME);
Assertions.assertTrue(sqlServer.databases().list().size() > 0);
- String blobEntrypoint = storageAccount.endPoints().primary().blob();
-
- List disabledAlerts = Collections.singletonList("Sql_Injection");
-
- sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
- .withPolicyEnabled()
- .withStorageEndpoint(blobEntrypoint)
- // use system-assigned MI
- .withStorageAccountAccessKey(null)
- .withAlertsFilter(disabledAlerts)
- .create();
-
- sqlDatabase.refresh();
-
- SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
- Assertions.assertNotNull(alertPolicy);
- Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
- Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
- Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
+ // Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) does not require
+ // storage account any more. See Oury Ba's answer:
+ // https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp
+ // StorageAccount storageAccount = storageManager.storageAccounts()
+ // .define(storageAccountName)
+ // .withRegion(DEFAULT_REGION)
+ // .withExistingResourceGroup(rgName)
+ // .disableSharedKeyAccess()
+ // .create();
+ //
+ // authorizationManager.roleAssignments()
+ // .define(generateRandomUuid())
+ // .forObjectId(sqlServer.systemAssignedManagedServiceIdentityPrincipalId())
+ // .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR)
+ // .withResourceScope(storageAccount)
+ // .create();
+ // String blobEntrypoint = storageAccount.endPoints().primary().blob();
+ //
+ // List disabledAlerts = Collections.singletonList("Sql_Injection");
+ //
+ // sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy"))
+ // .withPolicyEnabled()
+ // .withStorageEndpoint(blobEntrypoint)
+ // // use system-assigned MI
+ // .withStorageAccountAccessKey(null)
+ // .withAlertsFilter(disabledAlerts)
+ // .create();
+ //
+ // sqlDatabase.refresh();
+ //
+ // SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy();
+ // Assertions.assertNotNull(alertPolicy);
+ // Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState());
+ // Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts);
+ // Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy());
// Test transparent data encryption settings.
TransparentDataEncryption transparentDataEncryption = sqlDatabase.getTransparentDataEncryption();
From aa51b4834956dc215d30a410d49b9cdc643e4241 Mon Sep 17 00:00:00 2001
From: "Xiaofei Cao (from Dev Box)"
Date: Mon, 1 Jun 2026 14:15:03 +0800
Subject: [PATCH 11/13] imports
---
.../azure/resourcemanager/sql/SqlServerOperationsTests.java | 4 ----
1 file changed, 4 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 18dfed06ed81..b5003fe8570f 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -33,8 +33,6 @@
import com.azure.resourcemanager.sql.models.ReplicationLink;
import com.azure.resourcemanager.sql.models.ReplicationState;
import com.azure.resourcemanager.sql.models.SampleName;
-import com.azure.resourcemanager.sql.models.SecurityAlertPolicyName;
-import com.azure.resourcemanager.sql.models.SecurityAlertPolicyState;
import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag;
import com.azure.resourcemanager.sql.models.ServiceObjectiveName;
import com.azure.resourcemanager.sql.models.Sku;
@@ -44,7 +42,6 @@
import com.azure.resourcemanager.sql.models.SqlDatabaseImportExportResponse;
import com.azure.resourcemanager.sql.models.SqlDatabasePremiumServiceObjective;
import com.azure.resourcemanager.sql.models.SqlDatabaseStandardServiceObjective;
-import com.azure.resourcemanager.sql.models.SqlDatabaseThreatDetectionPolicy;
import com.azure.resourcemanager.sql.models.SqlElasticPool;
import com.azure.resourcemanager.sql.models.SqlElasticPoolBasicEDTUs;
import com.azure.resourcemanager.sql.models.SqlFailoverGroup;
@@ -61,7 +58,6 @@
import com.azure.resourcemanager.sql.models.TransparentDataEncryptionState;
import com.azure.resourcemanager.storage.models.StorageAccount;
import com.azure.resourcemanager.test.model.AzureUser;
-
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
From 67fd6e0dc82f79c0e69661c1d6f4d2c99b364cb0 Mon Sep 17 00:00:00 2001
From: "Xiaofei Cao (from Dev Box)"
Date: Tue, 2 Jun 2026 17:30:13 +0800
Subject: [PATCH 12/13] address comments
---
.../SqlDatabaseImportRequestImpl.java | 4 ++
.../sql/implementation/SqlServerImpl.java | 43 +++++++++++--------
.../sql/models/SqlDatabaseImportRequest.java | 6 +--
.../sql/SqlServerOperationsTests.java | 33 +++++++++++++-
4 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
index 289110bc740d..ad9c4a23f888 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java
@@ -132,6 +132,10 @@ public SqlDatabaseImportRequestImpl withManagedIdentity(String managedIdentityRe
this.inner.withAdministratorLogin(managedIdentityResourceId);
// No administrator password is required for managed identity authentication.
this.inner.withAdministratorLoginPassword(null);
+
+ // Use the same MI for storage account access.
+ this.inner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY);
+ this.inner.withStorageKey(managedIdentityResourceId);
return this;
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index 3998dfe1ea9b..56b9e5b072cb 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -528,33 +528,42 @@ public SqlServerImpl withoutDatabase(String databaseName) {
@Override
public SqlServerImpl withSystemAssignedManagedServiceIdentity() {
- this.innerModel().withIdentity(new ResourceIdentity().withType(IdentityType.SYSTEM_ASSIGNED));
+ initIdentity(IdentityType.SYSTEM_ASSIGNED);
return this;
}
@Override
public SqlServerImpl withPrimaryUserAssignedManagedServiceIdentity(String identityResourceId) {
Objects.requireNonNull(identityResourceId, "'identityResourceId' cannot be null.");
+ initIdentity(IdentityType.USER_ASSIGNED);
+ this.innerModel().identity().userAssignedIdentities().put(identityResourceId, new UserIdentity());
+ this.innerModel().withPrimaryUserAssignedIdentityId(identityResourceId);
+ return this;
+ }
+
+ /**
+ * Initializes or merges the identity on the inner model so that the requested
+ * {@code desiredType} is present. When both SYSTEM_ASSIGNED and USER_ASSIGNED
+ * are requested (in any order), the resulting type is SYSTEM_ASSIGNED_USER_ASSIGNED.
+ * Existing user-assigned identities are preserved across calls.
+ */
+ private void initIdentity(IdentityType desiredType) {
ResourceIdentity existing = this.innerModel().identity();
- IdentityType type;
- Map identities;
if (existing == null || existing.type() == null || existing.type() == IdentityType.NONE) {
- type = IdentityType.USER_ASSIGNED;
- identities = new HashMap<>();
- } else if (existing.type() == IdentityType.SYSTEM_ASSIGNED) {
- type = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED;
- identities = new HashMap<>();
+ this.innerModel()
+ .withIdentity(new ResourceIdentity().withType(desiredType).withUserAssignedIdentities(new HashMap<>()));
+ } else if (existing.type() == desiredType || existing.type() == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED) {
+ // Already has the desired type (or already combined) — nothing to change.
+ if (existing.userAssignedIdentities() == null) {
+ existing.withUserAssignedIdentities(new HashMap<>());
+ }
} else {
- type = existing.type();
- identities = existing.userAssignedIdentities() == null
- ? new HashMap<>()
- : new HashMap<>(existing.userAssignedIdentities());
+ // One of SYSTEM_ASSIGNED + USER_ASSIGNED → combine
+ existing.withType(IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED);
+ if (existing.userAssignedIdentities() == null) {
+ existing.withUserAssignedIdentities(new HashMap<>());
+ }
}
- identities.put(identityResourceId, new UserIdentity());
- this.innerModel()
- .withIdentity(new ResourceIdentity().withType(type).withUserAssignedIdentities(identities))
- .withPrimaryUserAssignedIdentityId(identityResourceId);
- return this;
}
@Override
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
index c684bbeb6700..356b4baaa732 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java
@@ -95,11 +95,11 @@ interface WithAuthenticationTypeAndLoginPassword {
* Contributor}), and it must be mapped to a database user with the required privileges. When this method
* is used, no administrator password is sent to the service.
*
- * This method is for user-assigned managed identity. To use the server's system-assigned managed
- * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in
- * user's object ID is not a managed identity and cannot be used here.
+ * This method is for user-assigned managed identity. System-assigned managed identity is not supported.
+ * See Limitations
*
* @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use
+ * for both SQL and storage access
* @return next definition stage
*/
default SqlDatabaseImportRequest.DefinitionStages.WithExecute
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index b5003fe8570f..9448a79d47be 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -26,6 +26,7 @@
import com.azure.resourcemanager.sql.models.ElasticPoolEdition;
import com.azure.resourcemanager.sql.models.ElasticPoolSku;
import com.azure.resourcemanager.sql.models.FailoverGroupReplicationRole;
+import com.azure.resourcemanager.sql.models.IdentityType;
import com.azure.resourcemanager.sql.models.PrincipalType;
import com.azure.resourcemanager.sql.models.ReadOnlyEndpointFailoverPolicy;
import com.azure.resourcemanager.sql.models.ReadWriteEndpointFailoverPolicy;
@@ -128,6 +129,14 @@ public void canCRUDSqlSyncMember() throws Exception {
SqlDatabase dbSync = sqlPrimaryServer.databases().get(dbSyncName);
SqlDatabase dbMember = sqlPrimaryServer.databases().get(dbMemberName);
+ // SQL Data Sync requires SQL authentication for connections to the hub and member databases. Microsoft Entra (Azure AD)
+ // authentication isn't supported by SQL Data Sync. Because SQL authentication relies on static passwords,
+ // it doesn't benefit from modern protections like multifactor authentication (MFA), Conditional Access, or managed identities.
+ // This can increase exposure for the entire SQL instance to credential theft, brute‑force attacks, and operational
+ // overhead for password rotation and policy enforcement. Where possible, prefer solutions that support Microsoft
+ // Entra authentication or managed identities. Since SQL Data Sync is scheduled for retirement, migrate to an
+ // alternative that aligns with your organization's security standards.
+ // See https://learn.microsoft.com/azure/azure-sql/database/sql-data-sync-data-sql-server-sql-database?view=azuresql
SqlSyncGroup sqlSyncGroup = dbSync.syncGroups()
.define(syncGroupName)
.withSyncDatabaseId(dbSource.id())
@@ -195,6 +204,14 @@ public void canCRUDSqlSyncGroup() throws Exception {
SqlDatabase dbSource = sqlPrimaryServer.databases().get(dbName);
SqlDatabase dbSync = sqlPrimaryServer.databases().get(dbSyncName);
+ // SQL Data Sync requires SQL authentication for connections to the hub and member databases. Microsoft Entra (Azure AD)
+ // authentication isn't supported by SQL Data Sync. Because SQL authentication relies on static passwords,
+ // it doesn't benefit from modern protections like multifactor authentication (MFA), Conditional Access, or managed identities.
+ // This can increase exposure for the entire SQL instance to credential theft, brute‑force attacks, and operational
+ // overhead for password rotation and policy enforcement. Where possible, prefer solutions that support Microsoft
+ // Entra authentication or managed identities. Since SQL Data Sync is scheduled for retirement, migrate to an
+ // alternative that aligns with your organization's security standards.
+ // See https://learn.microsoft.com/azure/azure-sql/database/sql-data-sync-data-sql-server-sql-database?view=azuresql
SqlSyncGroup sqlSyncGroup = dbSync.syncGroups()
.define(syncGroupName)
.withSyncDatabaseId(dbSource.id())
@@ -611,9 +628,11 @@ public void canCRUDSqlServerWithImportDatabase() {
.withAzureActiveDirectoryOnlyAuthentication()
.withExternalActiveDirectoryAdministrator(uamiName, uami.principalId(), PrincipalType.APPLICATION)
.withPrimaryUserAssignedManagedServiceIdentity(uami.id())
+ .withSystemAssignedManagedServiceIdentity()
.create();
Assertions.assertTrue(sqlServer.isAzureActiveDirectoryOnlyAuthenticationEnabled());
+ Assertions.assertSame(IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED, sqlServer.managedServiceIdentityType());
SqlDatabase dbFromSample = sqlServer.databases()
.define("db-from-sample")
@@ -641,6 +660,18 @@ public void canCRUDSqlServerWithImportDatabase() {
Assertions.assertNotNull(dbFromImport);
Assertions.assertEquals("ep1", dbFromImport.elasticPoolName());
+ // Test importBacpac with managed identity on an existing empty database
+ SqlDatabase dbForImportBacpac
+ = sqlServer.databases().define("db-for-import-bacpac").withBasicEdition().create();
+ Assertions.assertNotNull(dbForImportBacpac);
+
+ SqlDatabaseImportExportResponse importBacpacResponse
+ = dbForImportBacpac.importBacpac(storageAccount, "from-sample", "dbfromsample.bacpac")
+ .withManagedIdentity(uami.id())
+ .execute();
+ Assertions.assertNotNull(importBacpacResponse);
+
+ dbForImportBacpac.delete();
dbFromImport.delete();
dbFromSample.delete();
sqlServer.elasticPools().delete("ep1");
@@ -898,7 +929,6 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception {
@Test
public void canCRUDSqlDatabase() {
- final String storageAccountName = generateRandomResourceName("sqlsa", 20);
AzureUser user = azureCliSignedInUser();
// Create
@@ -921,6 +951,7 @@ public void canCRUDSqlDatabase() {
validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME);
Assertions.assertTrue(sqlServer.databases().list().size() > 0);
+// final String storageAccountName = generateRandomResourceName("sqlsa", 20);
// Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) does not require
// storage account any more. See Oury Ba's answer:
// https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp
From 82c846a737658ac0bc2959e139deaf42365639ed Mon Sep 17 00:00:00 2001
From: "Xiaofei Cao (from Dev Box)"
Date: Tue, 2 Jun 2026 20:25:03 +0800
Subject: [PATCH 13/13] record
---
sdk/sql/azure-resourcemanager-sql/assets.json | 2 +-
.../sql/implementation/SqlServerImpl.java | 17 +++++++++++------
.../sql/SqlServerOperationsTests.java | 2 +-
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/sdk/sql/azure-resourcemanager-sql/assets.json b/sdk/sql/azure-resourcemanager-sql/assets.json
index da5a62b25723..ac2251c2df50 100644
--- a/sdk/sql/azure-resourcemanager-sql/assets.json
+++ b/sdk/sql/azure-resourcemanager-sql/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/sql/azure-resourcemanager-sql",
- "Tag": "java/sql/azure-resourcemanager-sql_4145a7592c"
+ "Tag": "java/sql/azure-resourcemanager-sql_a84e1b066f"
}
diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
index 56b9e5b072cb..927f72ecd469 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java
@@ -21,11 +21,12 @@
import com.azure.resourcemanager.sql.fluent.models.ServerUsageInner;
import com.azure.resourcemanager.sql.models.AdministratorName;
import com.azure.resourcemanager.sql.models.AdministratorType;
-import com.azure.resourcemanager.sql.models.PrincipalType;
import com.azure.resourcemanager.sql.models.IdentityType;
+import com.azure.resourcemanager.sql.models.PrincipalType;
import com.azure.resourcemanager.sql.models.ResourceIdentity;
import com.azure.resourcemanager.sql.models.ServerExternalAdministrator;
import com.azure.resourcemanager.sql.models.ServerMetric;
+import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag;
import com.azure.resourcemanager.sql.models.SqlDatabaseOperations;
import com.azure.resourcemanager.sql.models.SqlElasticPoolOperations;
import com.azure.resourcemanager.sql.models.SqlEncryptionProtectorOperations;
@@ -34,7 +35,6 @@
import com.azure.resourcemanager.sql.models.SqlFirewallRuleOperations;
import com.azure.resourcemanager.sql.models.SqlRestorableDroppedDatabase;
import com.azure.resourcemanager.sql.models.SqlServer;
-import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag;
import com.azure.resourcemanager.sql.models.SqlServerAutomaticTuning;
import com.azure.resourcemanager.sql.models.SqlServerDnsAliasOperations;
import com.azure.resourcemanager.sql.models.SqlServerKeyOperations;
@@ -48,7 +48,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -550,11 +549,17 @@ public SqlServerImpl withPrimaryUserAssignedManagedServiceIdentity(String identi
private void initIdentity(IdentityType desiredType) {
ResourceIdentity existing = this.innerModel().identity();
if (existing == null || existing.type() == null || existing.type() == IdentityType.NONE) {
- this.innerModel()
- .withIdentity(new ResourceIdentity().withType(desiredType).withUserAssignedIdentities(new HashMap<>()));
+ ResourceIdentity identity = new ResourceIdentity().withType(desiredType);
+ if (desiredType == IdentityType.USER_ASSIGNED
+ || desiredType == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED) {
+ identity.withUserAssignedIdentities(new HashMap<>());
+ }
+ this.innerModel().withIdentity(identity);
} else if (existing.type() == desiredType || existing.type() == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED) {
// Already has the desired type (or already combined) — nothing to change.
- if (existing.userAssignedIdentities() == null) {
+ if (existing.userAssignedIdentities() == null
+ && (existing.type() == IdentityType.USER_ASSIGNED
+ || existing.type() == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED)) {
existing.withUserAssignedIdentities(new HashMap<>());
}
} else {
diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
index 9448a79d47be..c8dadeb79038 100644
--- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
+++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java
@@ -951,7 +951,7 @@ public void canCRUDSqlDatabase() {
validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME);
Assertions.assertTrue(sqlServer.databases().list().size() > 0);
-// final String storageAccountName = generateRandomResourceName("sqlsa", 20);
+ // final String storageAccountName = generateRandomResourceName("sqlsa", 20);
// Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) does not require
// storage account any more. See Oury Ba's answer:
// https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp