Skip to content

Commit

Permalink
Fix Passwordless feature does't set the cloud-type correctly and pipe…
Browse files Browse the repository at this point in the history
…line fail (Azure#31600)

Fix Passwordless feature does't set the cloud-type correctly and pipeline fail
  • Loading branch information
backwind1233 authored Oct 27, 2022
1 parent d87a764 commit 644f835
Show file tree
Hide file tree
Showing 23 changed files with 257 additions and 74 deletions.
3 changes: 3 additions & 0 deletions eng/versioning/version_client.txt
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,9 @@ com.azure.tools:azure-sdk-build-tool;1.0.0-beta.1;1.0.0-beta.2
# In the pom, the version update tag after the version should name the unreleased package and the dependency version:
# <!-- {x-version-update;unreleased_com.azure:azure-core;dependency} -->
unreleased_com.azure:azure-identity;1.7.0-beta.2
unreleased_com.azure:azure-identity-providers-core;1.0.0-beta.2
unreleased_com.azure:azure-identity-providers-jdbc-mysql;1.0.0-beta.2
unreleased_com.azure:azure-identity-providers-jdbc-postgresql;1.0.0-beta.2

# Released Beta dependencies: Copy the entry from above, prepend "beta_", remove the current
# version and set the version to the released beta. Released beta dependencies are only valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@
package com.azure.identity.providers.jdbc.implementation.token;


import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.AzureAuthorityHosts;
import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;

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


/**
* Contains details of a request to get a token.
*/
public class AccessTokenResolverOptions {

private static final ClientLogger LOGGER = new ClientLogger(AccessTokenResolverOptions.class);
private static final Map<String, String> OSS_RDBMS_SCOPE_MAP = new HashMap<String, String>() {
{
put(AzureAuthorityHosts.AZURE_PUBLIC_CLOUD, "https://ossrdbms-aad.database.windows.net/.default");
put(AzureAuthorityHosts.AZURE_CHINA, "https://ossrdbms-aad.database.chinacloudapi.cn/.default");
put(AzureAuthorityHosts.AZURE_GERMANY, "https://ossrdbms-aad.database.cloudapi.de/.default");
put(AzureAuthorityHosts.AZURE_GOVERNMENT, "https://ossrdbms-aad.database.usgovcloudapi.net/.default");
}
};
private String claims;
private String tenantId;
private String[] scopes;
Expand All @@ -27,7 +37,26 @@ public AccessTokenResolverOptions(Properties properties) {
this.claims = AuthProperty.CLAIMS.get(properties);

String scopeProperty = AuthProperty.SCOPES.get(properties);
this.scopes = scopeProperty == null ? new String[0] : scopeProperty.split(",");
if (scopeProperty == null) {
scopeProperty = getDefaultScope(properties);
}
this.scopes = scopeProperty.split(",");
}

private String getDefaultScope(Properties properties) {
String ossrdbmsScope = OSS_RDBMS_SCOPE_MAP.get(AzureAuthorityHosts.AZURE_PUBLIC_CLOUD);
String authorityHost = AuthProperty.AUTHORITY_HOST.get(properties);
if (AzureAuthorityHosts.AZURE_PUBLIC_CLOUD.startsWith(authorityHost)) {
ossrdbmsScope = OSS_RDBMS_SCOPE_MAP.get(AzureAuthorityHosts.AZURE_PUBLIC_CLOUD);
} else if (AzureAuthorityHosts.AZURE_CHINA.startsWith(authorityHost)) {
ossrdbmsScope = OSS_RDBMS_SCOPE_MAP.get(AzureAuthorityHosts.AZURE_CHINA);
} else if (AzureAuthorityHosts.AZURE_GERMANY.startsWith(authorityHost)) {
ossrdbmsScope = OSS_RDBMS_SCOPE_MAP.get(AzureAuthorityHosts.AZURE_GERMANY);
} else if (AzureAuthorityHosts.AZURE_GOVERNMENT.startsWith(authorityHost)) {
ossrdbmsScope = OSS_RDBMS_SCOPE_MAP.get(AzureAuthorityHosts.AZURE_GOVERNMENT);
}
LOGGER.info("Ossrdbms scope set to {}.", ossrdbmsScope);
return ossrdbmsScope;
}

public String getClaims() {
Expand All @@ -47,7 +76,7 @@ public void setTenantId(String tenantId) {
}

public String[] getScopes() {
return scopes.clone();
return scopes == null ? null : scopes.clone();
}

public void setScopes(String[] scopes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity.providers.jdbc.implementation.token;

import com.azure.identity.AzureAuthorityHosts;
import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Properties;
import java.util.stream.Stream;

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

class AccessTokenResolverOptionsTest {

@Test
void testDefaultConstructor() {
AccessTokenResolverOptions accessTokenResolverOptions = new AccessTokenResolverOptions();
assertNull(accessTokenResolverOptions.getClaims());
assertNull(accessTokenResolverOptions.getScopes());
assertNull(accessTokenResolverOptions.getTenantId());
}

@Test
void testConstructorWithProperties() {
Properties properties = new Properties();
properties.setProperty(AuthProperty.TENANT_ID.getPropertyKey(), "fake-tenant-id");
properties.setProperty(AuthProperty.CLAIMS.getPropertyKey(), "fake-claims");
properties.setProperty(AuthProperty.SCOPES.getPropertyKey(), "fake-scopes");

AccessTokenResolverOptions accessTokenResolverOptions = new AccessTokenResolverOptions(properties);
assertEquals("fake-claims", accessTokenResolverOptions.getClaims());
assertArrayEquals(new String[]{"fake-scopes"}, accessTokenResolverOptions.getScopes());
assertEquals("fake-tenant-id", accessTokenResolverOptions.getTenantId());
}

@ParameterizedTest
@MethodSource("provideAuthorityHostScopeMap")
void testDifferentAuthorties(String authorityHost, String scope) {
Properties properties = new Properties();
AuthProperty.AUTHORITY_HOST.setProperty(properties, authorityHost);
AccessTokenResolverOptions accessTokenResolverOptions = new AccessTokenResolverOptions(properties);
assertArrayEquals(new String[]{scope}, accessTokenResolverOptions.getScopes());
}

private static Stream<Arguments> provideAuthorityHostScopeMap() {
return Stream.of(
Arguments.of(null, "https://ossrdbms-aad.database.windows.net/.default"),
Arguments.of("", "https://ossrdbms-aad.database.windows.net/.default"),
Arguments.of(AzureAuthorityHosts.AZURE_PUBLIC_CLOUD, "https://ossrdbms-aad.database.windows.net/.default"),
Arguments.of(AzureAuthorityHosts.AZURE_CHINA, "https://ossrdbms-aad.database.chinacloudapi.cn/.default"),
Arguments.of(AzureAuthorityHosts.AZURE_GERMANY, "https://ossrdbms-aad.database.cloudapi.de/.default"),
Arguments.of(AzureAuthorityHosts.AZURE_GOVERNMENT, "https://ossrdbms-aad.database.usgovcloudapi.net/.default")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package com.azure.identity.providers.mysql;

import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.identity.providers.jdbc.implementation.template.AzureAuthenticationTemplate;
import com.mysql.cj.callback.MysqlCallbackHandler;
import com.mysql.cj.protocol.AuthenticationPlugin;
Expand All @@ -20,7 +19,6 @@
*/
public class AzureIdentityMysqlAuthenticationPlugin implements AuthenticationPlugin<NativePacketPayload> {
private static final ClientLogger LOGGER = new ClientLogger(AzureIdentityMysqlAuthenticationPlugin.class);
private static final String OSSRDBMS_SCOPE = "https://ossrdbms-aad.database.windows.net/.default";
private static final String PLUGIN_NAME = "mysql_clear_password";

private final AzureAuthenticationTemplate azureAuthenticationTemplate;
Expand Down Expand Up @@ -60,7 +58,6 @@ public String getProtocolPluginName() {
public void init(Protocol<NativePacketPayload> protocol) {
this.protocol = protocol;
Properties properties = protocol.getPropertySet().exposeAsProperties();
AuthProperty.SCOPES.setProperty(properties, OSSRDBMS_SCOPE);
azureAuthenticationTemplate.init(properties);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package com.azure.identity.providers.mysql;

import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.identity.providers.jdbc.implementation.template.AzureAuthenticationTemplate;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.protocol.Protocol;
Expand All @@ -24,7 +23,6 @@
import static org.mockito.Mockito.when;

class AzureIdentityMysqlAuthenticationPluginTest {
protected static final String OSSRDBMS_SCOPES = "https://ossrdbms-aad.database.windows.net/.default";
private static final String CLEAR_PASSWORD = "mysql_clear_password";

Protocol<NativePacketPayload> protocol;
Expand Down Expand Up @@ -52,14 +50,6 @@ void testPluginName() {
assertEquals(CLEAR_PASSWORD, protocolPluginName);
}

@Test
void tokenAudienceShouldConfig() {
AzureAuthenticationTemplate template = new AzureAuthenticationTemplate();
AzureIdentityMysqlAuthenticationPlugin plugin = new AzureIdentityMysqlAuthenticationPlugin(template);
plugin.init(protocol);
assertEquals(OSSRDBMS_SCOPES, properties.getProperty(AuthProperty.SCOPES.getPropertyKey()));
}

@Test
void testRequiresConfidentiality() {
AzureIdentityMysqlAuthenticationPlugin plugin = new AzureIdentityMysqlAuthenticationPlugin();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package com.azure.identity.providers.postgresql;

import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.identity.providers.jdbc.implementation.template.AzureAuthenticationTemplate;
import org.postgresql.plugin.AuthenticationPlugin;
import org.postgresql.plugin.AuthenticationRequestType;
Expand All @@ -18,8 +17,6 @@
*/
public class AzureIdentityPostgresqlAuthenticationPlugin implements AuthenticationPlugin {

private static final String OSSRDBMS_SCOPE = "https://ossrdbms-aad.database.windows.net/.default";

private final AzureAuthenticationTemplate azureAuthenticationTemplate;

/**
Expand All @@ -29,13 +26,11 @@ public class AzureIdentityPostgresqlAuthenticationPlugin implements Authenticati
*/
public AzureIdentityPostgresqlAuthenticationPlugin(Properties properties) {
this.azureAuthenticationTemplate = new AzureAuthenticationTemplate();
AuthProperty.SCOPES.setProperty(properties, OSSRDBMS_SCOPE);
azureAuthenticationTemplate.init(properties);
}

AzureIdentityPostgresqlAuthenticationPlugin(AzureAuthenticationTemplate azureAuthenticationTemplate, Properties properties) {
this.azureAuthenticationTemplate = azureAuthenticationTemplate;
AuthProperty.SCOPES.setProperty(properties, OSSRDBMS_SCOPE);
this.azureAuthenticationTemplate.init(properties);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package com.azure.identity.providers.postgresql;

import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.identity.providers.jdbc.implementation.template.AzureAuthenticationTemplate;
import org.junit.jupiter.api.Test;
import org.postgresql.plugin.AuthenticationRequestType;
Expand All @@ -19,7 +18,6 @@


class AzureIdentityPostgresqlAuthenticationPluginTest {
private static final String OSSRDBMS_SCOPES = "https://ossrdbms-aad.database.windows.net/.default";

@Test
void testTokenCredentialProvider() {
Expand All @@ -28,13 +26,6 @@ void testTokenCredentialProvider() {
assertNotNull(plugin.getAzureAuthenticationTemplate());
}

@Test
protected void tokenAudienceShouldConfig() {
Properties properties = new Properties();
new AzureIdentityPostgresqlAuthenticationPlugin(properties);
assertEquals(OSSRDBMS_SCOPES, properties.getProperty(AuthProperty.SCOPES.getPropertyKey()));
}

@Test
void shouldThrowPSQLException() {
Properties properties = new Properties();
Expand Down
4 changes: 2 additions & 2 deletions sdk/spring/spring-cloud-azure-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,14 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity-providers-jdbc-mysql</artifactId>
<version>1.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-identity-providers-jdbc-mysql;dependency} -->
<version>1.0.0-beta.2</version> <!-- {x-version-update;unreleased_com.azure:azure-identity-providers-jdbc-mysql;dependency} -->
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity-providers-jdbc-postgresql</artifactId>
<version>1.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-identity-providers-jdbc-postgresql;dependency} -->
<version>1.0.0-beta.2</version> <!-- {x-version-update;unreleased_com.azure:azure-identity-providers-jdbc-postgresql;dependency} -->
<optional>true</optional>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.azure.core.credential.TokenCredential;
import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.DatabaseType;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcConnectionString;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcConnectionStringEnhancer;
Expand Down Expand Up @@ -32,6 +33,7 @@
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_CONNECTION_ATTRIBUTES_KV_DELIMITER;
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_NAME_CONNECTION_ATTRIBUTES;
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_NAME_APPLICATION_NAME;
import static com.azure.spring.cloud.core.implementation.util.AzurePropertiesUtils.copyPropertiesIgnoreTargetNonNull;
import static com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider.PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME;


Expand All @@ -51,9 +53,8 @@ class JdbcPropertiesBeanPostProcessor implements BeanPostProcessor, EnvironmentA
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSourceProperties) {
DataSourceProperties dataSourceProperties = (DataSourceProperties) bean;
AzurePasswordlessProperties properties = buildAzureProperties();

AzurePasswordlessProperties properties = Binder.get(environment)
.bindOrCreate(SPRING_CLOUD_AZURE_DATASOURCE_PREFIX, AzurePasswordlessProperties.class);
if (!properties.isPasswordlessEnabled()) {
LOGGER.debug("Feature passwordless authentication is not enabled, skip enhancing jdbc url.");
return bean;
Expand Down Expand Up @@ -129,6 +130,7 @@ private Map<String, String> buildEnhancedProperties(DatabaseType databaseType, A
}

AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.setProperty(result, SPRING_TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME);
AuthProperty.AUTHORITY_HOST.setProperty(result, properties.getProfile().getEnvironment().getActiveDirectoryEndpoint());

databaseType.setDefaultEnhancedProperties(result);

Expand All @@ -144,4 +146,13 @@ public void setEnvironment(Environment environment) {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (GenericApplicationContext) applicationContext;
}

private AzurePasswordlessProperties buildAzureProperties() {
AzureGlobalProperties azureGlobalProperties = applicationContext.getBean(AzureGlobalProperties.class);
AzurePasswordlessProperties azurePasswordlessProperties = Binder.get(environment)
.bindOrCreate(SPRING_CLOUD_AZURE_DATASOURCE_PREFIX, AzurePasswordlessProperties.class);
copyPropertiesIgnoreTargetNonNull(azureGlobalProperties.getProfile(), azurePasswordlessProperties.getProfile());
copyPropertiesIgnoreTargetNonNull(azureGlobalProperties.getCredential(), azurePasswordlessProperties.getCredential());
return azurePasswordlessProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

package com.azure.spring.cloud.autoconfigure.jdbc;

import com.azure.identity.providers.jdbc.implementation.enums.AuthProperty;
import com.azure.identity.providers.jdbc.implementation.template.AzureAuthenticationTemplate;
import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.context.AzureGlobalPropertiesAutoConfiguration;
import com.azure.spring.cloud.autoconfigure.context.AzureTokenCredentialAutoConfiguration;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.SpringTokenCredentialProviderContextProvider;
import org.junit.jupiter.api.Test;
Expand All @@ -19,6 +20,8 @@

abstract class AbstractAzureJdbcAutoConfigurationTest {

public static final String PUBLIC_AUTHORITY_HOST_STRING = AuthProperty.AUTHORITY_HOST.getPropertyKey() + "=" + "https://login.microsoftonline.com/";

abstract void pluginNotOnClassPath();
abstract void wrongJdbcUrl();
abstract void enhanceUrlWithDefaultCredential();
Expand All @@ -27,8 +30,8 @@ abstract class AbstractAzureJdbcAutoConfigurationTest {
protected final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AzureJdbcAutoConfiguration.class,
AzureTokenCredentialAutoConfiguration.class,
DataSourceAutoConfiguration.class,
AzureGlobalProperties.class));
AzureGlobalPropertiesAutoConfiguration.class,
DataSourceAutoConfiguration.class));

@Test
void testEnhanceUrlDefaultCredential() {
Expand Down
Loading

0 comments on commit 644f835

Please sign in to comment.