From 3c7f328ba4b35a764d64273645ba6895c141d047 Mon Sep 17 00:00:00 2001 From: zhihaoguo Date: Fri, 3 Mar 2023 14:49:04 +0800 Subject: [PATCH 1/7] Support passwordless connections for JMS ServiceBus in Spring (#33489) Support passwordless connections for JMS ServiceBus in Spring. --- .../jdbc/JdbcPropertiesBeanPostProcessor.java | 46 +-- .../jms/ServiceBusJmsAutoConfiguration.java | 27 +- ...ServiceBusJmsConnectionFactoryFactory.java | 28 +- .../AzureServiceBusJmsProperties.java | 160 +++++++- ...ePasswordlessEnvironmentPostProcessor.java | 43 ++ .../AzureServiceBusJmsCredentialSupplier.java | 37 ++ ...erviceBusJmsPasswordlessConfiguration.java | 40 ++ ...itional-spring-configuration-metadata.json | 22 +- ...bstractAzureJdbcAutoConfigurationTest.java | 2 +- .../JdbcPropertiesBeanPostProcessorTest.java | 14 +- ...essorWithApplicationContextRunnerTest.java | 9 +- .../MySqlAzureJdbcAutoConfigurationTest.java | 1 + ...tgreSqlAzureJdbcAutoConfigurationTest.java | 1 + .../ServiceBusJmsAutoConfigurationTests.java | 4 +- .../AzureServiceBusJmsPropertiesTests.java | 33 +- ...ceBusJmsPasswordlessConfigurationTest.java | 147 +++++++ .../MergeAzureCommonPropertiesTest.java | 113 +++++ sdk/spring/spring-cloud-azure-core/pom.xml | 7 + .../AzurePasswordlessPropertiesUtils.java | 107 +++++ .../util/AzureSpringIdentifier.java | 1 + .../properties/PasswordlessProperties.java | 137 +++++++ .../AzurePasswordlessPropertiesUtilsTest.java | 385 ++++++++++++++++++ .../jms/ServiceBusJmsPasswordlessIT.java | 45 ++ ...pplication-servicebus-jms-passwordless.yml | 8 + .../servicebus/test-resources.json | 25 ++ .../AzureJdbcPasswordlessProperties.java | 125 ++++++ .../AzureKafkaPasswordlessProperties.java | 98 +++++ ...OAuth2AuthenticateCallbackHandlerTest.java | 2 +- .../pom.xml | 5 + 29 files changed, 1605 insertions(+), 67 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java create mode 100644 sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtils.java create mode 100644 sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/properties/PasswordlessProperties.java create mode 100644 sdk/spring/spring-cloud-azure-core/src/test/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtilsTest.java create mode 100644 sdk/spring/spring-cloud-azure-integration-tests/src/test/java/com/azure/spring/cloud/integration/tests/servicebus/jms/ServiceBusJmsPasswordlessIT.java create mode 100644 sdk/spring/spring-cloud-azure-integration-tests/src/test/resources/application-servicebus-jms-passwordless.yml create mode 100644 sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureJdbcPasswordlessProperties.java create mode 100644 sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureKafkaPasswordlessProperties.java diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java index 0c3f494fef96..669562dafe5e 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java @@ -3,12 +3,14 @@ package com.azure.spring.cloud.autoconfigure.implementation.jdbc; import com.azure.core.credential.TokenCredential; +import com.azure.identity.extensions.implementation.credential.TokenCredentialProviderOptions; +import com.azure.identity.extensions.implementation.credential.provider.TokenCredentialProvider; import com.azure.identity.extensions.implementation.enums.AuthProperty; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; -import com.azure.spring.cloud.core.implementation.credential.resolver.AzureTokenCredentialResolver; +import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider; -import com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties; +import com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -25,14 +27,8 @@ import java.util.HashMap; import java.util.Map; -import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_CONNECTION_ATTRIBUTES_ATTRIBUTE_EXTENSION_VERSION; -import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_CONNECTION_ATTRIBUTES_DELIMITER; -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.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_NAME_ASSUME_MIN_SERVER_VERSION; -import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_VALUE_ASSUME_MIN_SERVER_VERSION; -import static com.azure.spring.cloud.core.implementation.util.AzurePropertiesUtils.copyPropertiesIgnoreTargetNonNull; +import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.*; +import com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties; import static com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider.PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME; @@ -52,7 +48,7 @@ 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(); + AzureJdbcPasswordlessProperties properties = buildAzureProperties(); if (!properties.isPasswordlessEnabled()) { LOGGER.debug("Feature passwordless authentication is not enabled, skip enhancing jdbc url."); @@ -125,17 +121,15 @@ private void enhanceUserAgent(DatabaseType databaseType, JdbcConnectionStringEnh } } - private Map buildEnhancedProperties(DatabaseType databaseType, AzurePasswordlessProperties properties) { + private Map buildEnhancedProperties(DatabaseType databaseType, AzureJdbcPasswordlessProperties properties) { Map result = new HashMap<>(); - AzureTokenCredentialResolver resolver = applicationContext.getBean(AzureTokenCredentialResolver.class); - TokenCredential tokenCredential = resolver.resolve(properties); + TokenCredentialProvider tokenCredentialProvider = TokenCredentialProvider.createDefault(new TokenCredentialProviderOptions(properties.toPasswordlessProperties())); + TokenCredential tokenCredential = tokenCredentialProvider.get(); - if (tokenCredential != null) { - LOGGER.debug("Add SpringTokenCredentialProvider as the default token credential provider."); - AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.setProperty(result, PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME); - applicationContext.registerBean(PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME, TokenCredential.class, () -> tokenCredential); - } + AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.setProperty(result, PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME); + applicationContext.registerBean(PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME, TokenCredential.class, () -> tokenCredential); + LOGGER.debug("Add SpringTokenCredentialProvider as the default token credential provider."); AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.setProperty(result, SPRING_TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME); AuthProperty.AUTHORITY_HOST.setProperty(result, properties.getProfile().getEnvironment().getActiveDirectoryEndpoint()); @@ -154,12 +148,14 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.applicationContext = (GenericApplicationContext) applicationContext; } - private AzurePasswordlessProperties buildAzureProperties() { + private AzureJdbcPasswordlessProperties 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; + AzureJdbcPasswordlessProperties azurePasswordlessProperties = Binder.get(environment) + .bindOrCreate(SPRING_CLOUD_AZURE_DATASOURCE_PREFIX, AzureJdbcPasswordlessProperties.class); + + AzureJdbcPasswordlessProperties mergedProperties = new AzureJdbcPasswordlessProperties(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(azureGlobalProperties, azurePasswordlessProperties, mergedProperties); + return mergedProperties; + } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java index 56471491f4c1..6c8e91f9ffd8 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java @@ -7,6 +7,8 @@ import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import com.azure.spring.cloud.autoconfigure.implementation.resourcemanager.AzureServiceBusResourceManagerAutoConfiguration; import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; +import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsPasswordlessConfiguration; +import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils; import com.azure.spring.cloud.core.provider.connectionstring.ServiceConnectionStringProvider; import com.azure.spring.cloud.core.service.AzureServiceType; import jakarta.jms.ConnectionFactory; @@ -32,6 +34,7 @@ import java.util.HashMap; import java.util.Map; +import static com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier.AZURE_SPRING_PASSWORDLESS_SERVICE_BUS; import static com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS; /** @@ -46,17 +49,27 @@ AzureServiceBusResourceManagerAutoConfiguration.class }) @ConditionalOnProperty(value = "spring.jms.servicebus.enabled", matchIfMissing = true) @ConditionalOnClass({ ConnectionFactory.class, JmsConnectionFactory.class, JmsTemplate.class }) -@EnableConfigurationProperties({ AzureServiceBusJmsProperties.class, JmsProperties.class }) -@Import({ ServiceBusJmsConnectionFactoryConfiguration.class, ServiceBusJmsContainerConfiguration.class }) +@EnableConfigurationProperties({ JmsProperties.class }) +@Import({ ServiceBusJmsPasswordlessConfiguration.class, ServiceBusJmsConnectionFactoryConfiguration.class, ServiceBusJmsContainerConfiguration.class }) public class ServiceBusJmsAutoConfiguration { + @Bean + AzureServiceBusJmsProperties serviceBusJmsProperties(AzureGlobalProperties azureGlobalProperties) { + AzureServiceBusJmsProperties properties = new AzureServiceBusJmsProperties(); + return mergeAzureProperties(azureGlobalProperties, properties); + } + @Bean @ConditionalOnExpression("'premium'.equalsIgnoreCase('${spring.jms.servicebus.pricing-tier}')") - ServiceBusJmsConnectionFactoryCustomizer amqpOpenPropertiesCustomizer() { + ServiceBusJmsConnectionFactoryCustomizer amqpOpenPropertiesCustomizer(ObjectProvider azureServiceBusJmsCredentialSupplier) { return factory -> { final Map properties = new HashMap<>(); properties.put("com.microsoft:is-client-provider", true); - properties.put("user-agent", AZURE_SPRING_SERVICE_BUS); + if (azureServiceBusJmsCredentialSupplier.getIfAvailable() != null) { + properties.put("user-agent", AZURE_SPRING_PASSWORDLESS_SERVICE_BUS); + } else { + properties.put("user-agent", AZURE_SPRING_SERVICE_BUS); + } //set user agent factory.setExtension(JmsConnectionExtensions.AMQP_OPEN_PROPERTIES.toString(), (connection, uri) -> properties); @@ -70,4 +83,10 @@ static AzureServiceBusJmsPropertiesBeanPostProcessor azureServiceBusJmsPropertie ObjectProvider> connectionStringProviders) { return new AzureServiceBusJmsPropertiesBeanPostProcessor(connectionStringProviders); } + + private AzureServiceBusJmsProperties mergeAzureProperties(AzureGlobalProperties azureGlobalProperties, AzureServiceBusJmsProperties azurePasswordlessProperties) { + AzureServiceBusJmsProperties mergedProperties = new AzureServiceBusJmsProperties(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(azureGlobalProperties, azurePasswordlessProperties, mergedProperties); + return mergedProperties; + } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java index 7fd5cff6e404..d2f46d666e14 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java @@ -56,20 +56,26 @@ private void setPrefetchPolicy(T fact private T createConnectionFactoryInstance(Class factoryClass) { try { T factory; - ServiceBusConnectionString serviceBusConnectionString = new ServiceBusConnectionString(properties.getConnectionString()); - String host = serviceBusConnectionString.getEndpointUri().getHost(); + if (properties.isPasswordlessEnabled()) { + String remoteUrl = String.format(AMQP_URI_FORMAT, + properties.getNamespace() + "." + properties.getProfile().getEnvironment().getServiceBusDomainName(), + properties.getIdleTimeout().toMillis()); + factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl); + } else { + ServiceBusConnectionString serviceBusConnectionString = new ServiceBusConnectionString(properties.getConnectionString()); + String host = serviceBusConnectionString.getEndpointUri().getHost(); - String remoteUrl = String.format(AMQP_URI_FORMAT, host, properties.getIdleTimeout().toMillis()); - String username = serviceBusConnectionString.getSharedAccessKeyName(); - String password = serviceBusConnectionString.getSharedAccessKey(); + String remoteUrl = String.format(AMQP_URI_FORMAT, host, properties.getIdleTimeout().toMillis()); + String username = serviceBusConnectionString.getSharedAccessKeyName(); + String password = serviceBusConnectionString.getSharedAccessKey(); - if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) { - factory = factoryClass.getConstructor(String.class, String.class, String.class) - .newInstance(username, password, remoteUrl); - } else { - factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl); + if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) { + factory = factoryClass.getConstructor(String.class, String.class, String.class) + .newInstance(username, password, remoteUrl); + } else { + factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl); + } } - return factory; } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new IllegalStateException("Unable to create JmsConnectionFactory", ex); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java index ac96f6248d61..11647ed6d68f 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java @@ -3,6 +3,9 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms.properties; +import com.azure.spring.cloud.autoconfigure.implementation.properties.core.authentication.TokenCredentialConfigurationProperties; +import com.azure.spring.cloud.autoconfigure.implementation.properties.core.profile.AzureProfileConfigurationProperties; +import com.azure.spring.cloud.core.properties.PasswordlessProperties; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.jms.JmsPoolConnectionFactoryProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -11,20 +14,57 @@ import org.springframework.util.StringUtils; import java.time.Duration; +import java.util.Map; @ConfigurationProperties(prefix = AzureServiceBusJmsProperties.PREFIX) -public class AzureServiceBusJmsProperties implements InitializingBean { +public class AzureServiceBusJmsProperties implements InitializingBean, PasswordlessProperties { /** * Service Bus JMS properties prefix. */ public static final String PREFIX = "spring.jms.servicebus"; + private static final String SERVICE_BUS_SCOPE_AZURE = "https://servicebus.azure.net/.default"; + private static final String SERVICE_BUS_SCOPE_AZURE_CHINA = SERVICE_BUS_SCOPE_AZURE; + private static final String SERVICE_BUS_SCOPE_AZURE_GERMANY = SERVICE_BUS_SCOPE_AZURE; + private static final String SERVICE_BUS_SCOPE_AZURE_US_GOVERNMENT = SERVICE_BUS_SCOPE_AZURE; + + private static final Map SERVICEBUS_SCOPE_MAP = new HashMap() { + { + put(CloudType.AZURE, SERVICE_BUS_SCOPE_AZURE); + put(CloudType.AZURE_CHINA, SERVICE_BUS_SCOPE_AZURE_CHINA); + put(CloudType.AZURE_GERMANY, SERVICE_BUS_SCOPE_AZURE_GERMANY); + put(CloudType.AZURE_US_GOVERNMENT, SERVICE_BUS_SCOPE_AZURE_US_GOVERNMENT); + } + }; + + private AzureProfileConfigurationProperties profile = new AzureProfileConfigurationProperties(); + + /** + * The scopes required for the access token. + */ + private String scopes; + + private TokenCredentialConfigurationProperties credential = new TokenCredentialConfigurationProperties(); + + /** + * Whether to enable supporting azure identity token credentials. + * + * If the value is true, then 'spring.jms.servicebus.namespace' must be set. + * If the passwordlessEnabled is true, it will try to authenticate connections with Azure AD. + */ + private boolean passwordlessEnabled = false; + /** * Whether to enable Service Bus JMS autoconfiguration. */ private boolean enabled = true; + /** + * The Service Bus namespace. + */ + private String namespace; + /** * Connection string to connect to a Service Bus namespace. */ @@ -63,10 +103,18 @@ public JmsPoolConnectionFactoryProperties getPool() { return pool; } + /** + * Get the connection string to connect to a Service Bus namespace. + * @return the connection string to connect to a Service Bus namespace. + */ public String getConnectionString() { return connectionString; } + /** + * Set the connection string to connect to a Service Bus namespace. + * @param connectionString the connection string to connect to a Service Bus namespace. + */ public void setConnectionString(String connectionString) { this.connectionString = connectionString; } @@ -103,10 +151,112 @@ public PrefetchPolicy getPrefetchPolicy() { return prefetchPolicy; } + /** + * Get the Service Bus namespace. + * @return the Service Bus namespace. + */ + public String getNamespace() { + return namespace; + } + + /** + * Set the Service Bus namespace. + * @param namespace the Service Bus namespace. + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Get the scopes required for the access token. + * + * @return scopes required for the access token + */ + @Override + public String getScopes() { + return this.scopes == null ? getDefaultScopes() : this.scopes; + } + + /** + * Set the scopes required for the access token. + * + * @param scopes the scopes required for the access token + */ + public void setScopes(String scopes) { + this.scopes = scopes; + } + + /** + * Whether to enable connections authenticating with Azure AD, default is false. + * + * @return enable connections authenticating with Azure AD if true, otherwise false. + */ + @Override + public boolean isPasswordlessEnabled() { + return passwordlessEnabled; + } + + /** + * Set the value to enable/disable connections authenticating with Azure AD. + * If not set, by default the value is false. + * + * @param passwordlessEnabled the passwordlessEnabled + */ + public void setPasswordlessEnabled(boolean passwordlessEnabled) { + this.passwordlessEnabled = passwordlessEnabled; + } + + /** + * Get the profile + * @return the profile + */ + @Override + public AzureProfileConfigurationProperties getProfile() { + return profile; + } + + /** + * Set the profile + * @param profile the profile properties related to an Azure subscription + */ + public void setProfile(AzureProfileConfigurationProperties profile) { + this.profile = profile; + } + + /** + * Get the credential properties. + * + * @return the credential properties. + */ + @Override + public TokenCredentialConfigurationProperties getCredential() { + return credential; + } + + /** + * Set the credential properties. + * + * @param credential the credential properties + */ + public void setCredential(TokenCredentialConfigurationProperties credential) { + this.credential = credential; + } + + /** + * Validate spring.jms.servicebus related properties. + * + * @throws IllegalArgumentException If connectionString is empty. + */ @Override public void afterPropertiesSet() throws Exception { - if (!StringUtils.hasText(connectionString)) { - throw new IllegalArgumentException("'spring.jms.servicebus.connection-string' should be provided"); + if (isPasswordlessEnabled()) { + if (!StringUtils.hasText(namespace)) { + throw new IllegalArgumentException("Passwordless connections enabled, 'spring.jms.servicebus.namespace' should be provided."); + } + } else { + if (!StringUtils.hasText(connectionString)) { + throw new IllegalArgumentException("'spring.jms.servicebus.connection-string' should be provided."); + } } if (null == pricingTier || !pricingTier.matches("(?i)premium|standard|basic")) { @@ -245,4 +395,8 @@ public void setPhase(Integer phase) { this.phase = phase; } } + + private String getDefaultScopes() { + return SERVICEBUS_SCOPE_MAP.getOrDefault(getProfile().getCloudType(), SERVICE_BUS_SCOPE_AZURE); + } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java new file mode 100644 index 000000000000..fa46fef2af5f --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.implementation.passwordless; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * Add properties to 'spring.cloud.function.ineligible-definitions' to filter ineligible functions that used by passwordless autoconfigurations. + * + * @since 4.7.0 + */ +public class AzurePasswordlessEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + /** + * The order value of the {@link AzurePasswordlessEnvironmentPostProcessor}. + */ + public static final int ORDER = ConfigDataEnvironmentPostProcessor.ORDER + 2; + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + Properties properties = new Properties(); + List passwordlessCredentialSupplier = new ArrayList<>(); + passwordlessCredentialSupplier.add("azureRedisCredentialSupplier"); + passwordlessCredentialSupplier.add("azureServiceBusJmsCredentialSupplier"); + properties.setProperty("spring.cloud.function.ineligible-definitions", String.join(",", passwordlessCredentialSupplier)); + environment.getPropertySources().addLast(new PropertiesPropertySource("passwordless", properties)); + } + + @Override + public int getOrder() { + return ORDER; + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java new file mode 100644 index 000000000000..a54f916913b5 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.jms; + +import com.azure.identity.extensions.implementation.template.AzureAuthenticationTemplate; + +import java.util.Properties; +import java.util.function.Supplier; + +/** + * AzureServiceBusJmsCredentialSupplier that provides a String as the password to connect Azure ServiceBus. + * + * @since 4.7.0 + */ +public class AzureServiceBusJmsCredentialSupplier implements Supplier { + + private final AzureAuthenticationTemplate azureAuthenticationTemplate; + + /** + * Create {@link AzureServiceBusJmsCredentialSupplier} instance. + * @param properties properties to initialize AzureServiceBusJmsCredentialSupplier. + */ + public AzureServiceBusJmsCredentialSupplier(Properties properties) { + azureAuthenticationTemplate = new AzureAuthenticationTemplate(); + azureAuthenticationTemplate.init(properties); + } + + @Override + public String get() { + return azureAuthenticationTemplate.getTokenAsPassword(); + } + + AzureServiceBusJmsCredentialSupplier(AzureAuthenticationTemplate azureAuthenticationTemplate) { + this.azureAuthenticationTemplate = azureAuthenticationTemplate; + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java new file mode 100644 index 000000000000..537eba723c89 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.jms; + +import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties; +import org.apache.qpid.jms.JmsConnectionExtensions; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Azure Service Bus JMS passwordless support. + * + * @since 4.7.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.jms.servicebus.passwordless-enabled", havingValue = "true") +class ServiceBusJmsPasswordlessConfiguration { + + @Bean + @ConditionalOnMissingBean + AzureServiceBusJmsCredentialSupplier azureServiceBusJmsCredentialSupplier(AzureServiceBusJmsProperties azureServiceBusJmsProperties) { + return new AzureServiceBusJmsCredentialSupplier(azureServiceBusJmsProperties.toPasswordlessProperties()); + } + + @Bean + ServiceBusJmsConnectionFactoryCustomizer jmsAADAuthenticationCustomizer(AzureServiceBusJmsCredentialSupplier credentialSupplier) { + return factory -> { + factory.setExtension(JmsConnectionExtensions.USERNAME_OVERRIDE.toString(), (connection, uri) -> "$jwt"); + factory.setExtension(JmsConnectionExtensions.PASSWORD_OVERRIDE.toString(), (connection, uri) -> + credentialSupplier.get() + ); + }; + } + + +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 0ca3aed5c6d2..2f3aa7bc275a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -532,68 +532,68 @@ "name": "spring.datasource.azure.credential.client-id", "type": "java.lang.String", "description": "Client ID to use when performing service principal authentication with Azure.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.client-secret", "type": "java.lang.String", "description": "Client secret to use when performing service principal authentication with Azure.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.client-certificate-password", "type": "java.lang.String", "description": "Password of the certificate file.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.client-certificate-path", "type": "java.lang.String", "description": "Path of a PEM certificate file to use when performing service principal authentication with Azure.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.username", "type": "java.lang.String", "description": "Username to use when performing username\/password authentication with Azure.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.password", "type": "java.lang.String", "description": "Password to use when performing username\/password authentication with Azure.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.credential.managed-identity-enabled", "type": "java.lang.Boolean", "description": "Whether to enable managed identity to authenticate with Azure. If true and the client-id is set, will use the client ID as user assigned managed identity client ID.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties", + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties", "defaultValue": false }, { "name": "spring.datasource.azure.profile.environment.active-directory-endpoint", "type": "java.lang.String", "description": "The Azure Active Directory endpoint to connect to.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.profile.tenant-id", "type": "java.lang.String", "description": "Tenant ID for Azure resources.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.profile.cloud-type", "type": "java.lang.String", "description": "Name of the Azure cloud to connect to. Supported types are: AZURE, AZURE_CHINA, AZURE_US_GOVERNMENT, OTHER. The default value is `AZURE`.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties" + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties" }, { "name": "spring.datasource.azure.passwordless-enabled", "type": "java.lang.Boolean", "description": "Whether to enable passwordless connections to Azure databases by using OAuth2 Azure Active Directory token credentials.", - "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties", + "sourceType": "com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties", "defaultValue": false }, { diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/AbstractAzureJdbcAutoConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/AbstractAzureJdbcAutoConfigurationTest.java index 4483ecfc091b..761fd6e19289 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/AbstractAzureJdbcAutoConfigurationTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/AbstractAzureJdbcAutoConfigurationTest.java @@ -20,7 +20,7 @@ abstract class AbstractAzureJdbcAutoConfigurationTest { public static final String PUBLIC_AUTHORITY_HOST_STRING = AuthProperty.AUTHORITY_HOST.getPropertyKey() + "=" + "https://login.microsoftonline.com/"; - + public static final String PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING = AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.getPropertyKey() + "=" + "passwordlessTokenCredential"; abstract void pluginNotOnClassPath(); abstract void wrongJdbcUrl(); abstract void enhanceUrlWithDefaultCredential(); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorTest.java index ad05da8f6785..72f36bb8853c 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorTest.java @@ -34,6 +34,7 @@ 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/"; + public static final String PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING = AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.getPropertyKey() + "=" + "passwordlessTokenCredential"; private static final String POSTGRESQL_ASSUME_MIN_SERVER_VERSION = POSTGRESQL_PROPERTY_NAME_ASSUME_MIN_SERVER_VERSION + "=" + POSTGRESQL_PROPERTY_VALUE_ASSUME_MIN_SERVER_VERSION; @@ -97,6 +98,7 @@ void shouldPostprocessWhenSwitchOn() { String expectedJdbcUrl = enhanceJdbcUrl( DatabaseType.MYSQL, MYSQL_CONNECTION_STRING, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, MYSQL_USER_AGENT, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName() ); @@ -121,7 +123,8 @@ void shouldGetCloudTypeFromAzureUsGov() { MYSQL_CONNECTION_STRING, MYSQL_USER_AGENT, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName(), - US_AUTHORITY_HOST_STRING + US_AUTHORITY_HOST_STRING, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING ); assertEquals(expectedJdbcUrl, dataSourceProperties.getUrl()); @@ -138,6 +141,7 @@ void mySqlUserAgentShouldConfigureIfConnectionAttributesIsEmpty() { String expectedJdbcUrl = enhanceJdbcUrl( DatabaseType.MYSQL, MYSQL_CONNECTION_STRING, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, MYSQL_USER_AGENT, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName() ); @@ -159,7 +163,8 @@ void mySqlUserAgentShouldConfigureIfConnectionAttributesIsNotEmpty() { String expectedJdbcUrl = enhanceJdbcUrl( DatabaseType.MYSQL, baseUrl + ",_extension_version:" + AzureSpringIdentifier.AZURE_SPRING_MYSQL_OAUTH, - AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName() + AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName(), + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING ); assertEquals(expectedJdbcUrl, dataSourceProperties.getUrl()); @@ -179,7 +184,8 @@ void mySqlUserAgentShouldConfigureIfConnectionAttributes() { String expectedJdbcUrl = enhanceJdbcUrl( DatabaseType.MYSQL, baseUrl + ",_extension_version:" + AzureSpringIdentifier.AZURE_SPRING_MYSQL_OAUTH, - AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName() + AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName(), + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING ); assertEquals(expectedJdbcUrl, dataSourceProperties.getUrl()); } @@ -198,6 +204,7 @@ void postgreSqlUserAgentShouldConfigureIfNonApplicationNameProvided() { baseUrl, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName(), APPLICATION_NAME.getName() + "=" + AzureSpringIdentifier.AZURE_SPRING_POSTGRESQL_OAUTH, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, POSTGRESQL_ASSUME_MIN_SERVER_VERSION ); @@ -219,6 +226,7 @@ void postgreSqlUserAgentShouldNotConfigureIfApplicationNameExists() { DatabaseType.POSTGRESQL, baseUrl, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName(), + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, POSTGRESQL_ASSUME_MIN_SERVER_VERSION ); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorWithApplicationContextRunnerTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorWithApplicationContextRunnerTest.java index 598c977be605..3a85569689b7 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorWithApplicationContextRunnerTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessorWithApplicationContextRunnerTest.java @@ -9,7 +9,7 @@ import com.azure.spring.cloud.autoconfigure.implementation.context.AzureTokenCredentialAutoConfiguration; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; import com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider; -import com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties; +import com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; @@ -28,12 +28,12 @@ class JdbcPropertiesBeanPostProcessorWithApplicationContextRunnerTest { private static final String MYSQL_CONNECTION_STRING = "jdbc:mysql://host/database?enableSwitch1&property1=value1"; private static final String PUBLIC_AUTHORITY_HOST_STRING = AuthProperty.AUTHORITY_HOST.getPropertyKey() + "=" + "https://login.microsoftonline.com/"; - + public static final String PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING = AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.getPropertyKey() + "=" + "passwordlessTokenCredential"; private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(AzureJdbcAutoConfiguration.class, DataSourceProperties.class, - AzurePasswordlessProperties.class, + AzureJdbcPasswordlessProperties.class, AzureGlobalPropertiesAutoConfiguration.class, AzureTokenCredentialAutoConfiguration.class)); @@ -73,6 +73,7 @@ void mySqlAuthPluginOnClassPath() { String expectedJdbcUrl = enhanceJdbcUrl( DatabaseType.MYSQL, MYSQL_CONNECTION_STRING, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, PUBLIC_AUTHORITY_HOST_STRING, MYSQL_USER_AGENT, AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.getPropertyKey() + "=" + SpringTokenCredentialProvider.class.getName() @@ -112,7 +113,7 @@ void testBindSpringBootProperties() { assertThat(context).hasSingleBean(SpringTokenCredentialProviderContextProvider.class); ConfigurableEnvironment environment = context.getEnvironment(); - AzurePasswordlessProperties properties = Binder.get(environment).bindOrCreate("spring.datasource.azure", AzurePasswordlessProperties.class); + AzureJdbcPasswordlessProperties properties = Binder.get(environment).bindOrCreate("spring.datasource.azure", AzureJdbcPasswordlessProperties.class); assertNotEquals("azure-client-id", properties.getCredential().getClientId()); assertEquals("fake-jdbc-client-id", properties.getCredential().getClientId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/MySqlAzureJdbcAutoConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/MySqlAzureJdbcAutoConfigurationTest.java index 14cc80e4b5bb..a743dd418be7 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/MySqlAzureJdbcAutoConfigurationTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/MySqlAzureJdbcAutoConfigurationTest.java @@ -82,6 +82,7 @@ void enhanceUrlWithDefaultCredential() { DatabaseType.MYSQL, false, connectionString, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, PUBLIC_AUTHORITY_HOST_STRING, AUTHPROPERTY_TOKENCREDENTIALPROVIDERCLASSNAME_PROPERTY, MYSQL_USER_AGENT diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/PostgreSqlAzureJdbcAutoConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/PostgreSqlAzureJdbcAutoConfigurationTest.java index 0ed33c4ec949..301faa2d8902 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/PostgreSqlAzureJdbcAutoConfigurationTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/PostgreSqlAzureJdbcAutoConfigurationTest.java @@ -70,6 +70,7 @@ void enhanceUrlWithDefaultCredential() { DatabaseType.POSTGRESQL, false, connectionString, + PUBLIC_TOKEN_CREDENTIAL_BEAN_NAME_STRING, PUBLIC_AUTHORITY_HOST_STRING, POSTGRESQL_USER_AGENT, AUTHPROPERTY_TOKENCREDENTIALPROVIDERCLASSNAME_PROPERTY, diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java index fabae0271c32..6e6e509836a7 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java @@ -4,9 +4,9 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; -import com.azure.spring.jms.ServiceBusJmsConnectionFactory; import com.azure.spring.cloud.core.provider.connectionstring.StaticConnectionStringProvider; import com.azure.spring.cloud.core.service.AzureServiceType; +import com.azure.spring.jms.ServiceBusJmsConnectionFactory; import jakarta.jms.ConnectionFactory; import jakarta.jms.Session; import org.apache.qpid.jms.JmsConnectionFactory; @@ -43,6 +43,7 @@ class ServiceBusJmsAutoConfigurationTests { + "SharedAccessKey=sasKey"; private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withBean(AzureGlobalProperties.class, () -> new AzureGlobalProperties()) .withConfiguration(AutoConfigurations.of(JmsAutoConfiguration.class, ServiceBusJmsAutoConfiguration.class)); private void testQueueJmsListenerContainerFactoryWithCustomSettings(AssertableApplicationContext loaded) { @@ -128,7 +129,6 @@ void autoconfigurationEnabledAndContextSuccessWithNonpremiumTier(String pricingT assertThat(context).hasSingleBean(ConnectionFactory.class); assertThat(context).hasSingleBean(JmsTemplate.class); assertThat(context).hasSingleBean(DefaultJmsListenerContainerFactoryConfigurer.class); - assertThat(context).hasBean("jmsListenerContainerFactory"); assertThat(context).hasBean("topicJmsListenerContainerFactory"); assertThat(context).doesNotHaveBean("amqpOpenPropertiesCustomizer"); }); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsPropertiesTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsPropertiesTests.java index 7c04bfb944c8..7364ad7642d9 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsPropertiesTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsPropertiesTests.java @@ -7,6 +7,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -15,13 +19,16 @@ class AzureServiceBusJmsPropertiesTests { static final String CONNECTION_STRING = "Endpoint=sb://host/;SharedAccessKeyName=sasKeyName;" + "SharedAccessKey=sasKey"; + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(AzureServiceBusJmsPropertiesTestConfig.class); + @Test void connectionStringNotValid() { AzureServiceBusJmsProperties prop = new AzureServiceBusJmsProperties(); Exception ex = assertThrows(IllegalArgumentException.class, prop::afterPropertiesSet); - String expectedMessage = "'spring.jms.servicebus.connection-string' should be provided"; + String expectedMessage = "'spring.jms.servicebus.connection-string' should be provided."; String actualMessage = ex.getMessage(); System.out.println("message:" + actualMessage); assertTrue(actualMessage.contains(expectedMessage)); @@ -29,7 +36,7 @@ void connectionStringNotValid() { @ParameterizedTest @NullAndEmptySource - @ValueSource(strings = { "xx" }) + @ValueSource(strings = {"xx"}) void pricingTierNotValid(String pricingTier) { AzureServiceBusJmsProperties prop = new AzureServiceBusJmsProperties(); prop.setConnectionString(CONNECTION_STRING); @@ -43,4 +50,26 @@ void pricingTierNotValid(String pricingTier) { assertTrue(actualMessage.contains(expectedMessage)); } + @Test + void testPasswordlessEnabled() { + contextRunner + .withPropertyValues("spring.jms.servicebus.passwordless-enabled=true") + .run(context -> { + Exception ex = assertThrows(IllegalStateException.class, () -> context.getBean(AzureServiceBusJmsProperties.class)); + String actualMessage = ex.getCause().getMessage(); + String expectedMessage = "Passwordless connections enabled, 'spring.jms.servicebus.namespace' should be provided."; + assertTrue(actualMessage.contains(expectedMessage)); + }); + } + + @EnableConfigurationProperties + static class AzureServiceBusJmsPropertiesTestConfig { + + @Bean + @ConfigurationProperties(AzureServiceBusJmsProperties.PREFIX) + AzureServiceBusJmsProperties jmsProperties() { + return new AzureServiceBusJmsProperties(); + } + } + } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java new file mode 100644 index 000000000000..bcbba7d264be --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.jms; + +import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; +import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedConstruction; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE; +import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE_CHINA; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.when; + +class ServiceBusJmsPasswordlessConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(ServiceBusJmsPasswordlessTestConfig.class); + + @Test + void testPropertyEnabledIsFalse() { + AzureGlobalProperties azureProperties = new AzureGlobalProperties(); + azureProperties.getProfile().setCloudType(AZURE); + azureProperties.getProfile().getEnvironment().setActiveDirectoryEndpoint("abc"); + azureProperties.getProfile().getEnvironment().setActiveDirectoryGraphApiVersion("v2"); + + this.contextRunner + .withPropertyValues("spring.jms.servicebus.passwordless-enabled=false") + .withPropertyValues("spring.jms.servicebus.connection-string=fake-connection-string") + .withPropertyValues("spring.jms.servicebus.pricing-tier=standard") + .withBean(AzureGlobalProperties.class, () -> azureProperties) + .run(context -> { + assertThat(context).doesNotHaveBean(AzureServiceBusJmsCredentialSupplier.class); + assertThat(context).doesNotHaveBean(ServiceBusJmsConnectionFactoryCustomizer.class); + }); + } + + @Test + void testPropertyEnabledIsTrue() { + AzureGlobalProperties azureProperties = new AzureGlobalProperties(); + azureProperties.getProfile().setCloudType(AZURE); + azureProperties.getProfile().getEnvironment().setActiveDirectoryEndpoint("abc"); + azureProperties.getProfile().getEnvironment().setActiveDirectoryGraphApiVersion("v2"); + + this.contextRunner + .withPropertyValues("spring.jms.servicebus.passwordless-enabled=true") + .withPropertyValues("spring.jms.servicebus.namespace=testnamespace") + .withPropertyValues("spring.jms.servicebus.pricing-tier=standard") + .withBean(AzureGlobalProperties.class, () -> azureProperties) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusJmsCredentialSupplier.class); + assertThat(context).hasSingleBean(ServiceBusJmsConnectionFactoryCustomizer.class); + }); + } + + @Test + void testServiceBusPasswordlessProperties() { + AzureGlobalProperties azureProperties = new AzureGlobalProperties(); + azureProperties.getProfile().setCloudType(AZURE); + azureProperties.getProfile().getEnvironment().setActiveDirectoryEndpoint("abc"); + azureProperties.getProfile().getEnvironment().setActiveDirectoryGraphApiVersion("v2"); + this.contextRunner + .withPropertyValues("spring.jms.servicebus.passwordless-enabled=true") + .withPropertyValues("spring.jms.servicebus.namespace=testnamespace") + .withPropertyValues("spring.jms.servicebus.pricing-tier=standard") + .withPropertyValues("spring.jms.servicebus.scopes=scopes", + "spring.jms.servicebus.profile.tenant-id=tenant-id", + "spring.jms.servicebus.profile.subscription-id=subscription-id", + "spring.jms.servicebus.profile.CloudType=AZURE_CHINA", + "spring.jms.servicebus.credential.client-id=client-id", + "spring.jms.servicebus.credential.client-secret=secret", + "spring.jms.servicebus.credential.client-certificatePath=client-certificatePath", + "spring.jms.servicebus.credential.client-certificatePassword=client-certificatePassword", + "spring.jms.servicebus.credential.username=username", + "spring.jms.servicebus.credential.password=password", + "spring.jms.servicebus.credential.managed-identity-enabled=true", + "spring.jms.servicebus.client.application-id=application-id", + "spring.jms.servicebus.proxy.hostname=hostname", + "spring.jms.servicebus.proxy.username=username", + "spring.jms.servicebus.proxy.port=1111", + "spring.jms.servicebus.proxy.password=password", + "spring.jms.servicebus.proxy.type=type") + .withBean(AzureGlobalProperties.class, () -> azureProperties) + .run(context -> { + assertThat(context).hasSingleBean(AzureServiceBusJmsCredentialSupplier.class); + assertThat(context).hasSingleBean(ServiceBusJmsConnectionFactoryCustomizer.class); + AzureServiceBusJmsProperties properties = context.getBean(AzureServiceBusJmsProperties.class); + assertThat(properties.getScopes()).isEqualTo("scopes"); + assertThat(properties.getProfile().getTenantId()).isEqualTo("tenant-id"); + assertThat(properties.getProfile().getSubscriptionId()).isEqualTo("subscription-id"); + assertThat(properties.getProfile().getCloudType()).isEqualTo(AZURE_CHINA); + assertThat(properties.getCredential().getClientId()).isEqualTo("client-id"); + assertThat(properties.getCredential().getClientSecret()).isEqualTo("secret"); + assertThat(properties.getCredential().getClientCertificatePath()).isEqualTo("client-certificatePath"); + assertThat(properties.getCredential().getClientCertificatePassword()).isEqualTo("client-certificatePassword"); + assertThat(properties.getCredential().getUsername()).isEqualTo("username"); + assertThat(properties.getCredential().getPassword()).isEqualTo("password"); + assertThat(properties.getCredential().isManagedIdentityEnabled()).isTrue(); + + }); + } + + @Test + void testAzureServiceBusJmsCredentialSupplier() { + AzureGlobalProperties azureProperties = new AzureGlobalProperties(); + azureProperties.getProfile().setCloudType(AZURE); + azureProperties.getProfile().getEnvironment().setActiveDirectoryEndpoint("abc"); + azureProperties.getProfile().getEnvironment().setActiveDirectoryGraphApiVersion("v2"); + + this.contextRunner + .withPropertyValues("spring.jms.servicebus.passwordless-enabled=true") + .withBean(AzureGlobalProperties.class, () -> azureProperties); + + try (MockedConstruction supplierMockedConstruction = mockConstruction(AzureServiceBusJmsCredentialSupplier.class, + (azureServiceBusJmsCredentialSupplierMocker, mockerContext) -> { + when(azureServiceBusJmsCredentialSupplierMocker.get()).thenReturn("fake-token"); + + contextRunner.run(runnerContext -> { + assertThat(runnerContext).hasSingleBean(AzureServiceBusJmsCredentialSupplier.class); + assertThat(runnerContext).hasSingleBean(ServiceBusJmsConnectionFactoryCustomizer.class); + runnerContext.getBean(AzureServiceBusJmsCredentialSupplier.class).get().equals("fake-token"); + }); + + })) { + Assertions.assertNotNull(supplierMockedConstruction); + } + + } + + @EnableConfigurationProperties + @Import({ServiceBusJmsPasswordlessConfiguration.class}) + static class ServiceBusJmsPasswordlessTestConfig { + + @Bean + @ConfigurationProperties(AzureServiceBusJmsProperties.PREFIX) + AzureServiceBusJmsProperties jmsProperties() { + return new AzureServiceBusJmsProperties(); + } + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java new file mode 100644 index 000000000000..dcfacec34df4 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.passwordless; + +import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; +import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils; +import com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider; +import com.azure.spring.cloud.service.implementation.passwordless.AzureRedisPasswordlessProperties; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MergeAzureCommonPropertiesTest { + + @Test + void testGetPropertiesFromGlobalProperties() { + AzureGlobalProperties globalProperties = new AzureGlobalProperties(); + globalProperties.getCredential().setClientId("global-client-id"); + globalProperties.getCredential().setClientSecret("global-client-secret"); + globalProperties.getCredential().setPassword("global-password"); + globalProperties.getCredential().setUsername("global-user-name"); + globalProperties.getCredential().setManagedIdentityEnabled(true); + globalProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_CHINA); + globalProperties.getProfile().setSubscriptionId("global-sub"); + globalProperties.getProfile().setTenantId("global-tenant-id"); + + AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + + AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); + + assertEquals("https://*.cacheinfra.windows.net:10225/appid/.default", result.getScopes()); + assertEquals("global-client-id", result.getCredential().getClientId()); + assertEquals("global-client-secret", result.getCredential().getClientSecret()); + assertEquals("global-password", result.getCredential().getPassword()); + assertEquals("global-user-name", result.getCredential().getUsername()); + assertEquals(false, result.getCredential().isManagedIdentityEnabled()); + assertEquals(AzureProfileOptionsProvider.CloudType.AZURE_CHINA, result.getProfile().getCloudType()); + assertEquals("global-sub", result.getProfile().getSubscriptionId()); + assertEquals("global-tenant-id", result.getProfile().getTenantId()); + } + + @Test + void testGetPropertiesFromAzurePasswordlessProperties() { + AzureGlobalProperties globalProperties = new AzureGlobalProperties(); + + AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + passwordlessProperties.setScopes("scopes-us-gov"); + passwordlessProperties.getCredential().setClientId("client-id"); + passwordlessProperties.getCredential().setClientSecret("client-secret"); + passwordlessProperties.getCredential().setPassword("password"); + passwordlessProperties.getCredential().setUsername("user-name"); + passwordlessProperties.getCredential().setManagedIdentityEnabled(true); + passwordlessProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT); + passwordlessProperties.getProfile().setSubscriptionId("sub"); + passwordlessProperties.getProfile().setTenantId("tenant-id"); + + AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); + + assertEquals("scopes-us-gov", result.getScopes()); + assertEquals("client-id", result.getCredential().getClientId()); + assertEquals("client-secret", result.getCredential().getClientSecret()); + assertEquals("password", result.getCredential().getPassword()); + assertEquals("user-name", result.getCredential().getUsername()); + assertEquals(true, result.getCredential().isManagedIdentityEnabled()); + assertEquals(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT, result.getProfile().getCloudType()); + assertEquals("sub", result.getProfile().getSubscriptionId()); + assertEquals("tenant-id", result.getProfile().getTenantId()); + + } + + @Test + void testGetPropertiesFromGlobalAndPasswordlessProperties() { + + AzureGlobalProperties globalProperties = new AzureGlobalProperties(); + globalProperties.getCredential().setClientId("global-client-id"); + globalProperties.getCredential().setClientSecret("global-client-secret"); + globalProperties.getCredential().setPassword("global-password"); + globalProperties.getCredential().setUsername("global-user-name"); + globalProperties.getCredential().setManagedIdentityEnabled(true); + globalProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_CHINA); + globalProperties.getProfile().setSubscriptionId("global-sub"); + globalProperties.getProfile().setTenantId("global-tenant-id"); + globalProperties.getClient().setApplicationId("global-application-id"); + globalProperties.getProxy().setUsername("global-proxy-username"); + globalProperties.getProxy().setPassword("global-proxy-password"); + globalProperties.getProxy().setHostname("global-proxy-hostname"); + globalProperties.getProxy().setPort(1111); + + AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + passwordlessProperties.setScopes("scope"); + passwordlessProperties.getCredential().setClientSecret("client-secret"); + passwordlessProperties.getCredential().setPassword("password"); + passwordlessProperties.getCredential().setManagedIdentityEnabled(true); + passwordlessProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT); + passwordlessProperties.getProfile().setSubscriptionId("sub"); + + AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); + + assertEquals("scope", result.getScopes()); + assertEquals("global-client-id", result.getCredential().getClientId()); + assertEquals("client-secret", result.getCredential().getClientSecret()); + assertEquals("password", result.getCredential().getPassword()); + assertEquals("global-user-name", result.getCredential().getUsername()); + assertEquals(true, result.getCredential().isManagedIdentityEnabled()); + assertEquals(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT, result.getProfile().getCloudType()); + assertEquals("sub", result.getProfile().getSubscriptionId()); + assertEquals("global-tenant-id", result.getProfile().getTenantId()); + } +} diff --git a/sdk/spring/spring-cloud-azure-core/pom.xml b/sdk/spring/spring-cloud-azure-core/pom.xml index 6b4d2ca24c1b..9f1f5ffcdc3b 100644 --- a/sdk/spring/spring-cloud-azure-core/pom.xml +++ b/sdk/spring/spring-cloud-azure-core/pom.xml @@ -62,6 +62,13 @@ 1.11.2 + + com.azure + azure-identity-extensions + 1.1.1 + true + + com.azure azure-storage-blob diff --git a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtils.java b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtils.java new file mode 100644 index 000000000000..54df0ceecbe7 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtils.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.core.implementation.util; + +import com.azure.spring.cloud.core.properties.AzureProperties; +import com.azure.spring.cloud.core.properties.PasswordlessProperties; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; + +import java.beans.PropertyDescriptor; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +/** + * Util class for AzurePasswordlessProperties. + */ +public final class AzurePasswordlessPropertiesUtils { + + private AzurePasswordlessPropertiesUtils() { + + } + + /** + * Copy common properties from source {@link AzureProperties} object to target {@link T extends PasswordlessProperties} object. + * If a field x.y.z exists in both source and target object, the source value will override the target value. + * + * @param source The source {@link AzureProperties} object. + * @param target The target object. + * @param The type of the target that extends PasswordlessProperties. + */ + public static void copyAzureCommonProperties(AzureProperties source, T target) { + // call explicitly for these fields could be defined as final + + BeanUtils.copyProperties(source.getProfile(), target.getProfile()); + BeanUtils.copyProperties(source.getProfile().getEnvironment(), target.getProfile().getEnvironment()); + BeanUtils.copyProperties(source.getCredential(), target.getCredential()); + + } + + /** + * Copy common properties from source {@link PasswordlessProperties} object to target {@link T extends PasswordlessProperties} object. Ignore the source + * value if it is null. + * + * @param source The source {@link PasswordlessProperties} object. + * @param target The target object. + * @param The type of the target that extends PasswordlessProperties. + */ + public static void copyAzureCommonPropertiesIgnoreNull(PasswordlessProperties source, T target) { + + copyPropertiesIgnoreNull(source.getProfile(), target.getProfile()); + copyPropertiesIgnoreNull(source.getProfile().getEnvironment(), target.getProfile().getEnvironment()); + copyPropertiesIgnoreNull(source.getCredential(), target.getCredential()); + + target.setScopes(source.getScopes()); + target.setPasswordlessEnabled(source.isPasswordlessEnabled()); + } + + /** + * Merge properties from a {@link AzureProperties} object and a {@link PasswordlessProperties} object. If a same property appears in both two objects, the + * value from the latter will take precedence. + * @param defaultProperties The default properties, the merge result will take value from this property as default. + * @param properties The overridden properties, the merge result will take value from this if a same property + * appears in two property objects. + * @param target The merge result. + * @param The type of the merge result. + */ + public static void mergeAzureCommonProperties(AzureProperties defaultProperties, + PasswordlessProperties properties, + T target) { + copyAzureCommonProperties(defaultProperties, target); + copyAzureCommonPropertiesIgnoreNull(properties, target); + } + + /** + * Copy common properties from source object to target object. Ignore the source value if it is null. + * + * @param source The source object. + * @param target The target object. + */ + public static void copyPropertiesIgnoreNull(Object source, Object target) { + BeanUtils.copyProperties(source, target, findNullPropertyNames(source)); + } + + private static String[] findPropertyNames(Object source, Predicate predicate) { + final Set emptyNames = new HashSet<>(); + + final BeanWrapper beanWrapper = new BeanWrapperImpl(source); + PropertyDescriptor[] pds = beanWrapper.getPropertyDescriptors(); + + for (PropertyDescriptor pd : pds) { + Object srcValue = beanWrapper.getPropertyValue(pd.getName()); + if (predicate.test(srcValue)) { + emptyNames.add(pd.getName()); + } + } + return emptyNames.toArray(new String[0]); + } + + private static String[] findNullPropertyNames(Object source) { + return findPropertyNames(source, Objects::isNull); + } + +} diff --git a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzureSpringIdentifier.java b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzureSpringIdentifier.java index a4a8333f10c8..42e2c1712101 100644 --- a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzureSpringIdentifier.java +++ b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/implementation/util/AzureSpringIdentifier.java @@ -55,6 +55,7 @@ private AzureSpringIdentifier() { * Azure Spring ServiceBus */ public static final String AZURE_SPRING_SERVICE_BUS = "az-sp-bus/" + VERSION; + public static final String AZURE_SPRING_PASSWORDLESS_SERVICE_BUS = "az-sp-pl-sb/" + VERSION; /** * Azure Spring Storage Blob diff --git a/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/properties/PasswordlessProperties.java b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/properties/PasswordlessProperties.java new file mode 100644 index 000000000000..2389c228a364 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-core/src/main/java/com/azure/spring/cloud/core/properties/PasswordlessProperties.java @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.core.properties; + +import com.azure.identity.extensions.implementation.enums.AuthProperty; +import com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider; +import com.azure.spring.cloud.core.provider.authentication.TokenCredentialOptionsProvider; + +import java.util.Properties; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Unified properties for Azure passwordless clients. + */ +public interface PasswordlessProperties extends TokenCredentialOptionsProvider, AzureProfileOptionsProvider { + + /** + * Get the scopes required for the access token. + * + * @return scopes required for the access token + */ + String getScopes(); + + /** + * Set the scopes to get the access token. + * + * @param scopes the scopes required for the access token + */ + void setScopes(String scopes); + + /** + * Whether to enable connections authenticating with Azure AD, default is false. + * + * @return Whether to enable connections authenticating with Azure AD. + */ + boolean isPasswordlessEnabled(); + + /** + * Set the passwordlessEnabled value. + * + * @param passwordlessEnabled the passwordlessEnabled + */ + void setPasswordlessEnabled(boolean passwordlessEnabled); + + /** + * Convert {@link PasswordlessProperties} to {@link Properties}. + * @return converted {@link Properties} instance + */ + default Properties toPasswordlessProperties() { + Properties target = new Properties(); + for (AzurePasswordlessPropertiesMapping m : AzurePasswordlessPropertiesMapping.values()) { + if (m.getter.apply(this) != null) { + m.setter.accept(target, m.getter.apply(this)); + } + } + return target; + } + + /** + * A mapping util used to convert a {@link PasswordlessProperties} instance to a {@link Properties} instance. + */ + enum AzurePasswordlessPropertiesMapping { + + /** + * Getter function and setter biConsumer for scopes. + */ + scopes(p -> p.getScopes(), + (p, s) -> p.setProperty(AuthProperty.SCOPES.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for clientCertificatePassword. + */ + clientCertificatePassword(p -> p.getCredential().getClientCertificatePassword(), + (p, s) -> p.setProperty(AuthProperty.CLIENT_CERTIFICATE_PASSWORD.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for clientCertificatePath. + */ + clientCertificatePath(p -> p.getCredential().getClientCertificatePath(), + (p, s) -> p.setProperty(AuthProperty.CLIENT_CERTIFICATE_PATH.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for clientId. + */ + clientId(p -> p.getCredential().getClientId(), + (p, s) -> p.setProperty(AuthProperty.CLIENT_ID.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for clientSecret. + */ + clientSecret(p -> p.getCredential().getClientSecret(), + (p, s) -> p.setProperty(AuthProperty.CLIENT_SECRET.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for managedIdentityEnabled. + */ + managedIdentityEnabled(p -> String.valueOf(p.getCredential().isManagedIdentityEnabled()), + (p, s) -> p.setProperty(AuthProperty.MANAGED_IDENTITY_ENABLED.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for password. + */ + password(p -> p.getCredential().getPassword(), + (p, s) -> p.setProperty(AuthProperty.PASSWORD.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for username. + */ + username(p -> p.getCredential().getUsername(), + (p, s) -> p.setProperty(AuthProperty.USERNAME.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for tenantId. + */ + tenantId(p -> p.getProfile().getTenantId(), + (p, s) -> p.setProperty(AuthProperty.TENANT_ID.getPropertyKey(), s)), + + /** + * Getter function and setter biConsumer for authorityHost. + */ + authorityHost(p -> p.getProfile().getEnvironment().getActiveDirectoryEndpoint(), + (p, s) -> p.setProperty(AuthProperty.AUTHORITY_HOST.getPropertyKey(), s)); + + private Function getter; + private BiConsumer setter; + + AzurePasswordlessPropertiesMapping(Function getter, BiConsumer setter) { + this.getter = getter; + this.setter = setter; + } + + } +} + diff --git a/sdk/spring/spring-cloud-azure-core/src/test/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtilsTest.java b/sdk/spring/spring-cloud-azure-core/src/test/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtilsTest.java new file mode 100644 index 000000000000..dc552df767f7 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-core/src/test/java/com/azure/spring/cloud/core/implementation/util/AzurePasswordlessPropertiesUtilsTest.java @@ -0,0 +1,385 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.core.implementation.util; + +import com.azure.core.management.AzureEnvironment; +import com.azure.spring.cloud.core.properties.AzureProperties; +import com.azure.spring.cloud.core.properties.PasswordlessProperties; +import com.azure.spring.cloud.core.properties.authentication.TokenCredentialProperties; +import com.azure.spring.cloud.core.properties.client.ClientProperties; +import com.azure.spring.cloud.core.properties.profile.AzureProfileProperties; +import com.azure.spring.cloud.core.properties.proxy.ProxyProperties; +import com.azure.spring.cloud.core.properties.retry.RetryProperties; +import com.azure.spring.cloud.core.provider.RetryOptionsProvider; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE; +import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE_CHINA; +import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.OTHER; +import static com.azure.spring.cloud.core.provider.RetryOptionsProvider.RetryMode.FIXED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AzurePasswordlessPropertiesUtilsTest { + + @Test + void testCopyPropertiesToNewObjectShouldEqual() { + AzurePropertiesA source = new AzurePropertiesA(); + source.client.setApplicationId("client-application-id-A"); + source.profile.setCloudType(AZURE_CHINA); + source.profile.setTenantId("profile-tenant-id-A"); + source.profile.setSubscriptionId("profile-sub-id-A"); + source.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A"); + source.proxy.setType("proxy-type-A"); + source.proxy.setHostname("proxy-hostname-A"); + source.proxy.setPort(1234); + source.proxy.setUsername("proxy-username-A"); + source.proxy.setPassword("proxy-password-A"); + source.retry.setMode(FIXED); + source.retry.getExponential().setMaxRetries(3); + source.retry.getExponential().setBaseDelay(Duration.ofSeconds(4)); + source.retry.getExponential().setMaxDelay(Duration.ofSeconds(5)); + source.retry.getFixed().setDelay(Duration.ofSeconds(6)); + source.retry.getFixed().setMaxRetries(7); + source.credential.setClientId("credential-client-id-A"); + source.credential.setClientSecret("credential-client-secret-A"); + source.credential.setClientCertificatePath("credential-client-cert-path-A"); + source.credential.setClientCertificatePassword("credential-client-cert-password-A"); + source.credential.setUsername("credential-username-A"); + source.credential.setPassword("credential-password-A"); + source.credential.setManagedIdentityEnabled(true); + + final PasswordlessPropertiesB target = new PasswordlessPropertiesB(); + AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target); + + assertEquals(AZURE_CHINA, target.profile.getCloudType()); + assertEquals("profile-tenant-id-A", target.profile.getTenantId()); + assertEquals("profile-sub-id-A", target.profile.getSubscriptionId()); + assertEquals("aad-endpoint-A", target.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-A", target.credential.getClientId()); + assertEquals("credential-client-secret-A", target.credential.getClientSecret()); + assertEquals("credential-client-cert-path-A", target.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-A", target.credential.getClientCertificatePassword()); + assertEquals("credential-username-A", target.credential.getUsername()); + assertEquals("credential-password-A", target.credential.getPassword()); + + } + + @Test + void testCopyPropertiesToObjectWithSameFieldsSetShouldOverrideWithNull() { + PasswordlessPropertiesB target = new PasswordlessPropertiesB(); + target.profile.setCloudType(AZURE); + target.profile.setTenantId("profile-tenant-id-B"); + target.profile.setSubscriptionId("profile-sub-id-B"); + target.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B"); + target.credential.setClientId("credential-client-id-B"); + target.credential.setClientSecret("credential-client-secret-B"); + target.credential.setClientCertificatePath("credential-client-cert-path-B"); + target.credential.setClientCertificatePassword("credential-client-cert-password-B"); + target.credential.setUsername("credential-username-B"); + target.credential.setPassword("credential-password-B"); + target.credential.setManagedIdentityEnabled(true); + + // assert properties have been set correctly to target + assertEquals(AZURE, target.profile.getCloudType()); + assertEquals("profile-tenant-id-B", target.profile.getTenantId()); + assertEquals("profile-sub-id-B", target.profile.getSubscriptionId()); + assertEquals("aad-endpoint-B", target.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-B", target.credential.getClientId()); + assertEquals("credential-client-secret-B", target.credential.getClientSecret()); + assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword()); + assertEquals("credential-username-B", target.credential.getUsername()); + assertEquals("credential-password-B", target.credential.getPassword()); + + AzurePropertiesA source = new AzurePropertiesA(); + source.client.setApplicationId("client-application-id-A"); + source.profile.setCloudType(AZURE_CHINA); + source.profile.setTenantId("profile-tenant-id-A"); + source.proxy.setHostname("proxy-hostname-A"); + source.retry.getExponential().setMaxRetries(13); + source.retry.getExponential().setMaxDelay(Duration.ofSeconds(14)); + source.credential.setClientId("credential-client-id-A"); + + AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target); + + // assert properties have been set correctly to target after copy + assertEquals(AZURE_CHINA, target.profile.getCloudType()); + assertEquals("profile-tenant-id-A", target.profile.getTenantId()); + assertNull(target.profile.getSubscriptionId()); + assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryEndpoint(), target.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-A", target.credential.getClientId()); + assertNull(target.credential.getClientSecret()); + assertNull(target.credential.getClientCertificatePath()); + assertNull(target.credential.getClientCertificatePassword()); + assertNull(target.credential.getUsername()); + assertNull(target.credential.getPassword()); + } + + @Test + void testCopyPropertiesIgnoresNullToObjectWithSameFieldsSetShouldOverrideWithoutNull() { + PasswordlessPropertiesB target = new PasswordlessPropertiesB(); + target.profile.setCloudType(AZURE); + target.profile.setTenantId("profile-tenant-id-B"); + target.profile.setSubscriptionId("profile-sub-id-B"); + target.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B"); + target.credential.setClientId("credential-client-id-B"); + target.credential.setClientSecret("credential-client-secret-B"); + target.credential.setClientCertificatePath("credential-client-cert-path-B"); + target.credential.setClientCertificatePassword("credential-client-cert-password-B"); + target.credential.setUsername("credential-username-B"); + target.credential.setPassword("credential-password-B"); + target.credential.setManagedIdentityEnabled(true); + + // assert properties have been set correctly to target + assertEquals(AZURE, target.profile.getCloudType()); + assertEquals("profile-tenant-id-B", target.profile.getTenantId()); + assertEquals("profile-sub-id-B", target.profile.getSubscriptionId()); + assertEquals("aad-endpoint-B", target.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-B", target.credential.getClientId()); + assertEquals("credential-client-secret-B", target.credential.getClientSecret()); + assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword()); + assertEquals("credential-username-B", target.credential.getUsername()); + assertEquals("credential-password-B", target.credential.getPassword()); + + PasswordlessPropertiesB source = new PasswordlessPropertiesB(); + source.profile.setCloudType(AZURE_CHINA); + source.profile.setTenantId("profile-tenant-id-A"); + source.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A"); + source.credential.setClientId("credential-client-id-A"); + source.setPasswordlessEnabled(true); + source.setScopes("fake-scopes"); + + AzurePasswordlessPropertiesUtils.copyAzureCommonPropertiesIgnoreNull(source, target); + + // assert properties have been set correctly to target after copy + assertEquals(AZURE_CHINA, target.profile.getCloudType()); + assertEquals("profile-tenant-id-A", target.profile.getTenantId()); + assertEquals("profile-sub-id-B", target.profile.getSubscriptionId()); + assertEquals("aad-endpoint-A", target.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-A", target.credential.getClientId()); + assertEquals("credential-client-secret-B", target.credential.getClientSecret()); + assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword()); + assertEquals("credential-username-B", target.credential.getUsername()); + assertEquals("credential-password-B", target.credential.getPassword()); + assertTrue(target.isPasswordlessEnabled()); + assertEquals("fake-scopes", target.getScopes()); + + } + + @Test + void testMergePropertiesObjectWithSameFieldsSetShouldTakeLater() { + AzurePropertiesA defaultProperties = new AzurePropertiesA(); + defaultProperties.client.setApplicationId("client-application-id-B"); + defaultProperties.profile.setCloudType(AZURE); + defaultProperties.profile.setTenantId("profile-tenant-id-B"); + defaultProperties.profile.setSubscriptionId("profile-sub-id-B"); + defaultProperties.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B"); + defaultProperties.proxy.setType("proxy-type-B"); + defaultProperties.proxy.setHostname("proxy-hostname-B"); + defaultProperties.proxy.setPort(1234); + defaultProperties.proxy.setUsername("proxy-username-B"); + defaultProperties.proxy.setPassword("proxy-password-B"); + defaultProperties.retry.setMode(FIXED); + defaultProperties.retry.getExponential().setMaxRetries(3); + defaultProperties.retry.getExponential().setBaseDelay(Duration.ofSeconds(4)); + defaultProperties.retry.getExponential().setMaxDelay(Duration.ofSeconds(5)); + defaultProperties.retry.getFixed().setDelay(Duration.ofSeconds(6)); + defaultProperties.retry.getFixed().setMaxRetries(7); + defaultProperties.credential.setClientId("credential-client-id-B"); + defaultProperties.credential.setClientSecret("credential-client-secret-B"); + defaultProperties.credential.setClientCertificatePath("credential-client-cert-path-B"); + defaultProperties.credential.setClientCertificatePassword("credential-client-cert-password-B"); + defaultProperties.credential.setUsername("credential-username-B"); + defaultProperties.credential.setPassword("credential-password-B"); + defaultProperties.credential.setManagedIdentityEnabled(true); + + // assert properties have been set correctly to defaultProperties + assertEquals(AZURE, defaultProperties.profile.getCloudType()); + assertEquals("profile-tenant-id-B", defaultProperties.profile.getTenantId()); + assertEquals("profile-sub-id-B", defaultProperties.profile.getSubscriptionId()); + assertEquals("aad-endpoint-B", defaultProperties.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), defaultProperties.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-B", defaultProperties.credential.getClientId()); + assertEquals("credential-client-secret-B", defaultProperties.credential.getClientSecret()); + assertEquals("credential-client-cert-path-B", defaultProperties.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-B", defaultProperties.credential.getClientCertificatePassword()); + assertEquals("credential-username-B", defaultProperties.credential.getUsername()); + assertEquals("credential-password-B", defaultProperties.credential.getPassword()); + + PasswordlessPropertiesB propertiesToOverride = new PasswordlessPropertiesB(); + propertiesToOverride.profile.setCloudType(AZURE_CHINA); + propertiesToOverride.profile.setTenantId("profile-tenant-id-A"); + propertiesToOverride.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A"); + propertiesToOverride.credential.setClientId("credential-client-id-A"); + propertiesToOverride.setScopes("fake-scopes-1"); + + PasswordlessPropertiesB result = new PasswordlessPropertiesB(); + AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(defaultProperties, propertiesToOverride, result); + + // assert properties have been set correctly to result after copy + assertEquals(AZURE_CHINA, result.profile.getCloudType()); + assertEquals("profile-tenant-id-A", result.profile.getTenantId()); + assertEquals("profile-sub-id-B", result.profile.getSubscriptionId()); + assertEquals("aad-endpoint-A", result.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), result.profile.getEnvironment().getActiveDirectoryGraphApiVersion()); + assertEquals("credential-client-id-A", result.credential.getClientId()); + assertEquals("credential-client-secret-B", result.credential.getClientSecret()); + assertEquals("credential-client-cert-path-B", result.credential.getClientCertificatePath()); + assertEquals("credential-client-cert-password-B", result.credential.getClientCertificatePassword()); + assertEquals("credential-username-B", result.credential.getUsername()); + assertEquals("credential-password-B", result.credential.getPassword()); + assertEquals("fake-scopes-1", result.getScopes()); + assertFalse(result.isPasswordlessEnabled()); + } + + @Test + void testCopyPropertiesIgnoreNullToObjectWithDifferentFieldsSetShouldMerge() { + PasswordlessPropertiesB source = new PasswordlessPropertiesB(); + source.credential.setClientId("client-id-A"); + source.getProfile().setCloudType(AZURE); + source.setScopes("fake-scopes"); + source.setPasswordlessEnabled(true); + + PasswordlessPropertiesB target = new PasswordlessPropertiesB(); + target.credential.setClientSecret("client-secret-B"); + target.profile.setCloudType(OTHER); + target.profile.getEnvironment().setActiveDirectoryEndpoint("abc"); + + assertTrue(source.isPasswordlessEnabled()); + assertFalse(target.isPasswordlessEnabled()); + assertEquals("fake-scopes", source.getScopes()); + assertNull(target.getScopes()); + + assertEquals(AZURE, source.getProfile().getCloudType()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(), + source.profile.getEnvironment().getActiveDirectoryEndpoint()); + assertEquals("client-secret-B", target.credential.getClientSecret()); + + AzurePasswordlessPropertiesUtils.copyAzureCommonPropertiesIgnoreNull(source, target); + + // target properties should be merged properties from source + target + assertEquals("client-id-A", target.credential.getClientId()); + assertEquals("client-secret-B", target.credential.getClientSecret()); + assertEquals(AZURE, source.getProfile().getCloudType()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(), + target.profile.getEnvironment().getActiveDirectoryEndpoint()); + + + // source properties should not be updated + assertNull(source.credential.getClientSecret()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(), + source.profile.getEnvironment().getActiveDirectoryEndpoint()); + + assertTrue(source.isPasswordlessEnabled()); + assertTrue(target.isPasswordlessEnabled()); + assertEquals("fake-scopes", source.getScopes()); + assertEquals("fake-scopes", target.getScopes()); + } + + @Test + void testCopyPropertiesSourceNotChanged() { + AzurePropertiesA source = new AzurePropertiesA(); + source.credential.setClientId("client-id-A"); + source.getProfile().setCloudType(AZURE); + + PasswordlessPropertiesB target = new PasswordlessPropertiesB(); + + AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target); + + assertEquals("client-id-A", target.credential.getClientId()); + + // Update target will not affect source + target.profile.setCloudType(OTHER); + target.profile.getEnvironment().setActiveDirectoryEndpoint("abc"); + + assertNull(source.retry.getExponential().getBaseDelay()); + assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(), + source.profile.getEnvironment().getActiveDirectoryEndpoint()); + } + + + static class AzurePropertiesA implements AzureProperties, RetryOptionsProvider { + + private final ClientProperties client = new ClientProperties(); + private final ProxyProperties proxy = new ProxyProperties(); + private final RetryProperties retry = new RetryProperties(); + private final TokenCredentialProperties credential = new TokenCredentialProperties(); + private final AzureProfileProperties profile = new AzureProfileProperties(); + + @Override + public ClientProperties getClient() { + return client; + } + + @Override + public ProxyProperties getProxy() { + return proxy; + } + + @Override + public RetryProperties getRetry() { + return retry; + } + + @Override + public TokenCredentialProperties getCredential() { + return credential; + } + + @Override + public AzureProfileProperties getProfile() { + return profile; + } + } + + static class PasswordlessPropertiesB implements PasswordlessProperties { + + private final TokenCredentialProperties credential = new TokenCredentialProperties(); + private final AzureProfileProperties profile = new AzureProfileProperties(); + private String scopes; + private boolean passwordlessEnabled = false; + + @Override + public TokenCredentialProperties getCredential() { + return credential; + } + + @Override + public AzureProfileProperties getProfile() { + return profile; + } + + @Override + public String getScopes() { + return scopes; + } + + @Override + public void setScopes(String scopes) { + this.scopes = scopes; + } + + @Override + public boolean isPasswordlessEnabled() { + return passwordlessEnabled; + } + + @Override + public void setPasswordlessEnabled(boolean passwordlessEnabled) { + this.passwordlessEnabled = passwordlessEnabled; + } + } +} diff --git a/sdk/spring/spring-cloud-azure-integration-tests/src/test/java/com/azure/spring/cloud/integration/tests/servicebus/jms/ServiceBusJmsPasswordlessIT.java b/sdk/spring/spring-cloud-azure-integration-tests/src/test/java/com/azure/spring/cloud/integration/tests/servicebus/jms/ServiceBusJmsPasswordlessIT.java new file mode 100644 index 000000000000..76a5ffdd4651 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-integration-tests/src/test/java/com/azure/spring/cloud/integration/tests/servicebus/jms/ServiceBusJmsPasswordlessIT.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.cloud.integration.tests.servicebus.jms; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.concurrent.Exchanger; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ActiveProfiles("servicebus-jms-passwordless") +public class ServiceBusJmsPasswordlessIT { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBusJmsPasswordlessIT.class); + private static final String DATA = "service bus jms passwordless test"; + private static final String QUEUE_NAME = "passwordless_que001"; + private final Exchanger EXCHANGER = new Exchanger<>(); + + @Autowired + private JmsTemplate jmsTemplate; + + @Test + @Timeout(70) + void testServiceBusJmsOperation() throws InterruptedException { + LOGGER.info("ServiceBusJmsPasswordlessIT begin."); + jmsTemplate.convertAndSend(QUEUE_NAME, DATA); + LOGGER.info("Send message: {}", DATA); + String msg = EXCHANGER.exchange(null); + Assertions.assertEquals(DATA, msg); + LOGGER.info("ServiceBusJmsPasswordlessIT end."); + } + + @JmsListener(destination = QUEUE_NAME, containerFactory = "jmsListenerContainerFactory") + void receiveQueueMessage(String message) throws InterruptedException { + LOGGER.info("Received message from queue: {}", message); + EXCHANGER.exchange(message); + } +} diff --git a/sdk/spring/spring-cloud-azure-integration-tests/src/test/resources/application-servicebus-jms-passwordless.yml b/sdk/spring/spring-cloud-azure-integration-tests/src/test/resources/application-servicebus-jms-passwordless.yml new file mode 100644 index 000000000000..14a77cc58923 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-integration-tests/src/test/resources/application-servicebus-jms-passwordless.yml @@ -0,0 +1,8 @@ +spring: + jms: + servicebus: + enabled: true + pricing-tier: standard + passwordless-enabled: true + namespace: ${AZURE_SERVICE_BUS_NAMESPACE} + diff --git a/sdk/spring/spring-cloud-azure-integration-tests/test-resources/servicebus/test-resources.json b/sdk/spring/spring-cloud-azure-integration-tests/test-resources/servicebus/test-resources.json index 636ef04e1bbf..fce64f635082 100644 --- a/sdk/spring/spring-cloud-azure-integration-tests/test-resources/servicebus/test-resources.json +++ b/sdk/spring/spring-cloud-azure-integration-tests/test-resources/servicebus/test-resources.json @@ -84,6 +84,31 @@ "enableExpress": false } }, + { + "type": "Microsoft.ServiceBus/namespaces/queues", + "apiVersion": "2022-01-01-preview", + "name": "[concat(variables('azureServiceBusNamespaceName'), '/passwordless_que001')]", + "location": "[variables('location')]", + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', variables('azureServiceBusNamespaceName'))]" + ], + "properties": { + "maxMessageSizeInKilobytes": 256, + "lockDuration": "PT30S", + "maxSizeInMegabytes": 1024, + "requiresDuplicateDetection": false, + "requiresSession": false, + "defaultMessageTimeToLive": "P14D", + "deadLetteringOnMessageExpiration": false, + "enableBatchedOperations": true, + "duplicateDetectionHistoryTimeWindow": "PT10M", + "maxDeliveryCount": 10, + "status": "Active", + "autoDeleteOnIdle": "P10675199DT2H48M5.4775807S", + "enablePartitioning": false, + "enableExpress": false + } + }, { "type": "Microsoft.ServiceBus/namespaces/queues", "apiVersion": "2022-01-01-preview", diff --git a/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureJdbcPasswordlessProperties.java b/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureJdbcPasswordlessProperties.java new file mode 100644 index 000000000000..689b13a25c0a --- /dev/null +++ b/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureJdbcPasswordlessProperties.java @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.service.implementation.passwordless; + +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; + +/** + * Configuration properties for passwordless connections with Azure Database. + */ +public class AzureJdbcPasswordlessProperties implements PasswordlessProperties { + + private static final String JDBC_SCOPE_AZURE = "https://ossrdbms-aad.database.windows.net/.default"; + private static final String JDBC_SCOPE_AZURE_CHINA = "https://ossrdbms-aad.database.chinacloudapi.cn/.default"; + private static final String JDBC_SCOPE_AZURE_GERMANY = "https://ossrdbms-aad.database.cloudapi.de/.default"; + private static final String JDBC_SCOPE_AZURE_US_GOVERNMENT = "https://ossrdbms-aad.database.usgovcloudapi.net/.default"; + + private static final Map JDBC_SCOPE_MAP = new HashMap() { + { + put(CloudType.AZURE, JDBC_SCOPE_AZURE); + put(CloudType.AZURE_CHINA, JDBC_SCOPE_AZURE_CHINA); + put(CloudType.AZURE_GERMANY, JDBC_SCOPE_AZURE_GERMANY); + put(CloudType.AZURE_US_GOVERNMENT, JDBC_SCOPE_AZURE_US_GOVERNMENT); + } + }; + + /** + * Whether to enable supporting azure identity token credentials, by default is false. + * + * If the passwordlessEnabled is true, but the 'spring.datasource.password' property is not empty, it will still use username/password to authenticate connections. + * To use passwordless connections, you need to remove 'spring.datasource.password' property. + */ + private boolean passwordlessEnabled = false; + + private AzureProfileProperties profile = new AzureProfileProperties(); + + private String scopes; + + private TokenCredentialProperties credential = new TokenCredentialProperties(); + + /** + * Get the scopes required for the access token. + * + * @return scopes required for the access token + */ + @Override + public String getScopes() { + return this.scopes == null ? getDefaultScopes() : this.scopes; + } + + /** + * Set the scopes required for the access token. + * + * @param scopes the scopes required for the access token + */ + public void setScopes(String scopes) { + this.scopes = scopes; + } + + /** + * Whether to enable connections authenticating with Azure AD, default is false. + * + * @return enable connections authenticating with Azure AD if true, otherwise false. + */ + @Override + public boolean isPasswordlessEnabled() { + return this.passwordlessEnabled; + } + + /** + * Set the value to enable/disable connections authenticating with Azure AD. + * If not set, by default the value is false. + * + * @param passwordlessEnabled the passwordlessEnabled + */ + @Override + public void setPasswordlessEnabled(boolean passwordlessEnabled) { + this.passwordlessEnabled = passwordlessEnabled; + } + + private String getDefaultScopes() { + return JDBC_SCOPE_MAP.getOrDefault(getProfile().getCloudType(), JDBC_SCOPE_AZURE); + } + + /** + * Get the profile + * @return the profile + */ + @Override + public AzureProfileProperties getProfile() { + return profile; + } + + /** + * Set the profile + * @param profile the profile properties related to an Azure subscription + */ + public void setProfile(AzureProfileProperties profile) { + this.profile = profile; + } + + /** + * Get the credential properties. + * + * @return the credential properties. + */ + @Override + public TokenCredentialProperties getCredential() { + return credential; + } + + /** + * Set the credential properties. + * + * @param credential the credential properties + */ + public void setCredential(TokenCredentialProperties credential) { + this.credential = credential; + } +} diff --git a/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureKafkaPasswordlessProperties.java b/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureKafkaPasswordlessProperties.java new file mode 100644 index 000000000000..9b99c5f0795f --- /dev/null +++ b/sdk/spring/spring-cloud-azure-service/src/main/java/com/azure/spring/cloud/service/implementation/passwordless/AzureKafkaPasswordlessProperties.java @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.service.implementation.passwordless; + +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; + +/** + * Configuration properties for passwordless connections with Azure ServiceBus. + */ +public class AzureKafkaPasswordlessProperties implements PasswordlessProperties { + + private boolean passwordlessEnabled = false; + + private AzureProfileProperties profile = new AzureProfileProperties(); + + private TokenCredentialProperties credential = new TokenCredentialProperties(); + + /** + * Get the scopes required for the access token. + * This method is not available in AzureKafkaPasswordlessProperties, will always return null. + * + * @return null + */ + @Override + public String getScopes() { + return null; + } + + /** + * Set the scopes required for the access token. + * + * This method is not available in AzureKafkaPasswordlessProperties + */ + @Override + public void setScopes(String scopes) { + throw new RuntimeException("This method is not available in AzureKafkaPasswordlessProperties"); + } + + /** + * Whether to enable connections authenticating with Azure AD, default is false. + * + * @return enable connections authenticating with Azure AD if true, otherwise false. + */ + @Override + public boolean isPasswordlessEnabled() { + return this.passwordlessEnabled; + } + + /** + * Set the value to enable/disable connections authenticating with Azure AD. + * If not set, by default the value is false. + * + * @param passwordlessEnabled the passwordlessEnabled + */ + @Override + public void setPasswordlessEnabled(boolean passwordlessEnabled) { + this.passwordlessEnabled = passwordlessEnabled; + } + + /** + * Get the profile + * @return the profile + */ + @Override + public AzureProfileProperties getProfile() { + return profile; + } + + /** + * Set the profile + * @param profile the profile properties related to an Azure subscription + */ + public void setProfile(AzureProfileProperties profile) { + this.profile = profile; + } + + /** + * Get the credential properties. + * + * @return the credential properties. + */ + @Override + public TokenCredentialProperties getCredential() { + return credential; + } + + /** + * Set the credential properties. + * + * @param credential the credential properties + */ + public void setCredential(TokenCredentialProperties credential) { + this.credential = credential; + } +} diff --git a/sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/kafka/KafkaOAuth2AuthenticateCallbackHandlerTest.java b/sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/kafka/KafkaOAuth2AuthenticateCallbackHandlerTest.java index 03f3b5cfc03e..403828fa7bdb 100644 --- a/sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/kafka/KafkaOAuth2AuthenticateCallbackHandlerTest.java +++ b/sdk/spring/spring-cloud-azure-service/src/test/java/com/azure/spring/cloud/service/implementation/kafka/KafkaOAuth2AuthenticateCallbackHandlerTest.java @@ -11,8 +11,8 @@ import com.azure.spring.cloud.service.implementation.jaas.Jaas; import com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties; import org.apache.kafka.common.config.types.Password; -import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule; +import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; diff --git a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml index 0b318ae54255..96931348dbec 100644 --- a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml +++ b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml @@ -141,6 +141,11 @@ netty-codec-http 4.1.90.Final + + com.azure + azure-identity-extensions + 1.1.1 + From dc7270000c639f679f607bd6a13739f9abe3fca1 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 14:28:05 +0800 Subject: [PATCH 2/7] fix differences in two versions --- .../AzureServiceBusJmsCredentialSupplier.java | 2 +- .../jms/ServiceBusJmsAutoConfiguration.java | 3 +-- ...eBusJmsConnectionFactoryConfiguration.java | 1 - ...viceBusJmsConnectionFactoryCustomizer.java | 2 +- ...ServiceBusJmsConnectionFactoryFactory.java | 1 - ...erviceBusJmsPasswordlessConfiguration.java | 4 ++-- .../AzureServiceBusJmsProperties.java | 1 + ...ePasswordlessEnvironmentPostProcessor.java | 1 - .../cloud/autoconfigure/jms/package-info.java | 7 ------- .../main/resources/META-INF/spring.factories | 3 ++- .../ServiceBusJmsAutoConfigurationTests.java | 1 + ...ceBusJmsPasswordlessConfigurationTest.java | 6 +++--- .../MergeAzureCommonPropertiesTest.java | 20 +++++++++---------- 13 files changed, 22 insertions(+), 30 deletions(-) rename sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/{ => implementation}/jms/AzureServiceBusJmsCredentialSupplier.java (94%) rename sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/{ => implementation}/jms/ServiceBusJmsConnectionFactoryCustomizer.java (88%) rename sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/{ => implementation}/jms/ServiceBusJmsPasswordlessConfiguration.java (90%) delete mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java rename sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/{ => implementation}/jms/ServiceBusJmsPasswordlessConfigurationTest.java (96%) rename sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/{ => implementation}/passwordless/MergeAzureCommonPropertiesTest.java (86%) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/AzureServiceBusJmsCredentialSupplier.java similarity index 94% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/AzureServiceBusJmsCredentialSupplier.java index a54f916913b5..3d5782d7b28f 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/AzureServiceBusJmsCredentialSupplier.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/AzureServiceBusJmsCredentialSupplier.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.jms; +package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.identity.extensions.implementation.template.AzureAuthenticationTemplate; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java index 6c8e91f9ffd8..2983383dc30c 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java @@ -4,10 +4,9 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.spring.cloud.autoconfigure.implementation.condition.ConditionalOnMissingProperty; +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.resourcemanager.AzureServiceBusResourceManagerAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; -import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsPasswordlessConfiguration; import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils; import com.azure.spring.cloud.core.provider.connectionstring.ServiceConnectionStringProvider; import com.azure.spring.cloud.core.service.AzureServiceType; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java index aa9062d9825e..09eafdb8c728 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java @@ -5,7 +5,6 @@ import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; -import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import jakarta.jms.ConnectionFactory; import org.apache.commons.pool2.PooledObject; import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java similarity index 88% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java index 88515981b37f..b685c09b6dbf 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.jms; +package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java index d2f46d666e14..4711c5868629 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java @@ -5,7 +5,6 @@ import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; -import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import com.azure.spring.cloud.core.implementation.connectionstring.ServiceBusConnectionString; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.springframework.util.Assert; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java similarity index 90% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java index 537eba723c89..a225e97aa102 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.jms; +package com.azure.spring.cloud.autoconfigure.implementation.jms; -import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties; +import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import org.apache.qpid.jms.JmsConnectionExtensions; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java index 11647ed6d68f..647c23b9f581 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java @@ -14,6 +14,7 @@ import org.springframework.util.StringUtils; import java.time.Duration; +import java.util.HashMap; import java.util.Map; @ConfigurationProperties(prefix = AzureServiceBusJmsProperties.PREFIX) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java index fa46fef2af5f..6ffcfb07a20a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/AzurePasswordlessEnvironmentPostProcessor.java @@ -30,7 +30,6 @@ public class AzurePasswordlessEnvironmentPostProcessor implements EnvironmentPos public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Properties properties = new Properties(); List passwordlessCredentialSupplier = new ArrayList<>(); - passwordlessCredentialSupplier.add("azureRedisCredentialSupplier"); passwordlessCredentialSupplier.add("azureServiceBusJmsCredentialSupplier"); properties.setProperty("spring.cloud.function.ineligible-definitions", String.join(",", passwordlessCredentialSupplier)); environment.getPropertySources().addLast(new PropertiesPropertySource("passwordless", properties)); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java deleted file mode 100644 index 99724ee0a907..000000000000 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * Spring Cloud Azure's auto-configuration for Spring JMS. - */ -package com.azure.spring.cloud.autoconfigure.jms; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories index 397e250c05b6..3951cbadacc6 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,6 +1,7 @@ org.springframework.boot.env.EnvironmentPostProcessor=\ com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalConfigurationEnvironmentPostProcessor,\ -com.azure.spring.cloud.autoconfigure.implementation.keyvault.environment.KeyVaultEnvironmentPostProcessor +com.azure.spring.cloud.autoconfigure.implementation.keyvault.environment.KeyVaultEnvironmentPostProcessor,\ +com.azure.spring.cloud.autoconfigure.implementation.passwordless.AzurePasswordlessEnvironmentPostProcessor org.springframework.boot.diagnostics.FailureAnalyzer=\ com.azure.spring.cloud.autoconfigure.implementation.compatibility.AzureCompatibilityNotMetFailureAnalyzer diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java index 6e6e509836a7..c2ba620dde0a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java @@ -3,6 +3,7 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; +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.core.provider.connectionstring.StaticConnectionStringProvider; import com.azure.spring.cloud.core.service.AzureServiceType; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java similarity index 96% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java index bcbba7d264be..d96f22a2f1e0 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsPasswordlessConfigurationTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.jms; +package com.azure.spring.cloud.autoconfigure.implementation.jms; -import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; -import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties; +import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; +import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/MergeAzureCommonPropertiesTest.java similarity index 86% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/MergeAzureCommonPropertiesTest.java index dcfacec34df4..9b4db99190af 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/passwordless/MergeAzureCommonPropertiesTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/passwordless/MergeAzureCommonPropertiesTest.java @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.passwordless; +package com.azure.spring.cloud.autoconfigure.implementation.passwordless; -import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; +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.core.implementation.util.AzurePasswordlessPropertiesUtils; import com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider; -import com.azure.spring.cloud.service.implementation.passwordless.AzureRedisPasswordlessProperties; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -25,12 +25,12 @@ void testGetPropertiesFromGlobalProperties() { globalProperties.getProfile().setSubscriptionId("global-sub"); globalProperties.getProfile().setTenantId("global-tenant-id"); - AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties passwordlessProperties = new AzureServiceBusJmsProperties(); - AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties result = new AzureServiceBusJmsProperties(); AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); - assertEquals("https://*.cacheinfra.windows.net:10225/appid/.default", result.getScopes()); + assertEquals("https://servicebus.azure.net/.default", result.getScopes()); assertEquals("global-client-id", result.getCredential().getClientId()); assertEquals("global-client-secret", result.getCredential().getClientSecret()); assertEquals("global-password", result.getCredential().getPassword()); @@ -45,7 +45,7 @@ void testGetPropertiesFromGlobalProperties() { void testGetPropertiesFromAzurePasswordlessProperties() { AzureGlobalProperties globalProperties = new AzureGlobalProperties(); - AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties passwordlessProperties = new AzureServiceBusJmsProperties(); passwordlessProperties.setScopes("scopes-us-gov"); passwordlessProperties.getCredential().setClientId("client-id"); passwordlessProperties.getCredential().setClientSecret("client-secret"); @@ -56,7 +56,7 @@ void testGetPropertiesFromAzurePasswordlessProperties() { passwordlessProperties.getProfile().setSubscriptionId("sub"); passwordlessProperties.getProfile().setTenantId("tenant-id"); - AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties result = new AzureServiceBusJmsProperties(); AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); assertEquals("scopes-us-gov", result.getScopes()); @@ -89,7 +89,7 @@ void testGetPropertiesFromGlobalAndPasswordlessProperties() { globalProperties.getProxy().setHostname("global-proxy-hostname"); globalProperties.getProxy().setPort(1111); - AzureRedisPasswordlessProperties passwordlessProperties = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties passwordlessProperties = new AzureServiceBusJmsProperties(); passwordlessProperties.setScopes("scope"); passwordlessProperties.getCredential().setClientSecret("client-secret"); passwordlessProperties.getCredential().setPassword("password"); @@ -97,7 +97,7 @@ void testGetPropertiesFromGlobalAndPasswordlessProperties() { passwordlessProperties.getProfile().setCloudType(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT); passwordlessProperties.getProfile().setSubscriptionId("sub"); - AzureRedisPasswordlessProperties result = new AzureRedisPasswordlessProperties(); + AzureServiceBusJmsProperties result = new AzureServiceBusJmsProperties(); AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(globalProperties, passwordlessProperties, result); assertEquals("scope", result.getScopes()); From b6398f07834b286dae1e950d1b4897d7d308bd02 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 15:05:48 +0800 Subject: [PATCH 3/7] fix pipeline --- .../src/main/resources/revapi/revapi.json | 6 ------ .../jdbc/JdbcPropertiesBeanPostProcessor.java | 9 +++++++-- .../jms/ServiceBusJmsAutoConfiguration.java | 1 + .../jms/ServiceBusJmsConnectionFactoryConfiguration.java | 1 + .../jms/ServiceBusJmsConnectionFactoryFactory.java | 1 + .../jms/ServiceBusJmsPasswordlessConfiguration.java | 1 + .../jms/ServiceBusJmsConnectionFactoryCustomizer.java | 2 +- .../spring/cloud/autoconfigure/jms/package-info.java | 7 +++++++ .../jms/ServiceBusJmsPasswordlessConfigurationTest.java | 1 + sdk/spring/spring-cloud-azure-core/pom.xml | 2 +- 10 files changed, 21 insertions(+), 10 deletions(-) rename sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/{implementation => }/jms/ServiceBusJmsConnectionFactoryCustomizer.java (88%) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java diff --git a/eng/code-quality-reports/src/main/resources/revapi/revapi.json b/eng/code-quality-reports/src/main/resources/revapi/revapi.json index 7dba51f8db79..68b6bceed7b1 100644 --- a/eng/code-quality-reports/src/main/resources/revapi/revapi.json +++ b/eng/code-quality-reports/src/main/resources/revapi/revapi.json @@ -138,12 +138,6 @@ "old": "method void com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties::setUsername(java.lang.String)", "justification": "Remove some meaningless jms properties" }, - { - "code" : "java.annotation.attributeValueChanged", - "old" : "class com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsAutoConfiguration", - "new" : "class com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsAutoConfiguration", - "justification": "Import ServiceBusJmsPasswordlessConfiguration.class" - }, { "regex": true, "code": "java.method.numberOfParametersChanged", diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java index 669562dafe5e..a9443e2dbc64 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jdbc/JdbcPropertiesBeanPostProcessor.java @@ -27,8 +27,13 @@ import java.util.HashMap; import java.util.Map; -import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.*; -import com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties; +import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_CONNECTION_ATTRIBUTES_ATTRIBUTE_EXTENSION_VERSION; +import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.MYSQL_PROPERTY_CONNECTION_ATTRIBUTES_DELIMITER; +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.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_NAME_ASSUME_MIN_SERVER_VERSION; +import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_VALUE_ASSUME_MIN_SERVER_VERSION; import static com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider.PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java index 2983383dc30c..3268ca074020 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfiguration.java @@ -10,6 +10,7 @@ import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils; import com.azure.spring.cloud.core.provider.connectionstring.ServiceConnectionStringProvider; import com.azure.spring.cloud.core.service.AzureServiceType; +import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import jakarta.jms.ConnectionFactory; import org.apache.qpid.jms.JmsConnectionExtensions; import org.apache.qpid.jms.JmsConnectionFactory; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java index 09eafdb8c728..aa9062d9825e 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfiguration.java @@ -5,6 +5,7 @@ import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; +import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import jakarta.jms.ConnectionFactory; import org.apache.commons.pool2.PooledObject; import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java index 4711c5868629..b7b018947d83 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryFactory.java @@ -6,6 +6,7 @@ import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; import com.azure.spring.cloud.core.implementation.connectionstring.ServiceBusConnectionString; +import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java index a225e97aa102..68a0a551ec4e 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfiguration.java @@ -4,6 +4,7 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.spring.cloud.autoconfigure.implementation.jms.properties.AzureServiceBusJmsProperties; +import com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsConnectionFactoryCustomizer; import org.apache.qpid.jms.JmsConnectionExtensions; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java similarity index 88% rename from sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java rename to sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java index b685c09b6dbf..88515981b37f 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryCustomizer.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/ServiceBusJmsConnectionFactoryCustomizer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.cloud.autoconfigure.implementation.jms; +package com.azure.spring.cloud.autoconfigure.jms; import com.azure.spring.jms.ServiceBusJmsConnectionFactory; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java new file mode 100644 index 000000000000..99724ee0a907 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/jms/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Spring Cloud Azure's auto-configuration for Spring JMS. + */ +package com.azure.spring.cloud.autoconfigure.jms; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java index d96f22a2f1e0..73b40323cfbf 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsPasswordlessConfigurationTest.java @@ -5,6 +5,7 @@ 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.jms.ServiceBusJmsConnectionFactoryCustomizer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; diff --git a/sdk/spring/spring-cloud-azure-core/pom.xml b/sdk/spring/spring-cloud-azure-core/pom.xml index 9f1f5ffcdc3b..3236d8092f69 100644 --- a/sdk/spring/spring-cloud-azure-core/pom.xml +++ b/sdk/spring/spring-cloud-azure-core/pom.xml @@ -65,7 +65,7 @@ com.azure azure-identity-extensions - 1.1.1 + 1.1.4 true From 097316a86b3a26fda381f4f43fe88ac9d289837c Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 15:10:46 +0800 Subject: [PATCH 4/7] remove unnecessary javadocs --- .../AzureServiceBusJmsProperties.java | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java index 647c23b9f581..e732a0fc627f 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/properties/AzureServiceBusJmsProperties.java @@ -104,18 +104,10 @@ public JmsPoolConnectionFactoryProperties getPool() { return pool; } - /** - * Get the connection string to connect to a Service Bus namespace. - * @return the connection string to connect to a Service Bus namespace. - */ public String getConnectionString() { return connectionString; } - /** - * Set the connection string to connect to a Service Bus namespace. - * @param connectionString the connection string to connect to a Service Bus namespace. - */ public void setConnectionString(String connectionString) { this.connectionString = connectionString; } @@ -152,102 +144,50 @@ public PrefetchPolicy getPrefetchPolicy() { return prefetchPolicy; } - /** - * Get the Service Bus namespace. - * @return the Service Bus namespace. - */ public String getNamespace() { return namespace; } - /** - * Set the Service Bus namespace. - * @param namespace the Service Bus namespace. - */ public void setNamespace(String namespace) { this.namespace = namespace; } - /** - * Get the scopes required for the access token. - * - * @return scopes required for the access token - */ @Override public String getScopes() { return this.scopes == null ? getDefaultScopes() : this.scopes; } - /** - * Set the scopes required for the access token. - * - * @param scopes the scopes required for the access token - */ public void setScopes(String scopes) { this.scopes = scopes; } - /** - * Whether to enable connections authenticating with Azure AD, default is false. - * - * @return enable connections authenticating with Azure AD if true, otherwise false. - */ @Override public boolean isPasswordlessEnabled() { return passwordlessEnabled; } - /** - * Set the value to enable/disable connections authenticating with Azure AD. - * If not set, by default the value is false. - * - * @param passwordlessEnabled the passwordlessEnabled - */ public void setPasswordlessEnabled(boolean passwordlessEnabled) { this.passwordlessEnabled = passwordlessEnabled; } - /** - * Get the profile - * @return the profile - */ @Override public AzureProfileConfigurationProperties getProfile() { return profile; } - /** - * Set the profile - * @param profile the profile properties related to an Azure subscription - */ public void setProfile(AzureProfileConfigurationProperties profile) { this.profile = profile; } - /** - * Get the credential properties. - * - * @return the credential properties. - */ @Override public TokenCredentialConfigurationProperties getCredential() { return credential; } - /** - * Set the credential properties. - * - * @param credential the credential properties - */ public void setCredential(TokenCredentialConfigurationProperties credential) { this.credential = credential; } - /** - * Validate spring.jms.servicebus related properties. - * - * @throws IllegalArgumentException If connectionString is empty. - */ @Override public void afterPropertiesSet() throws Exception { if (isPasswordlessEnabled()) { From f6bc429365e6f8068a9f6f1c0f4772f67fa5cf68 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 15:18:07 +0800 Subject: [PATCH 5/7] fix pipeline --- .../implementation/jms/ServiceBusJmsAutoConfigurationTests.java | 1 + sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java index c2ba620dde0a..5a7860c16b58 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsAutoConfigurationTests.java @@ -130,6 +130,7 @@ void autoconfigurationEnabledAndContextSuccessWithNonpremiumTier(String pricingT assertThat(context).hasSingleBean(ConnectionFactory.class); assertThat(context).hasSingleBean(JmsTemplate.class); assertThat(context).hasSingleBean(DefaultJmsListenerContainerFactoryConfigurer.class); + assertThat(context).hasBean("jmsListenerContainerFactory"); assertThat(context).hasBean("topicJmsListenerContainerFactory"); assertThat(context).doesNotHaveBean("amqpOpenPropertiesCustomizer"); }); diff --git a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml index 96931348dbec..ea68ad85c0d5 100644 --- a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml +++ b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml @@ -144,7 +144,7 @@ com.azure azure-identity-extensions - 1.1.1 + 1.1.4 From 9eae761d7db2b38b3422ce31d73f10068de699a9 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 15:30:35 +0800 Subject: [PATCH 6/7] fix versions --- sdk/spring/spring-cloud-azure-core/pom.xml | 2 +- sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-core/pom.xml b/sdk/spring/spring-cloud-azure-core/pom.xml index 3236d8092f69..5906b4e047d7 100644 --- a/sdk/spring/spring-cloud-azure-core/pom.xml +++ b/sdk/spring/spring-cloud-azure-core/pom.xml @@ -65,7 +65,7 @@ com.azure azure-identity-extensions - 1.1.4 + 1.1.5 true diff --git a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml index ea68ad85c0d5..45fded1050c6 100644 --- a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml +++ b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml @@ -144,7 +144,7 @@ com.azure azure-identity-extensions - 1.1.4 + 1.1.5 From 3b2f2bbe3ff45ee0dce8daaa79cc43b46f08d337 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Mon, 26 Jun 2023 15:34:35 +0800 Subject: [PATCH 7/7] update changelog --- sdk/spring/CHANGELOG.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index 573c834f5b1f..008236cb741d 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -2,13 +2,12 @@ ## 5.3.0-beta.1 (Unreleased) -### Features Added - -### Breaking Changes +### Spring Cloud Azure Actuator Autoconfigure +This section includes changes in `spring-cloud-azure-actuator-autoconfigure` module. -### Bugs Fixed +#### Features Added +- Support passwordless connections for JMS ServiceBus in Spring Boot 3. [#35608](https://github.com/Azure/azure-sdk-for-java/pull/35608) -### Other Changes ## 5.2.0 (2023-06-02) - This release is compatible with Spring Boot 3.0.0-3.0.5. (Note: 3.0.x (x>1) should be supported, but they aren't tested with this release.)