Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion eng/Directory.Build.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@
<!-- *********** Management Client Library Override section ************* -->
<ItemGroup Condition="('$(IsMgmtLibrary)' == 'true' and '$(IsTestProject)' != 'true') or '$(IsGeneratorLibraryGenerationTest)' == 'true'" >

<PackageReference Include="Azure.Core" />
<ProjectReference Include="$(RepoRoot)sdk\core\Azure.Core\src\Azure.Core.csproj" />
<ProjectReference Include="$(RepoRoot)sdk\core\System.ClientModel\gen\System.ClientModel.SourceGeneration.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />

<!-- TODO: Review these file references-->
<Compile Include="$(AzureCoreSharedSources)NoValueResponseOfT.cs" LinkBase="Shared" />
Expand Down
2 changes: 2 additions & 0 deletions eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@
<!-- Other approved packages -->
<PackageReference Update="Microsoft.Azure.Amqp" Version="2.7.0" />
<PackageReference Update="Microsoft.Azure.WebPubSub.Common" Version="1.5.0" />
<PackageReference Update="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Update="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageReference Update="Microsoft.Identity.Client" Version="4.76.0" />
<PackageReference Update="Microsoft.Identity.Client.Extensions.Msal" Version="4.76.0" />
<PackageReference Update="Microsoft.Identity.Client.Broker" Version="4.76.0" />
Expand Down
3 changes: 2 additions & 1 deletion sdk/core/Azure.Core/src/Azure.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.ClientModel" />
<ProjectReference Include="..\..\System.ClientModel\src\System.ClientModel.csproj" />
<ProjectReference Include="..\..\System.ClientModel\gen\System.ClientModel.SourceGeneration.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
<PackageReference Include="System.Numerics.Vectors" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
Expand Down
23 changes: 23 additions & 0 deletions sdk/core/System.ClientModel/src/Convenience/ClientConnection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Configuration;

namespace System.ClientModel.Primitives;

/// <summary>
Expand Down Expand Up @@ -51,6 +53,21 @@ internal ClientConnection(string id, string locator, CredentialKind credentialKi
/// <param name="credentialKind">The kind of connection used by the client</param>
/// <param name="metadata">The connection metadata.</param>
public ClientConnection(string id, string locator, object? credential, CredentialKind credentialKind, IReadOnlyDictionary<string, string>? metadata)
: this(id, locator, credential, credentialKind, metadata, configurationSection: null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ClientConnection"/> struct with the specified subclient ID.
/// It is only for the JSON serializer.
/// </summary>
/// <param name="id">The identifier for the connection.</param>
/// <param name="locator">The endpoint or resource identifier.</param>
/// <param name="credential">The client credential.</param>
/// <param name="credentialKind">The kind of connection used by the client</param>
/// <param name="metadata">The connection metadata.</param>
/// <param name="configurationSection">The <see cref="IConfigurationSection"/> used to construct this instance.</param>
public ClientConnection(string id, string locator, object? credential, CredentialKind credentialKind, IReadOnlyDictionary<string, string>? metadata, IConfigurationSection? configurationSection)
{
if (string.IsNullOrWhiteSpace(id))
{
Expand All @@ -74,6 +91,7 @@ public ClientConnection(string id, string locator, object? credential, Credentia
Locator = locator;
Credential = credential;
CredentialKind = credentialKind;
Configuration = configurationSection;
}

/// <summary>
Expand All @@ -96,6 +114,11 @@ public ClientConnection(string id, string locator, object? credential, Credentia
/// </summary>
public CredentialKind CredentialKind { get; }

/// <summary>
/// Gets the configuration section associated with this instance.
/// </summary>
public IConfiguration? Configuration { get; }

/// <summary>
/// Tries to convert the connection locator to a URI.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Primitives;
using Microsoft.Extensions.Configuration;

namespace System.ClientModel;

/// <summary>
/// .
/// </summary>
public static class ConfigurationManagerExtensions
{
private static readonly HashSet<string> FirstClassProperties = new() { "CredentialSource", "Key", "KEY", "Endpoint" };

/// <summary>
/// .
/// </summary>
/// <param name="configuration"></param>
/// <param name="sectionName"></param>
/// <returns></returns>
public static ClientConnection GetConnection(this IConfigurationManager configuration, string sectionName)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we dont need an optional parameter called "format" (or something like that). This would allow us to add support for all the combinations in aspire. Unless we are fine with probing all the formats.

=> configuration.GetSection(sectionName).GetConnection();

/// <summary>
/// .
/// </summary>
/// <param name="section"></param>
/// <returns></returns>
public static ClientConnection GetConnection(this IConfigurationSection section)
{
var credential = CreateCredentials(section);
Dictionary<string, string>? metadata = null;
foreach (var child in section.GetChildren())
{
if (!FirstClassProperties.Contains(child.Key) && !string.IsNullOrEmpty(child.Value))
{
metadata ??= new Dictionary<string, string>();
metadata[child.Key] = child.Value!;
}
}
return new ClientConnection(section.Key, section["Endpoint"] ?? "$auto", credential.Credential, credential.Kind, metadata, section);
}

internal static (object? Credential, CredentialKind Kind) CreateCredentials(IConfigurationSection section)
{
CredentialKind credentialKind;
object? credential = default;
if (section["Credential:CredentialSource"] is null)
{
credentialKind = CredentialKind.None;
}
else if (section["Credential:CredentialSource"]!.Equals("ApiKey", StringComparison.Ordinal))
{
credentialKind = CredentialKind.ApiKeyString;
credential = section["Credential:Key"];
}
else
{
throw new Exception($"Unsupported credential source '{section["Credential:CredentialSource"]}'.");
}

return (credential, credentialKind);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReference Include="System.Memory.Data" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion sdk/identity/Azure.Identity/src/Azure.Identity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
</When>
</Choose>
<ItemGroup>
<PackageReference Include="Azure.Core" />
<ProjectReference Include="..\..\..\core\Azure.Core\src\Azure.Core.csproj" />
<PackageReference Include="System.Memory" />
<PackageReference Include="Microsoft.Identity.Client" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(AzureCoreSharedSources)AppContextSwitchHelper.cs" LinkBase="Shared" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

namespace Azure.Identity
{
/// <summary>
/// .
/// </summary>
public static class ConfigurationManagerExtensions
{
private static readonly HashSet<string> FirstClassProperties = new() { "CredentialSource", "Key", "KEY", "Endpoint" };

/// <summary>
/// .
/// </summary>
/// <param name="configuration"></param>
/// <param name="sectionName"></param>
/// <returns></returns>
public static ClientConnection GetAzureConnection(this IConfigurationManager configuration, string sectionName)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should not give it a nice name: GetClientConnection. This method will work with both Azure and non-Azure scenarios. I even wonder if we could call the SCM variant of the method the same and use the new C# attribute (overload priority) to resolve ambiguity

{
IConfigurationSection section = configuration.GetSection(sectionName);
var credential = CreateCredentials(section.GetSection("Credential"));
Dictionary<string, string> metadata = null;
foreach (var child in section.GetChildren())
{
if (!FirstClassProperties.Contains(child.Key) && !string.IsNullOrEmpty(child.Value))
{
metadata ??= new Dictionary<string, string>();
metadata[child.Key] = child.Value!;
}
}
return new ClientConnection(section.Key, section["Endpoint"] ?? "$auto", credential.Credential, credential.Kind, metadata, section);
}

private static (object Credential, CredentialKind Kind) CreateCredentials(IConfigurationSection credentialSection)
{
CredentialKind credentialKind;
object credential = default;
if (credentialSection["CredentialSource"] is null)
{
credentialKind = CredentialKind.None;
}
else if (credentialSection["CredentialSource"].Equals("ApiKey", StringComparison.Ordinal))
{
credentialKind = CredentialKind.ApiKeyString;
credential = credentialSection["Key"];
}
else
{
credentialKind = CredentialKind.TokenCredential;
DefaultAzureCredentialOptions dacOptions = new();
ConfigureDefaultAzureCredentialOptions(credentialSection, dacOptions);
credential = new DefaultAzureCredential(dacOptions);
}

return (credential, credentialKind);
}

internal static void ConfigureDefaultAzureCredentialOptions(IConfigurationSection section, DefaultAzureCredentialOptions options)
{
if (section["CredentialSource"] is string credentialSource)
{
options.CredentialSource = credentialSource;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ public T Value
private UpdateTracker<string> _visualStudioTenantId = new UpdateTracker<string>(EnvironmentVariables.TenantId);
private UpdateTracker<string> _visualStudioCodeTenantId = new UpdateTracker<string>(EnvironmentVariables.TenantId);

private string _credentialSource;
/// <summary>
/// Gets or sets the kind of credential to use.
/// </summary>
public string CredentialSource
{
get => _credentialSource;
set
{
_credentialSource = ConvertCredentialSource(value);
}
}

private static string ConvertCredentialSource(string value) => value switch
{
"AzureCli" => "azureclicredential",
_ => value,
};

/// <summary>
/// The ID of the tenant to which the credential will authenticate by default. If not specified, the credential will authenticate to any requested tenant, and will default to the tenant to which the chosen authentication method was originally authenticated.
/// </summary>
Expand Down Expand Up @@ -327,6 +346,7 @@ public string VisualStudioCodeTenantId
dacClone.ExcludeAzurePowerShellCredential = ExcludeAzurePowerShellCredential;
dacClone.IsForceRefreshEnabled = IsForceRefreshEnabled;
dacClone.ExcludeBrokerCredential = ExcludeBrokerCredential;
dacClone.CredentialSource = CredentialSource;
}

return clone;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected DefaultAzureCredentialFactory(DefaultAzureCredentialOptions options, C
public TokenCredential[] CreateCredentialChain()
{
TokenCredential[] tokenCredentials = Array.Empty<TokenCredential>();
string credentialSelection = EnvironmentVariables.CredentialSelection?.Trim().ToLower();
string credentialSelection = Options.CredentialSource ?? EnvironmentVariables.CredentialSelection?.Trim().ToLower();

if (_customEnvironmentVariableName != null)
{
Expand Down
36 changes: 36 additions & 0 deletions sdk/identity/Azure.Identity/src/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel;
using Azure.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Azure.Identity
{
/// <summary>
/// .
/// </summary>
public static class HostBuilderExtensions
{
/// <summary>
/// .
/// </summary>
/// <param name="host"></param>
/// <param name="sectionName"></param>
/// <returns></returns>
public static IHostBuilder AddAzureCredential(this IHostBuilder host, string sectionName)
{
host.ConfigureServices((context, services) =>
{
DefaultAzureCredentialOptions options = new();
ConfigurationManagerExtensions.ConfigureDefaultAzureCredentialOptions(context.Configuration.GetSection(sectionName), options);
DefaultAzureCredential credential = new DefaultAzureCredential(options);
services.AddSingleton<TokenCredential>(sp => credential);
services.AddSingleton<AuthenticationTokenProvider>(sp => credential);
});

return host;
}
}
}
12 changes: 12 additions & 0 deletions sdk/resourcemanager/Azure.ResourceManager/src/ArmClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Azure.ResourceManager.ManagementGroups;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
using Microsoft.Extensions.Configuration;

namespace Azure.ResourceManager
{
Expand Down Expand Up @@ -88,6 +89,17 @@ public ArmClient(TokenCredential credential, string defaultSubscriptionId, ArmCl
new SubscriptionResource(this, SubscriptionResource.CreateResourceIdentifier(defaultSubscriptionId));
}

internal void RegisterConfigReload(IConfiguration configuration)
{
configuration.GetReloadToken().RegisterChangeCallback(state =>
{
var newDefaultSubscription = configuration["DefaultSubscriptionId"];
_defaultSubscription = newDefaultSubscription is null
? null
: new SubscriptionResource(this, SubscriptionResource.CreateResourceIdentifier(newDefaultSubscription));
}, null);
}

internal virtual bool CanUseTagResource(CancellationToken cancellationToken = default)
{
if (_canUseTagResource == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>1.14.0-beta.2</Version>
Expand All @@ -13,6 +13,10 @@
<IncludeAutorestDependency>true</IncludeAutorestDependency>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
</ItemGroup>

<ItemGroup>
<None Remove="Assets\Profile\2020-09-01-hybrid.json" />
</ItemGroup>
Expand Down
Loading
Loading