Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions sdk/spring/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

### Bugs Fixed

### Spring Cloud Azure Autoconfigure

This section includes changes in `spring-cloud-azure-autoconfigure` module.

#### Bugs Fixed

Comment thread
rujche marked this conversation as resolved.
- Fixed `azure.scopes` using wrong default value for Azure China and Azure US Government when `spring.cloud.azure.profile.cloud-type` is set to `azure_china` or `azure_us_government`. The scopes are now correctly derived from the merged cloud type. ([#47096](https://github.com/Azure/azure-sdk-for-java/issues/47096))
Comment thread
rujche marked this conversation as resolved.
Outdated

### Other Changes

## 7.2.0 (2026-04-17)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

package com.azure.spring.cloud.autoconfigure.implementation.passwordless.properties;

import com.azure.spring.cloud.core.implementation.properties.AzurePasswordlessPropertiesMapping;
import com.azure.spring.cloud.core.properties.PasswordlessProperties;
import com.azure.spring.cloud.core.properties.authentication.TokenCredentialProperties;
import com.azure.spring.cloud.core.properties.profile.AzureProfileProperties;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* Configuration properties for passwordless connections with Azure Database.
Expand Down Expand Up @@ -43,11 +45,22 @@ public class AzureJdbcPasswordlessProperties implements PasswordlessProperties {

/**
* Get the scopes required for the access token.
* Returns null if scopes have not been explicitly set, so that the default
* scopes can be computed from the merged cloud type after property merging.
*
* @return scopes required for the access token
* @return scopes required for the access token, or null if not explicitly set
*/
@Override
public String getScopes() {
return this.scopes;
}

/**
* Get the effective scopes, returning default cloud-specific scopes when not explicitly set.
*
* @return scopes required for the access token
*/
public String getEffectiveScopes() {
return this.scopes == null ? getDefaultScopes() : this.scopes;
}

Expand Down Expand Up @@ -120,4 +133,25 @@ public TokenCredentialProperties getCredential() {
public void setCredential(TokenCredentialProperties credential) {
this.credential = credential;
}

/**
* Convert {@link AzureJdbcPasswordlessProperties} to {@link Properties}.
* Uses the effective scopes (cloud-type-aware) rather than the raw scopes value,
* ensuring the correct default scope is used when scopes have not been explicitly set.
*
* @return converted {@link Properties} instance
*/
@Override
public Properties toPasswordlessProperties() {
Properties properties = new Properties();
for (AzurePasswordlessPropertiesMapping m : AzurePasswordlessPropertiesMapping.values()) {
String value = m == AzurePasswordlessPropertiesMapping.SCOPES
? getEffectiveScopes()
: m.getGetter().apply(this);
if (value != null) {
m.getSetter().accept(properties, value);
}
}
return properties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ class JdbcPropertiesBeanPostProcessorTest {
private static final String POSTGRESQL_CONNECTION_STRING = "jdbc:postgresql://host/database?enableSwitch1&property1=value1";
private static final String PASSWORD = "password";
private static final String US_AUTHORITY_HOST_STRING = AuthProperty.AUTHORITY_HOST.getPropertyKey() + "=" + "https://login.microsoftonline.us/";
private static final String CHINA_AUTHORITY_HOST_STRING = AuthProperty.AUTHORITY_HOST.getPropertyKey() + "=" + "https://login.chinacloudapi.cn/";
public static final String PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING = AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.getPropertyKey() + "=";
private static final String POSTGRESQL_ASSUME_MIN_SERVER_VERSION = POSTGRESQL_PROPERTY_NAME_ASSUME_MIN_SERVER_VERSION + "="
+ POSTGRESQL_PROPERTY_VALUE_ASSUME_MIN_SERVER_VERSION;
protected static final String MANAGED_IDENTITY_ENABLED_DEFAULT = "azure.managedIdentityEnabled=false";
protected static final String SCOPES_DEFAULT = "azure.scopes=https://ossrdbms-aad.database.windows.net/.default";
private static final String SCOPES_CHINA = "azure.scopes=https://ossrdbms-aad.database.chinacloudapi.cn/.default";
private static final String SCOPES_US_GOVERNMENT = "azure.scopes=https://ossrdbms-aad.database.usgovcloudapi.net/.default";
Comment thread
rujche marked this conversation as resolved.
Outdated
private static final String DEFAULT_PASSWORDLESS_PROPERTIES_SUFFIX = ".spring.datasource.azure";
private MockEnvironment mockEnvironment;

Expand Down Expand Up @@ -153,14 +156,39 @@ void shouldGetCloudTypeFromAzureUsGov() {
DatabaseType.MYSQL,
MYSQL_CONNECTION_STRING,
MANAGED_IDENTITY_ENABLED_DEFAULT,
SCOPES_DEFAULT,
SCOPES_US_GOVERNMENT,
MYSQL_USER_AGENT,
US_AUTHORITY_HOST_STRING
);

assertEquals(expectedJdbcUrl, dataSourceProperties.getUrl());
}

@Test
void shouldGetCorrectScopeFromAzureChina() {
AzureProfileConfigurationProperties azureProfileConfigurationProperties = new AzureProfileConfigurationProperties();
azureProfileConfigurationProperties.setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_CHINA);
when(this.azureGlobalProperties.getProfile()).thenReturn(azureProfileConfigurationProperties);

DataSourceProperties dataSourceProperties = new DataSourceProperties();
dataSourceProperties.setUrl(POSTGRESQL_CONNECTION_STRING);

this.mockEnvironment.setProperty("spring.datasource.azure.passwordless-enabled", "true");
this.jdbcPropertiesBeanPostProcessor.postProcessBeforeInitialization(dataSourceProperties, "dataSourceProperties");

String expectedJdbcUrl = enhanceJdbcUrl(
DatabaseType.POSTGRESQL,
POSTGRESQL_CONNECTION_STRING,
MANAGED_IDENTITY_ENABLED_DEFAULT,
SCOPES_CHINA,
APPLICATION_NAME.getName() + "=" + AzureSpringIdentifier.AZURE_SPRING_POSTGRESQL_OAUTH,
POSTGRESQL_ASSUME_MIN_SERVER_VERSION,
CHINA_AUTHORITY_HOST_STRING
);

assertEquals(expectedJdbcUrl, dataSourceProperties.getUrl());
}

@Test
void mySqlUserAgentShouldConfigureIfConnectionAttributesIsEmpty() {
DataSourceProperties dataSourceProperties = new DataSourceProperties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties;
import com.azure.spring.cloud.autoconfigure.implementation.passwordless.properties.AzureJdbcPasswordlessProperties;
import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils;
import com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider;
import com.azure.identity.extensions.implementation.enums.AuthProperty;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MergeAzureCommonPropertiesTest {
Expand Down Expand Up @@ -116,4 +119,43 @@ void testGetPropertiesFromGlobalAndPasswordlessProperties() {
assertEquals("sub", result.getProfile().getSubscriptionId());
assertEquals("global-tenant-id", result.getProfile().getTenantId());
}

@Test
void testJdbcPropertiesGetCorrectScopeFromChinaCloudTypeInGlobalProperties() {
AzureGlobalProperties globalProperties = new AzureGlobalProperties();
globalProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_CHINA);

AzureJdbcPasswordlessProperties jdbcProperties = new AzureJdbcPasswordlessProperties();
// User has not explicitly set scopes

AzureJdbcPasswordlessProperties result = new AzureJdbcPasswordlessProperties();
AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, jdbcProperties, result);

// scopes field should be null (not explicitly set)
assertNull(result.getScopes());
// effective scopes should use the merged cloud type (AZURE_CHINA)
assertEquals("https://ossrdbms-aad.database.chinacloudapi.cn/.default", result.getEffectiveScopes());
// toPasswordlessProperties should include the correct cloud-type-aware scope
assertEquals("https://ossrdbms-aad.database.chinacloudapi.cn/.default",
result.toPasswordlessProperties().getProperty(AuthProperty.SCOPES.getPropertyKey()));
assertEquals(AzureProfileOptionsProvider.CloudType.AZURE_CHINA, result.getProfile().getCloudType());
Comment thread
rujche marked this conversation as resolved.
}

@Test
void testJdbcPropertiesExplicitScopesOverridesDefault() {
AzureGlobalProperties globalProperties = new AzureGlobalProperties();
globalProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_CHINA);

AzureJdbcPasswordlessProperties jdbcProperties = new AzureJdbcPasswordlessProperties();
jdbcProperties.setScopes("https://custom-scope/.default");

AzureJdbcPasswordlessProperties result = new AzureJdbcPasswordlessProperties();
AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, jdbcProperties, result);

// Explicit scopes should be preserved
assertEquals("https://custom-scope/.default", result.getScopes());
assertEquals("https://custom-scope/.default", result.getEffectiveScopes());
assertEquals("https://custom-scope/.default",
result.toPasswordlessProperties().getProperty(AuthProperty.SCOPES.getPropertyKey()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public static <T extends PasswordlessProperties> void copyAzureCommonPropertiesI
copyPropertiesIgnoreNull(source.getProfile().getEnvironment(), target.getProfile().getEnvironment());
copyPropertiesIgnoreNull(source.getCredential(), target.getCredential());

target.setScopes(source.getScopes());
String[] scopes = source.getScopes();
Comment thread
rujche marked this conversation as resolved.
Outdated
if (scopes != null) {
target.setScopes(scopes);
}
target.setPasswordlessEnabled(source.isPasswordlessEnabled());
Comment thread
rujche marked this conversation as resolved.
}

Expand Down
Loading