-
Notifications
You must be signed in to change notification settings - Fork 287
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AAD: Handling Azure.Core.TokenCredential (#2191)
- Loading branch information
1 parent
e1ff87e
commit ad3d952
Showing
12 changed files
with
608 additions
and
0 deletions.
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
.publicApi/Microsoft.ApplicationInsights.dll/net452/PublicAPI.Unshipped.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
.publicApi/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
.publicApi/Microsoft.ApplicationInsights.dll/netstandard2.0/PublicAPI.Unshipped.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
...t.ApplicationInsights.Tests/Extensibility/Implementation/Authentication/MockCredential.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#if NET461 || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 | ||
namespace Microsoft.ApplicationInsights.TestFramework.Extensibility.Implementation.Authentication | ||
{ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
using Azure.Core; | ||
|
||
|
||
/// <remarks> | ||
/// Copied from (https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs). | ||
/// </remarks> | ||
public class MockCredential : TokenCredential | ||
{ | ||
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
{ | ||
return new ValueTask<AccessToken>(GetToken(requestContext, cancellationToken)); | ||
} | ||
|
||
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
{ | ||
return new AccessToken("TEST TOKEN " + string.Join(" ", requestContext.Scopes), DateTimeOffset.MaxValue); | ||
} | ||
} | ||
} | ||
#endif |
203 changes: 203 additions & 0 deletions
203
...ts.Tests/Extensibility/Implementation/Authentication/ReflectionCredentialEnvelopeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
#if !NET452 && !NET46 | ||
namespace Microsoft.ApplicationInsights.TestFramework.Extensibility.Implementation.Authentication | ||
{ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
using Azure.Core; | ||
|
||
using Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
using Moq; | ||
|
||
/// <summary> | ||
/// The <see cref="ReflectionCredentialEnvelope"/> cannot take a dependency on <see cref="Azure.Core.TokenCredential"/>. | ||
/// We must use reflection to interact with this class. | ||
/// These tests are to confirm that we can correctly identity classes that implement TokenCredential and address it's methods. | ||
/// </summary> | ||
/// <remarks> | ||
/// These tests do not run in NET452 OR NET46. | ||
/// In these cases, the test runner is NET452 or NET46 and Azure.Core.TokenCredential is NOT SUPPORTED in these frameworks. | ||
/// This does not affect the end user because we REQUIRE the end user to create their own instance of TokenCredential. | ||
/// This ensures that the end user is consuming the AI SDK in one of the newer frameworks. | ||
/// </remarks> | ||
[TestClass] | ||
[TestCategory("AAD")] | ||
public class ReflectionCredentialEnvelopeTests | ||
{ | ||
[TestMethod] | ||
public void VerifyCanIdentifyValidClass() | ||
{ | ||
var testClass2 = new TestClassInheritsTokenCredential(); | ||
_ = new ReflectionCredentialEnvelope(testClass2); | ||
// NO ASSERT. This test is valid if no exception is thrown. :) | ||
} | ||
|
||
[TestMethod] | ||
[ExpectedException(typeof(ArgumentException))] | ||
public void VerifyCanIdentityInvalidClass() | ||
{ | ||
var notTokenCredential2 = new NotTokenCredential2(); | ||
_ = new ReflectionCredentialEnvelope(notTokenCredential2); | ||
} | ||
|
||
[TestMethod] | ||
[ExpectedException(typeof(ArgumentException))] | ||
public void VerifyCannotSetInvalidType() | ||
{ | ||
_ = new ReflectionCredentialEnvelope(Guid.Empty); | ||
} | ||
|
||
[TestMethod] | ||
public void VerifyCanMakeTokenRequestContext() | ||
{ | ||
var testScope = new string[] { "test/scope" }; | ||
|
||
var requestContext = new TokenRequestContext(testScope); | ||
|
||
var tokenRequestContextViaReflection = ReflectionCredentialEnvelope.AzureCore.MakeTokenRequestContext(testScope); | ||
Assert.IsInstanceOfType(tokenRequestContextViaReflection, typeof(TokenRequestContext)); | ||
Assert.AreEqual(requestContext, tokenRequestContextViaReflection); | ||
} | ||
|
||
[TestMethod] | ||
public void VerifyGetToken_UsingCompileTimeTypes() | ||
{ | ||
var mockCredential = new MockCredential(); | ||
var requestContext = new TokenRequestContext(new string[] { "test/scope" }); | ||
|
||
var testResult = ReflectionCredentialEnvelope.AzureCore.InvokeGetToken(mockCredential, requestContext, CancellationToken.None); | ||
|
||
Assert.AreEqual("TEST TOKEN test/scope", testResult); | ||
} | ||
|
||
[TestMethod] | ||
public async Task VerifyGetTokenAsync_UsingCompileTimeTypes() | ||
{ | ||
var mockCredential = new MockCredential(); | ||
var requestContext = new TokenRequestContext(new string[] { "test/scope" }); | ||
|
||
var testResult = await ReflectionCredentialEnvelope.AzureCore.InvokeGetTokenAsync(mockCredential, requestContext, CancellationToken.None); | ||
|
||
Assert.AreEqual("TEST TOKEN test/scope", testResult); | ||
} | ||
|
||
/// <summary> | ||
/// This more closely represents how this would be used in a production environment. | ||
/// </summary> | ||
[TestMethod] | ||
public void VerifyGetToken_UsingDynamicTypes() | ||
{ | ||
var mockCredential = (object)new MockCredential(); | ||
var requestContext = ReflectionCredentialEnvelope.AzureCore.MakeTokenRequestContext(new[] { "test/scope" }); | ||
|
||
var testResult = ReflectionCredentialEnvelope.AzureCore.InvokeGetToken(mockCredential, requestContext, CancellationToken.None); | ||
|
||
Assert.AreEqual("TEST TOKEN test/scope", testResult); | ||
} | ||
|
||
/// <summary> | ||
/// This more closely represents how this would be used in a production environment. | ||
/// </summary> | ||
[TestMethod] | ||
public async Task VerifyGetTokenAsync_UsingDynamicTypes() | ||
{ | ||
var mockCredential = (object)new MockCredential(); | ||
var requestContext = ReflectionCredentialEnvelope.AzureCore.MakeTokenRequestContext(new[] { "test/scope" }); | ||
|
||
var testResult = await ReflectionCredentialEnvelope.AzureCore.InvokeGetTokenAsync(mockCredential, requestContext, CancellationToken.None); | ||
|
||
Assert.AreEqual("TEST TOKEN test/scope", testResult); | ||
} | ||
|
||
/// <summary> | ||
/// This test verifies that both <see cref="Azure.Core"/> and <see cref="ReflectionCredentialEnvelope"/> return identical tokens. | ||
/// </summary> | ||
[TestMethod] | ||
public void VerifyGetToken_ReturnsValidToken() | ||
{ | ||
var requestContext = new TokenRequestContext(scopes: CredentialConstants.GetScopes()); | ||
var mockCredential = new MockCredential(); | ||
var tokenUsingTypes = mockCredential.GetToken(requestContext, CancellationToken.None); | ||
|
||
var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential); | ||
var tokenUsingReflection = reflectionCredentialEnvelope.GetToken(); | ||
|
||
Assert.AreEqual(tokenUsingTypes.Token, tokenUsingReflection); | ||
} | ||
|
||
/// <summary> | ||
/// This test verifies that both <see cref="Azure.Core"/> and <see cref="ReflectionCredentialEnvelope"/> return identical tokens. | ||
/// </summary> | ||
[TestMethod] | ||
public async Task VerifyGetTokenAsync_ReturnsValidToken() | ||
{ | ||
var requestContext = new TokenRequestContext(scopes: CredentialConstants.GetScopes()); | ||
var mockCredential = new MockCredential(); | ||
var tokenUsingTypes = await mockCredential.GetTokenAsync(requestContext, CancellationToken.None); | ||
|
||
var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential); | ||
var tokenUsingReflection = await reflectionCredentialEnvelope.GetTokenAsync(); | ||
|
||
Assert.AreEqual(tokenUsingTypes.Token, tokenUsingReflection); | ||
} | ||
|
||
[TestMethod] | ||
public void VerifyGetToken_IfCredentialThrowsException_EnvelopeReturnsNull() | ||
{ | ||
Mock<TokenCredential> mockTokenCredential = new Mock<TokenCredential>(); | ||
mockTokenCredential.Setup(x => x.GetToken(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>())).Throws(new NotImplementedException()); | ||
var mockCredential = mockTokenCredential.Object; | ||
|
||
var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential); | ||
var token = reflectionCredentialEnvelope.GetToken(); | ||
Assert.IsNull(token); | ||
} | ||
|
||
[TestMethod] | ||
public async Task VerifyGetTokenAsync_IfCredentialThrowsException_EnvelopeReturnsNull() | ||
{ | ||
Mock<TokenCredential> mockTokenCredential = new Mock<TokenCredential>(); | ||
mockTokenCredential.Setup(x => x.GetTokenAsync(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>())).Throws(new NotImplementedException()); | ||
var mockCredential = mockTokenCredential.Object; | ||
|
||
var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential); | ||
var token = await reflectionCredentialEnvelope.GetTokenAsync(); | ||
Assert.IsNull(token); | ||
} | ||
|
||
#region TestClasses | ||
|
||
/// <summary> | ||
/// This class inherits <see cref="MockCredential"/> which inherits <see cref="Azure.Core.TokenCredential"/>. | ||
/// This class is used to verify that the <see cref="ReflectionCredentialEnvelope"/> can correctly identify tests that inherit <see cref="Azure.Core.TokenCredential"/>. | ||
/// </summary> | ||
private class TestClassInheritsTokenCredential : MockCredential { } | ||
|
||
private abstract class NotTokenCredential | ||
{ | ||
public abstract AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken); | ||
|
||
public abstract ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken); | ||
} | ||
|
||
private class NotTokenCredential1 : NotTokenCredential | ||
{ | ||
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
|
||
private class NotTokenCredential2 : NotTokenCredential1 { } | ||
#endregion | ||
} | ||
} | ||
#endif |
52 changes: 52 additions & 0 deletions
52
...ensibility/Implementation/Authentication/TelemetryConfigurationCredentialEnvelopeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#if !NET452 && !NET46 | ||
namespace Microsoft.ApplicationInsights.TestFramework.Extensibility.Implementation.Authentication | ||
{ | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
using Microsoft.ApplicationInsights.Extensibility; | ||
using Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
/// <summary> | ||
/// These tests verify that <see cref="TelemetryConfiguration"/> can receive and store an instance of <see cref="Azure.Core.TokenCredential"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// These tests do not run in NET452 OR NET46. | ||
/// In these cases, the test runner is NET452 or NET46 and Azure.Core.TokenCredential is NOT SUPPORTED in these frameworks. | ||
/// This does not affect the end user because we REQUIRE the end user to create their own instance of TokenCredential. | ||
/// This ensures that the end user is consuming the AI SDK in one of the newer frameworks. | ||
/// </remarks> | ||
[TestClass] | ||
[TestCategory("AAD")] | ||
public class TelemetryConfigurationCredentialEnvelopeTests | ||
{ | ||
/// <summary> | ||
/// This tests verifies that each supported language can create and set a Credential. | ||
/// </summary> | ||
[TestMethod] | ||
public void VerifyCanSetCredential() | ||
{ | ||
var mockCredential = new MockCredential(); | ||
|
||
var telemetryConfiguration = new TelemetryConfiguration(); | ||
telemetryConfiguration.SetAzureTokenCredential(mockCredential); | ||
|
||
Assert.IsInstanceOfType(telemetryConfiguration.CredentialEnvelope, typeof(ReflectionCredentialEnvelope)); | ||
Assert.AreEqual(mockCredential, telemetryConfiguration.CredentialEnvelope.Credential, "Credential should be the same instance that we pass in."); | ||
} | ||
|
||
/// <summary> | ||
/// TelemetryConfiguration accepts an <see cref="Object"/> parameter, and uses reflection to verify the type at runtime | ||
/// This test is to verify that we cannot set invalid types. | ||
/// </summary> | ||
[TestMethod] | ||
[ExpectedException(typeof(ArgumentException))] | ||
public void VerifyCannotSetInvalidObjectOnTelemetryConfiguration() | ||
{ | ||
var telemetryConfiguration = new TelemetryConfiguration(); | ||
telemetryConfiguration.SetAzureTokenCredential(Guid.Empty); | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
...ft.ApplicationInsights/Extensibility/Implementation/Authentication/CredentialConstants.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
internal static class CredentialConstants | ||
{ | ||
/// <summary> | ||
/// Source: | ||
/// (https://docs.microsoft.com/azure/active-directory/develop/msal-acquire-cache-tokens#scopes-when-acquiring-tokens). | ||
/// (https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope). | ||
/// </summary> | ||
public const string AzureMonitorScope = "https://monitor.azure.com//.default"; // TODO: THIS SCOPE IS UNVERIFIED. WAITING FOR SERVICES TEAM TO PROVIDE AN INT ENVIRONMENT FOR E2E TESTING. | ||
|
||
/// <summary> | ||
/// Get scopes for Azure Monitor as an array. | ||
/// </summary> | ||
/// <returns>An array of scopes.</returns> | ||
public static string[] GetScopes() => new string[] { AzureMonitorScope }; | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
...ft.ApplicationInsights/Extensibility/Implementation/Authentication/ICredentialEnvelope.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
namespace Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication | ||
{ | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
internal interface ICredentialEnvelope | ||
{ | ||
/// <summary> | ||
/// Gets the TokenCredential instance held by this class. | ||
/// </summary> | ||
object Credential { get; } | ||
|
||
/// <summary> | ||
/// Gets an Azure.Core.AccessToken. | ||
/// </summary> | ||
/// <param name="cancellationToken">The System.Threading.CancellationToken to use.</param> | ||
/// <returns>A valid Azure.Core.AccessToken.</returns> | ||
string GetToken(CancellationToken cancellationToken = default); | ||
|
||
/// <summary> | ||
/// Gets an Azure.Core.AccessToken. | ||
/// </summary> | ||
/// <param name="cancellationToken">The System.Threading.CancellationToken to use.</param> | ||
/// <returns>A valid Azure.Core.AccessToken.</returns> | ||
Task<string> GetTokenAsync(CancellationToken cancellationToken = default); | ||
} | ||
} |
Oops, something went wrong.