Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridge azure properties to spring starter to authenticate #5

Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring;

import com.azure.spring.autoconfigure.unity.CredentialProperties;
import com.azure.spring.autoconfigure.unity.EnvironmentProperties;

import java.util.Optional;

/**
* An abstract implementation to provide all credential related properties based on properties subclass.
*/
public abstract class AbstractMappingCredentialPropertiesProvider implements MappingCredentialPropertiesProvider {

CredentialProperties credentialProperties;
EnvironmentProperties environment;

@Override
public void mapAzureProperties(
CredentialProperties credentialProperties,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I don't like this code style.

EnvironmentProperties environment) {
this.credentialProperties = credentialProperties;
this.environment = environment;
}

@Override
public String getTenantId() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getTenantId).orElse(null);
}

@Override
public String getClientId() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getClientId).orElse(null);
}

@Override
public String getClientSecret() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getClientId).orElse(null);
}

@Override
public String getClientCertificatePath() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getClientId).orElse(null);
}

@Override
public String getUsername() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getClientId).orElse(null);
}

@Override
public String getPassword() {
return Optional.ofNullable(credentialProperties).map(CredentialProperties::getClientId).orElse(null);
}

@Override
public String getAuthorityHost() {
return Optional.ofNullable(environment)
.map(EnvironmentProperties::getAuthorityHost).orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring;

import com.azure.core.credential.TokenCredential;
import com.azure.spring.autoconfigure.unity.CredentialProperties;
import com.azure.spring.autoconfigure.unity.EnvironmentProperties;
import com.azure.spring.identity.CredentialPropertiesProvider;

/**
* An interface to provide all credential related properties based on the properties subclass.
*/
public interface MappingCredentialPropertiesProvider extends CredentialPropertiesProvider {

void mapAzureProperties(CredentialProperties credentialProperties, EnvironmentProperties environment);

TokenCredential mappingTokenCredential();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@
package com.azure.spring.autoconfigure.cosmos;

import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.TokenCredential;
import com.azure.cosmos.ConnectionMode;
import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.MappingCredentialPropertiesProvider;
import com.azure.spring.autoconfigure.unity.identity.AzureDefaultTokenCredentialAutoConfiguration;
import com.azure.spring.data.cosmos.config.AbstractCosmosConfiguration;
import com.azure.spring.data.cosmos.config.CosmosConfig;
import com.azure.spring.data.cosmos.core.CosmosTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import static com.azure.spring.autoconfigure.unity.AzureProperties.AZURE_PROPERTY_BEAN_NAME;
import java.util.Optional;

/**
* Auto Configure Cosmos properties and connection policy.
Expand All @@ -27,15 +32,12 @@
@ConditionalOnClass({ CosmosAsyncClient.class, CosmosTemplate.class })
@ConditionalOnResource(resources = "classpath:cosmos.enable.config")
@EnableConfigurationProperties(CosmosProperties.class)
@AutoConfigureAfter(AzureDefaultTokenCredentialAutoConfiguration.class)
public class CosmosAutoConfiguration extends AbstractCosmosConfiguration {
private final CosmosProperties cosmosProperties;
private final AzureProperties azureProperties;


public CosmosAutoConfiguration(CosmosProperties cosmosProperties,
@Qualifier(AZURE_PROPERTY_BEAN_NAME) AzureProperties azureProperties) {
public CosmosAutoConfiguration(CosmosProperties cosmosProperties) {
this.cosmosProperties = cosmosProperties;
this.azureProperties = azureProperties;
}

@Override
Expand All @@ -44,20 +46,44 @@ protected String getDatabaseName() {
}

@Bean
@ConditionalOnMissingBean
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this to be a bean?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the source of this key could only be the CosmosProperties.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For @saragluna, we need to check the key rotation logic here.

public AzureKeyCredential azureKeyCredential() {
return new AzureKeyCredential(cosmosProperties.getKey());
return Optional.ofNullable(cosmosProperties.getKey())
.filter(StringUtils::hasText)
.map(AzureKeyCredential::new)
.orElse(null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null?

}

@Bean
public CosmosClientBuilder cosmosClientBuilder(AzureKeyCredential azureKeyCredential) {
CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder();
cosmosClientBuilder.credential(azureKeyCredential)
.consistencyLevel(cosmosProperties.getConsistencyLevel())
.endpoint(cosmosProperties.getUri());
@ConditionalOnMissingBean
public CosmosClientBuilder cosmosClientBuilder(
ObjectProvider<AzureKeyCredential> azureKeyCredentials,
ObjectProvider<MappingCredentialPropertiesProvider> mappingPropertiesProviders,
ObjectProvider<TokenCredential> defaultTokenCredentials) {
CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder()
.consistencyLevel(cosmosProperties.getConsistencyLevel())
.endpoint(cosmosProperties.getUri());
if (ConnectionMode.GATEWAY == cosmosProperties.getConnectionMode()) {
cosmosClientBuilder.gatewayMode();
}
return cosmosClientBuilder;
AzureKeyCredential azureKeyCredential = azureKeyCredentials.getIfAvailable();
if (azureKeyCredential != null) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen if we set both AzureKeyCredential and TokenCredential in an Azure SDK builder?

return cosmosClientBuilder.credential(azureKeyCredential);
}

MappingCredentialPropertiesProvider propertiesProvider = mappingPropertiesProviders.orderedStream()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using this EnvironemtCredential and the Azure Default Token Credential to construct a chained token credential?

.findFirst()
.orElse(null);
if (propertiesProvider != null) {
return cosmosClientBuilder.credential(propertiesProvider.mappingTokenCredential());
}

TokenCredential defaultTokenCredential = defaultTokenCredentials.orderedStream().findFirst().orElse(null);
if (defaultTokenCredential != null) {
return cosmosClientBuilder.credential(defaultTokenCredential);
}

throw new IllegalStateException("Not found any credential properties configured.");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.cosmos.ConnectionMode;
import com.azure.cosmos.ConsistencyLevel;
import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.autoconfigure.unity.SpringAzureProperties;
import com.azure.spring.data.cosmos.core.ResponseDiagnosticsProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -20,7 +21,7 @@
*/
@Validated
@ConfigurationProperties(CosmosProperties.PREFIX)
public class CosmosProperties extends AzureProperties {
public class CosmosProperties extends AzureProperties implements SpringAzureProperties {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need of SpringAzureProperties.


private static final Logger LOGGER = LoggerFactory.getLogger(CosmosProperties.class);
public static final String PREFIX = "spring.cloud.azure.cosmos";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

package com.azure.spring.autoconfigure.storage;

import com.azure.core.credential.TokenCredential;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.spring.MappingCredentialPropertiesProvider;
import com.azure.spring.autoconfigure.storage.resource.AzureStorageProtocolResolver;
import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.autoconfigure.unity.identity.AzureDefaultTokenCredentialAutoConfiguration;
import com.azure.spring.identity.SpringEnvironmentCredential;
import com.azure.spring.identity.SpringEnvironmentCredentialBuilder;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.common.StorageSharedKeyCredential;
import com.azure.storage.file.share.ShareServiceClientBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
Expand All @@ -18,8 +23,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;

import static com.azure.spring.autoconfigure.unity.AzureProperties.AZURE_PROPERTY_BEAN_NAME;
import static com.azure.spring.core.ApplicationId.AZURE_SPRING_STORAGE_BLOB;
import static com.azure.spring.core.ApplicationId.AZURE_SPRING_STORAGE_FILES;
import static com.azure.spring.core.ApplicationId.VERSION;
Expand All @@ -33,27 +38,54 @@
@ConditionalOnClass({ BlobServiceClientBuilder.class, ShareServiceClientBuilder.class })
@ConditionalOnResource(resources = "classpath:storage.enable.config")
@EnableConfigurationProperties(StorageProperties.class)
@AutoConfigureAfter(AzureDefaultTokenCredentialAutoConfiguration.class)
public class StorageAutoConfiguration {

private final SpringEnvironmentCredentialBuilder environmentCredentialBuilder;

private final TokenCredential defaultTokenCredential;

public StorageAutoConfiguration(SpringEnvironmentCredentialBuilder environmentCredentialBuilder,
TokenCredential defaultTokenCredential) {
this.environmentCredentialBuilder = environmentCredentialBuilder;
this.defaultTokenCredential = defaultTokenCredential;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("azure.storage.blob-endpoint")
public BlobServiceClientBuilder blobServiceClientBuilder(StorageProperties storageProperties, @Qualifier(
AZURE_PROPERTY_BEAN_NAME) AzureProperties azureProperties) {
public BlobServiceClientBuilder blobServiceClientBuilder(
StorageProperties storageProperties,
ObjectProvider<MappingCredentialPropertiesProvider> mappingPropertiesProviders,
ObjectProvider<TokenCredential> defaultTokenCredentials) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this called defaultTokenCredentials? Will there be multiple default token crdentials?

final String accountName = storageProperties.getAccountName();
final String accountKey = storageProperties.getAccountKey();

return new BlobServiceClientBuilder()
BlobServiceClientBuilder serviceClientBuilder = new BlobServiceClientBuilder()
.endpoint(storageProperties.getBlobEndpoint())
.credential(new StorageSharedKeyCredential(accountName, accountKey))
.httpLogOptions(new HttpLogOptions().setApplicationId(AZURE_SPRING_STORAGE_BLOB + VERSION));
if (StringUtils.hasText(accountName)
&& StringUtils.hasText(accountKey)) {
return serviceClientBuilder.credential(new StorageSharedKeyCredential(accountName, accountKey));
}

MappingCredentialPropertiesProvider propertiesProvider = mappingPropertiesProviders.orderedStream()
.findFirst()
.orElse(null);
if (propertiesProvider != null) {
return serviceClientBuilder.credential(propertiesProvider.mappingTokenCredential());
}

TokenCredential defaultTokenCredential = defaultTokenCredentials.orderedStream().findFirst().orElse(null);
if (defaultTokenCredential != null) {
return serviceClientBuilder.credential(defaultTokenCredential);
}

throw new IllegalStateException("Not found any credential properties configured.");
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("azure.storage.file-endpoint")
public ShareServiceClientBuilder shareServiceClientBuilder(StorageProperties storageProperties, @Qualifier(
AZURE_PROPERTY_BEAN_NAME) AzureProperties azureProperties) {
public ShareServiceClientBuilder shareServiceClientBuilder(StorageProperties storageProperties) {
final String accountName = storageProperties.getAccountName();
final String accountKey = storageProperties.getAccountKey();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
package com.azure.spring.autoconfigure.storage;

import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.autoconfigure.unity.SpringAzureProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
* Storage properties.
*/
@Validated
@ConfigurationProperties(StorageProperties.PREFIX)
public class StorageProperties extends AzureProperties {
public class StorageProperties extends AzureProperties implements SpringAzureProperties {

public static final String PREFIX = "spring.cloud.azure.storage";

@NotEmpty
// @NotEmpty
@Pattern(regexp = "^[a-z0-9]{3,24}$",
message = "must be between 3 and 24 characters in length and use numbers and lower-case letters only")
private String accountName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.azure.spring.autoconfigure.unity;

public interface SpringAzureProperties {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this empty interface?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per discussion, maybe we could remove this now.

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@

import com.azure.core.credential.TokenCredential;
import com.azure.identity.ChainedTokenCredentialBuilder;
import com.azure.spring.MappingCredentialPropertiesProvider;
import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.autoconfigure.unity.SpringAzureProperties;
import com.azure.spring.identity.SpringAzureCliCredentialBuilder;
import com.azure.spring.identity.SpringAzurePowerShellCredentialBuilder;
import com.azure.spring.identity.SpringCredentialBuilderBase;
import com.azure.spring.identity.SpringEnvironmentCredentialBuilder;
import com.azure.spring.identity.SpringIntelliJCredentialBuilder;
import com.azure.spring.identity.SpringManagedIdentityCredentialBuilder;
import com.azure.spring.identity.SpringVisualStudioCodeCredentialBuilder;
import org.springframework.beans.factory.ObjectProvider;
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;
import org.springframework.core.annotation.Order;
Expand All @@ -24,6 +28,7 @@
* Auto-configuration for Azure Spring default token credential.
*/
@Configuration
@ConditionalOnProperty(name = AzureProperties.PREFIX)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to depend on this property bean?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I will remove it

public class AzureDefaultTokenCredentialAutoConfiguration {

public static final int SPRING_ENV_CREDENTIAL_ORDER = 0;
Expand Down Expand Up @@ -87,6 +92,12 @@ public TokenCredential azureTokenCredential(List<SpringCredentialBuilderBase> cr
return chainedTokenCredentialBuilder.build();
}

@Bean
public MappingCredentialPropertiesProvider cosmosCredentialPropertiesProvider(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if service bus and cosmos work together?

ObjectProvider<SpringAzureProperties> springProperties) {
return new SpringMappingCredentialPropertiesProvider((AzureProperties) springProperties.getIfAvailable());
}

/*@Bean
public CredentialBuilderPostProcessor credentialBuilderPostProcessor() {
return new CredentialBuilderPostProcessor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.autoconfigure.unity.identity;

import com.azure.core.credential.TokenCredential;
import com.azure.spring.AbstractMappingCredentialPropertiesProvider;
import com.azure.spring.autoconfigure.unity.AzureProperties;
import com.azure.spring.identity.SpringEnvironmentCredentialBuilder;
import org.springframework.core.Ordered;

public class SpringMappingCredentialPropertiesProvider extends AbstractMappingCredentialPropertiesProvider implements Ordered {

public SpringMappingCredentialPropertiesProvider(AzureProperties azureProperties) {
if (azureProperties != null) {
mapAzureProperties(azureProperties.getCredential(), azureProperties.getEnvironment());
}
}

@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}

@Override
public TokenCredential mappingTokenCredential() {
SpringEnvironmentCredentialBuilder mapEnvironmentCredentialBuilder =
new SpringEnvironmentCredentialBuilder().credentialPropertiesProvider(this);
return mapEnvironmentCredentialBuilder.build();
}
}
Loading