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

Add support for Managed Identity #108

Merged
merged 8 commits into from
Dec 5, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public string? Container
CredentialSource.StoreWithThumbprint or CredentialSource.StoreWithDistinguishedName => CertificateStorePath,
CredentialSource.SignedAssertionFilePath => SignedAssertionFileDiskPath,
CredentialSource.SignedAssertionFromVault => KeyVaultUrl,
_ => null,
_ => null
};
}
set
Expand Down Expand Up @@ -257,8 +257,8 @@ public string? Container
public string? ClientSecret { get; set; }

/// <summary>
/// When <see cref="SourceType"/> is <see cref="CredentialSource.SignedAssertionFromManagedIdentity"/>, specifies the client ID of the Azure user-assigned managed identity
/// used to provide an signed assertion that will be used as a client credential for the application. This requires that the application is deployed on Azure, that the managed identity is configured,
/// When <see cref="SourceType"/> is <see cref="CredentialSource.SignedAssertionFromManagedIdentity"/>, it specifies the client ID of the Azure user-assigned managed identity
/// used to provide a signed assertion to act as a client credential for the application. This requires that the application is deployed on Azure, that the managed identity is configured,
/// and that workload identity federation with the managed identity is declared in the application registration. For details, see https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identity-federation.
/// </summary>
/// <example>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ComponentModel;

namespace Microsoft.Identity.Abstractions
{
/// <summary>
/// Data object to hold the definition of a managed identity for an application to use for authentication.<br/><br/>
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for more details.
/// </summary>
public class ManagedIdentityOptions
{
/// <summary>
/// Gets or sets whether the <see cref="Abstractions.ManagedIdentityType"/> is system-assigned or user assigned.
/// Defaults to <see cref="ManagedIdentityType.SystemAssigned"/> if not set.<br/><br/>
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for details on these two types of managed identity.
/// </summary>
[DefaultValue(ManagedIdentityType.SystemAssigned)]
public ManagedIdentityType ManagedIdentityType { get; set; }

/// <summary>
/// Gets or sets the value of the client id when <see cref="ManagedIdentityType"/> is set to
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// <see cref="ManagedIdentityType.UserAssigned"/>.<br/>If not set, the default value is null.
/// </summary>
public string? ClientId { get; set; }

/// <summary>
/// Ensures a clone of this object will not have the same reference.
/// </summary>
/// <returns>A new instance of <see cref="ManagedIdentityOptions"/> with the same field values.</returns>
public ManagedIdentityOptions Clone()
{
return new ManagedIdentityOptions
{
ManagedIdentityType = ManagedIdentityType,
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
ClientId = ClientId
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Abstractions
{
/// <summary>
/// Used by <see cref="ManagedIdentityOptions"/> to specify the type of managed identity to use.<br/><br/>
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for more details.
/// </summary>
public enum ManagedIdentityType
{
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// The default value, indicating the managed identity to use is the one configured for the Azure resource on which the
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// application is running.
/// </summary>
SystemAssigned = 0,

/// <summary>
/// Indicates the managed identity to use is a user-assigned identity which is defined in a standalone Azure resource.
/// </summary>
UserAssigned = 1,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public AcquireTokenOptions(AcquireTokenOptions other)
Claims = other.Claims;
PopPublicKey = other.PopPublicKey;
PopClaim = other.PopClaim;
ManagedIdentity = other.ManagedIdentity?.Clone();
LongRunningWebApiSessionKey = other.LongRunningWebApiSessionKey;
Tenant = other.Tenant;
UserFlow = other.UserFlow;
Expand Down Expand Up @@ -64,7 +65,8 @@ public AcquireTokenOptions(AcquireTokenOptions other)
/// A string with one or multiple claims to request. It's a json blob (encoded or not)
/// Normally used with Conditional Access. It receives the Claims member of the UiRequiredException.
/// It can also be used to request specific optional claims, and for
/// <see href="https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-cloud-apps">CA Auth context</see>
/// <see href="https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-cloud-apps">
/// CA Auth context</see>
/// </summary>
public string? Claims { get; set; }

Expand All @@ -90,6 +92,39 @@ public AcquireTokenOptions(AcquireTokenOptions other)
/// </summary>
public string? PopClaim { get; set; }

/// <summary>
/// When <see cref="ManagedIdentity"/> is set, the application uses a managed identity instead of client credentials to
/// acquire an app token.<br/><br/>
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
///
/// The type of managed identity is defined by the <see cref="ManagedIdentityOptions.ManagedIdentityType"/> field. When
/// using a <see cref="ManagedIdentityType.SystemAssigned"/> identity, this is the only field that needs to be set and is
/// set by default. However, for readability it can be useful to set explicitly<br/><br/>
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
///
/// To use a user-assigned identity, select the <see cref="ManagedIdentityType"/> that corresponds to the
/// <see cref="ManagedIdentityOptions.ClientId"/> you plan to use for authentication.<br/><br/>
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
///
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// Using either form of managed identity requires the application to be deployed on Azure and
/// the managed identity to be configured. For more details, check the
/// <see href="https://aka.ms/Entra/ManagedIdentityOverview"> managed identities for Azure documentation</see>.
/// </summary>
/// <example>
/// <format type="text/markdown">
/// <![CDATA[
/// The Json fragment below describes how to use a system-assigned Managed Identity for authentication in a confidential client application :
/// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentitysystem_json":::
///
/// The code below describes the same, programmatically in C#.
/// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentitysystem_csharp":::
///
/// The Json fragment below describes how to use a user-assigned Managed Identity for authentication in a confidential client application :
/// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentityuser_json":::
///
/// The code below describes the same, programmatically in C#.
/// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentityuser_csharp":::
/// ]]></format>
/// </example>
public ManagedIdentityOptions? ManagedIdentity { get; set; }

/// <summary>
/// Key used for long running web APIs that need to call downstream web
/// APIs on behalf of the user. Can be null, if you are not developing a long
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;

namespace Microsoft.Identity.Abstractions.Tests
{
public class AquireTokenOptionsTests
{
[Fact]
public void ManagedIdentitySystemAssigned()
{
// App token from a system-assigned managed identity.
// -------------------------------------------------
// https://aka.ms/Entra/ManagedIdentityOverview
/*
// <managedidentity_json>
{
"AquireTokenOptions": {
"ManagedIdentity": {
"ManagedIdentityType": "SystemAssigned"
}
}
}
// </managedidentitysystem_json>
*/

// <managedidentity_csharp>
AcquireTokenOptions acquireTokenOptions = new AcquireTokenOptions
{
ManagedIdentity = new ManagedIdentityOptions()
{
// default: ManagedIdentityType = ManagedIdentityType.SystemAssigned
}
};
// </managedidentitysystem_csharp>

Assert.Equal(ManagedIdentityType.SystemAssigned, acquireTokenOptions.ManagedIdentity.ManagedIdentityType);
Assert.Null(acquireTokenOptions.ManagedIdentity.ClientId);
}

[Fact]
public void ManagedIdentityUserAssigned()
{
// App token from a user-assigned managed identity.
// -------------------------------------------------
// https://aka.ms/Entra/ManagedIdentityOverview
/*
// <managedidentityuser_json>
{
"AquireTokenOptions": {
jmprieur marked this conversation as resolved.
Show resolved Hide resolved
"ManagedIdentity": {
"ManagedIdentityType": "UserAssigned"
"ClientId": "[ClientIdForTheManagedIdentityResource]"
}
}
}
// </managedidentityuser_json>
*/

// <managedidentityuser_csharp>
ManagedIdentityOptions managedIdentityDescription = new ManagedIdentityOptions
{
ManagedIdentityType = ManagedIdentityType.UserAssigned,
ClientId = "[ClientIdForTheManagedIdentityResource]"
};

AcquireTokenOptions acquireTokenOptions = new AcquireTokenOptions
{
ManagedIdentity = managedIdentityDescription
};
// </managedidentityuser_csharp>

JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
Assert.Equal(ManagedIdentityType.UserAssigned, acquireTokenOptions.ManagedIdentity.ManagedIdentityType);
Assert.Equal(managedIdentityDescription.ClientId, acquireTokenOptions.ManagedIdentity.ClientId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ExtraQueryParameters = new Dictionary<string, string> { { "slice", "test" } },
ForceRefresh = true,
LongRunningWebApiSessionKey = AcquireTokenOptions.LongRunningWebApiSessionKeyAuto,
ManagedIdentity = new ManagedIdentityOptions(),
PopPublicKey = "PopKey",
PopClaim = "jwkClaim",
Tenant = "domain.com",
Expand Down Expand Up @@ -75,14 +76,16 @@
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ExtraQueryParameters, downstreamApiClone.AcquireTokenOptions.ExtraQueryParameters);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ForceRefresh, downstreamApiClone.AcquireTokenOptions.ForceRefresh);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.LongRunningWebApiSessionKey, downstreamApiClone.AcquireTokenOptions.LongRunningWebApiSessionKey);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ManagedIdentity.ManagedIdentityType, downstreamApiClone.AcquireTokenOptions.ManagedIdentity.ManagedIdentityType);

Check warning on line 79 in test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs

View workflow job for this annotation

GitHub Actions / Abstractions GitHub Action Test

Dereference of a possibly null reference.

Check warning on line 79 in test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs

View workflow job for this annotation

GitHub Actions / Abstractions GitHub Action Test

Dereference of a possibly null reference.
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ManagedIdentity.ClientId, downstreamApiClone.AcquireTokenOptions.ManagedIdentity.ClientId);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.PopPublicKey, downstreamApiClone.AcquireTokenOptions.PopPublicKey);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.PopClaim, downstreamApiClone.AcquireTokenOptions.PopClaim);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.Tenant, downstreamApiClone.AcquireTokenOptions.Tenant);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.UserFlow, downstreamApiClone.AcquireTokenOptions.UserFlow);

// If this fails, think of also adding a line to test the new property
Assert.Equal(10, typeof(DownstreamApiOptions).GetProperties().Length);
Assert.Equal(12, typeof(AcquireTokenOptions).GetProperties().Length);
Assert.Equal(13, typeof(AcquireTokenOptions).GetProperties().Length);

DownstreamApiOptionsReadOnlyHttpMethod options = new DownstreamApiOptionsReadOnlyHttpMethod(downstreamApiOptions, HttpMethod.Delete.ToString());
Assert.Equal(HttpMethod.Delete.ToString(), options.HttpMethod);
Expand Down Expand Up @@ -152,4 +155,4 @@

public CustomAcquireTokenOptions(CustomAcquireTokenOptions other) : base(other) { }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;

namespace Microsoft.Identity.Abstractions.Tests
{
public class ManagedIdentityDescriptionTests
{
/// <summary>
/// If no field is set for <see cref="ManagedIdentityOptions"/> the <see cref="ManagedIdentityOptions.ManagedIdentityType"/>
/// field needs to default to <see cref="ManagedIdentitySource.SystemAssigned"/> as other Microsoft.Identity libraries
/// will depend on this.
/// </summary>
[Fact]
public void ManagedIdentity_NoDescriptionFieldsSet()
{
// Arrange
ManagedIdentityOptions description = new();

// Assert
Assert.Equal(ManagedIdentityType.SystemAssigned, description.ManagedIdentityType);
Assert.Null(description.ClientId);
}
}
}
Loading