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

AAD: Handling Azure.Core.TokenCredential #2191

Merged
merged 64 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
84eba5c
getting started. defining classes and tests
TimothyMothra Mar 11, 2021
180c039
more tests. currently passing
TimothyMothra Mar 12, 2021
d501eef
cleanup
TimothyMothra Mar 12, 2021
5a556d3
saving work in progress
TimothyMothra Mar 23, 2021
2f7f7f8
cleanup
TimothyMothra Mar 24, 2021
9beac17
cleanup
TimothyMothra Mar 24, 2021
14b65b2
cleanup
TimothyMothra Mar 24, 2021
8446075
save work in progress. all tests passing
TimothyMothra Mar 24, 2021
0fe4b8c
cleanup
TimothyMothra Mar 24, 2021
52785bb
change interface to abstract class.
TimothyMothra Mar 25, 2021
acb3fd0
cleanup
TimothyMothra Mar 25, 2021
66592c9
working reflection for GetTokenAsync
TimothyMothra Mar 25, 2021
33570ec
cleanup. tests pass
TimothyMothra Mar 25, 2021
f181df5
cleanup
TimothyMothra Mar 26, 2021
7b30407
rewriting reflection to fully use Expression.Lambda. tests partially …
TimothyMothra Mar 27, 2021
4e38ac2
cleanup
TimothyMothra Mar 29, 2021
862d3f2
change from Lambda<Func> to LambdaExpression. Tests pass
TimothyMothra Mar 29, 2021
ddcbecd
have a working example of aysnc + Expression tree. need to cleanup
TimothyMothra Mar 30, 2021
0a82f7b
this works. tests pass. needs significant cleanup.
TimothyMothra Mar 30, 2021
47ce7e5
significant refactor. cleanup. all tests pass
TimothyMothra Mar 30, 2021
76fd2d7
update scope
TimothyMothra Mar 31, 2021
c5e44de
save change to publicapi doc
TimothyMothra Mar 31, 2021
46f35e6
cleanup
TimothyMothra Mar 31, 2021
2464125
set default in abstract class
TimothyMothra Apr 5, 2021
8a93434
Merge branch 'develop' into tilee/feature_aad
TimothyMothra Apr 14, 2021
159bbc9
update publicapi
TimothyMothra Apr 14, 2021
f03f03f
Merge branch 'develop' into tilee/feature_aad
TimothyMothra Apr 15, 2021
740c1d0
merge develop
TimothyMothra Apr 28, 2021
003873b
Merge branch 'develop' into tilee/feature_aad
TimothyMothra May 7, 2021
1ff482a
fix dependencies
TimothyMothra May 7, 2021
87876f2
fix dependencies
TimothyMothra May 7, 2021
930ef53
Merge branch 'develop' into tilee/feature_aad
TimothyMothra May 20, 2021
c9735a9
add new project
TimothyMothra May 20, 2021
b0b4b7e
add .Net v5 to linux build definition
TimothyMothra May 20, 2021
ca89417
cleanup
TimothyMothra May 20, 2021
5a4d63e
testing change to linux definition
TimothyMothra May 20, 2021
e4050ec
add support for net5.0 to Microsoft.ApplicationInsights.Tests.csproj
TimothyMothra May 20, 2021
5773479
fix for TimeSpan cannot be null
TimothyMothra May 20, 2021
926a6e7
fix
TimothyMothra May 20, 2021
0412341
remove extra test project
TimothyMothra May 20, 2021
0bcd87f
cleanup yml
TimothyMothra May 20, 2021
57088cb
disabling netcoreapp 2.1 in linux build.
TimothyMothra May 20, 2021
bbecdd8
Merge branch 'tilee/aad_newtestproject' into tilee/feature_aad
TimothyMothra May 20, 2021
a7c3462
migrate AAD tests to Base Test project
TimothyMothra May 21, 2021
5d6011a
update Endpoints for Ingestion
TimothyMothra May 21, 2021
edb4e57
merge develop
TimothyMothra May 21, 2021
d4b0378
moving tests to Base Test project
TimothyMothra May 21, 2021
a82e321
remove Authentication.Test project
TimothyMothra May 21, 2021
393f0a2
remove change to AssemblyInfo
TimothyMothra May 21, 2021
0bf32ba
resolving merge conflict
TimothyMothra May 21, 2021
5e3ef5d
Revert "resolving merge conflict"
TimothyMothra May 24, 2021
dbe07d0
Merge branch 'develop' into tilee/feature_aad
TimothyMothra May 24, 2021
dc11249
Merge branch 'develop' into tilee/feature_aad
TimothyMothra May 26, 2021
7d6ad67
remove Azure.Core dependency
TimothyMothra May 26, 2021
2f3b668
cleanup csproj
TimothyMothra May 26, 2021
4b9b761
cleanup EOF
TimothyMothra May 26, 2021
4cf03ca
cleanup test
TimothyMothra May 26, 2021
62bbe38
cleanup. removing abstract class CredentialEnvelope
TimothyMothra May 26, 2021
aac78ed
fxcop
TimothyMothra May 26, 2021
ad1f387
revert changes to EndpointContainer
TimothyMothra May 26, 2021
7bfe41e
add interface ICredentialEnvelope
TimothyMothra May 26, 2021
6b00f3b
exception handling
TimothyMothra May 26, 2021
957f33f
set ConfigureAwait(true)
TimothyMothra May 26, 2021
d9da395
code review comments
TimothyMothra May 27, 2021
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
@@ -1,3 +1,5 @@
Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SetCredential(object tokenCredential) -> void

Microsoft.ApplicationInsights.Channel.IAsyncFlushable
Microsoft.ApplicationInsights.Channel.IAsyncFlushable.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Microsoft.ApplicationInsights.Channel.InMemoryChannel.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SetCredential(object tokenCredential) -> void

Microsoft.ApplicationInsights.Channel.IAsyncFlushable
Microsoft.ApplicationInsights.Channel.IAsyncFlushable.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Microsoft.ApplicationInsights.Channel.InMemoryChannel.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SetCredential(object tokenCredential) -> void

Microsoft.ApplicationInsights.Channel.IAsyncFlushable
Microsoft.ApplicationInsights.Channel.IAsyncFlushable.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Microsoft.ApplicationInsights.Channel.InMemoryChannel.FlushAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>
Expand Down
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#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 TestClass2();
_ = new ReflectionCredentialEnvelope(testClass2);
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
}

[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
private class TestClass1 : Azure.Core.TokenCredential
{
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 TestClass2 : TestClass1 { }

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
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.SetCredential(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.SetCredential(Guid.Empty);
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'netcoreapp2.1' Or '$(TargetFramework)' == 'netcoreapp3.1' Or '$(TargetFramework)' == 'net5.0'">
<PackageReference Include="Azure.Identity" Version="1.3.0" /> <!-- Supports: netstandard2.0 -->
<PackageReference Include="Azure.Core" Version="1.14.0" /> <!-- Supports: net461, netstandard2.0, and net5.0 -->
</ItemGroup>

<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
Expand Down
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 };
}
}
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);
}
}
Loading