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 9 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
23 changes: 23 additions & 0 deletions BASE/Microsoft.ApplicationInsights.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ApplicationInsigh
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelemetryChannel.Tests", "Test\ServerTelemetryChannel.Test\TelemetryChannel.Tests\TelemetryChannel.Tests.csproj", "{7AB3D817-9CAC-45A7-BA4D-25FA4D690DA4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ApplicationInsights.Authentication.Tests", "Test\Microsoft.ApplicationInsights.Test\Microsoft.ApplicationInsights.Authentication.Tests\Microsoft.ApplicationInsights.Authentication.Tests.csproj", "{AADBCDDC-3BD4-434E-B474-B832133AFC84}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Common\Common\Common.projitems*{3273d899-d9b3-44fe-b3ab-578e18b2ef90}*SharedItemsImports = 5
Expand Down Expand Up @@ -202,6 +204,26 @@ Global
{7AB3D817-9CAC-45A7-BA4D-25FA4D690DA4}.Release|x64.Build.0 = Release|Any CPU
{7AB3D817-9CAC-45A7-BA4D-25FA4D690DA4}.Release|x86.ActiveCfg = Release|Any CPU
{7AB3D817-9CAC-45A7-BA4D-25FA4D690DA4}.Release|x86.Build.0 = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|ARM.ActiveCfg = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|ARM.Build.0 = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|x64.ActiveCfg = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|x64.Build.0 = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|x86.ActiveCfg = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Debug|x86.Build.0 = Debug|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|Any CPU.Build.0 = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|ARM.ActiveCfg = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|ARM.Build.0 = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|x64.ActiveCfg = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|x64.Build.0 = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|x86.ActiveCfg = Release|Any CPU
{AADBCDDC-3BD4-434E-B474-B832133AFC84}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -215,6 +237,7 @@ Global
{3273D899-D9B3-44FE-B3AB-578E18B2EF90} = {E7B0521E-DA4D-40EA-B55D-ADCBDA69027C}
{EF007559-EA01-4D4A-9BEB-8CC661797BE5} = {C2FEEDE5-8CAE-41A4-8932-42D284A86EA7}
{7AB3D817-9CAC-45A7-BA4D-25FA4D690DA4} = {A61B048F-ECEA-4BED-A2ED-22834E7D4DFB}
{AADBCDDC-3BD4-434E-B474-B832133AFC84} = {C2FEEDE5-8CAE-41A4-8932-42D284A86EA7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F70D4C7C-02FB-4EE9-875A-A2F95EC90EAC}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
namespace Microsoft.ApplicationInsights.Authentication.Tests
{
using System;
using System.Threading;
using System.Threading.Tasks;

using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Identity;

using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class CredentialEnvelopeTests
{
[TestMethod]
public void VerifyCanSetCredential()
{
var defaultTokenCredential = new MockCredential();

var telemetryConfiguration = new TelemetryConfiguration();
telemetryConfiguration.SetCredential(defaultTokenCredential);


var credentialEnvelope = telemetryConfiguration.CredentialEnvelope;
#if NET461
Assert.IsInstanceOfType(credentialEnvelope, typeof(ReflectionCredentialEnvelope));
#elif NET5_0
Assert.IsInstanceOfType(credentialEnvelope, typeof(TokenCredentialEnvelope));
#else
throw new System.Exception("this is a testing gap.");
#endif

Assert.AreEqual(defaultTokenCredential, telemetryConfiguration.CredentialEnvelope.Credential);
}

#if NET461
/// <summary>
/// For older frameworks, TelemetryConfiguration accepts an <see cref="Object"/> parameter.
/// 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

#if NET5_0
[TestMethod]
public void VerifyCanGetTokenString()
{
var mockCredential = new MockCredential();

var tokenCredentialEnvelope = new TokenCredentialEnvelope(mockCredential);
var token = tokenCredentialEnvelope.GetToken();
Assert.IsNotNull(token);

var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential);
var tokenFromReflection = reflectionCredentialEnvelope.GetToken();
Assert.IsNotNull(tokenFromReflection);

Assert.AreEqual(token, tokenFromReflection);
}

[TestMethod]
public async Task VerifyCanGetTokenStringAsync()
{
var mockCredential = new MockCredential();

var tokenCredentialEnvelope = new TokenCredentialEnvelope(mockCredential);
var token = await tokenCredentialEnvelope.GetTokenAsync();
Assert.IsNotNull(token);

var reflectionCredentialEnvelope = new ReflectionCredentialEnvelope(mockCredential);
var tokenFromReflection = await reflectionCredentialEnvelope.GetTokenAsync();
Assert.IsNotNull(tokenFromReflection);

Assert.AreEqual(token, tokenFromReflection);
}
#endif
/// <remarks>
/// Copied from (https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core.TestFramework/src/MockCredential.cs).
/// </remarks>
private 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(PropsRoot)\Test.props" />

<PropertyGroup>
<TargetFrameworks>net461;net5.0</TargetFrameworks>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />

<PackageReference Include="Azure.Identity" Version="1.3.0" /> <!-- Supports: netstandard2.0 -->
<PackageReference Include="Azure.Core" Version="1.10.0" /> <!-- Supports: net4.6.1, netstandard2.0, net5.0 -->
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.ApplicationInsights\Microsoft.ApplicationInsights.csproj" /> <!-- Supports: net4.5.2, net4.6, netstandard2.0-->
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace Microsoft.ApplicationInsights.Authentication.Tests
{
using System;
using System.Threading;
using System.Threading.Tasks;

using Azure.Core;

using Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication;
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <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>
[TestClass]
public class ReflectionCredentialEnvelopeTests
{
[TestMethod]
public void VerifyCanIdentifyValidClass()
{
var testClass2 = new TestClass2();
_ = new ReflectionCredentialEnvelope(testClass2);
}

[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);
}

#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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 AuthConstants
{
/// <summary>
/// https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#scopes-when-acquiring-tokens
///
/// Other APIs might require that no scheme or host is included in the scope value, and expect only the app ID (a GUID) and the scope name, for example: 11111111-1111-1111-1111-111111111111/api.read
/// </summary>
public const string Scope = "https://storage.azure.com/.default"; // example from Blob Storage. TODO: NEED OUR OWN SCOPE

public static string[] GetScopes() => new string[] { Scope };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication
{
using System.Threading;
using System.Threading.Tasks;

public interface ICredentialEnvelope
{
object Credential { get; }

string GetToken(CancellationToken cancellationToken);

Task<string> GetTokenAsync(CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication
{
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
///
/// </summary>
/// <remarks>
/// Our SDK currently targets net452, net46, and netstandard2.0.
/// Azure.Core.TokenCredential is only available for netstandard2.0.
/// I'm introducing this class as a wrapper so we can receive an instance of this class and pass it around within our SDK.
/// </remarks>
internal class ReflectionCredentialEnvelope : ICredentialEnvelope
{
private readonly object tokenRequestContext;
private readonly MethodInfo getTokenAsyncMethod;
private readonly MethodInfo getTokenMethod;
//private readonly Type accessTokenType;

/// <summary>
///
/// </summary>
/// <remarks>
/// Must use reflection to verify that this object is an instance of Azure.Core.TokenCredential.
/// </remarks>
/// <param name="tokenCredential"></param>
public ReflectionCredentialEnvelope(object tokenCredential)
{
if (this.IsTokenCredential(tokenCredential.GetType()))
{
this.Credential = tokenCredential;

// (https://docs.microsoft.com/en-us/dotnet/api/azure.core.tokenrequestcontext.-ctor?view=azure-dotnet).
// Invoking this constructor: Azure.Core.TokenRequestContext(String[], String, String).
var tokenRequestContextType = Type.GetType("Azure.Core.TokenRequestContext, Azure.Core");
var paramArray = new object[] { AuthConstants.GetScopes(), null, null };
this.tokenRequestContext = Activator.CreateInstance(tokenRequestContextType, args: paramArray);

// (https://docs.microsoft.com/en-us/dotnet/api/azure.core.tokencredential.gettokenasync?view=azure-dotnet).
// Getting a handle for this method: Azure.Core.TokenCredential.GetTokenAsync().
this.getTokenAsyncMethod = this.Credential.GetType().GetMethod("GetTokenAsync");
this.getTokenMethod = this.Credential.GetType().GetMethod("GetToken");

// this.accessTokenType = Type.GetType("Azure.Core.AccessToken, Azure.Core");
}
else
{
throw new ArgumentException($"The provided {nameof(tokenCredential)} must inherit Azure.Core.TokenCredential", nameof(tokenCredential));
}
}

public object Credential { get; private set; }

public string GetToken(CancellationToken cancellationToken = default(CancellationToken))
{
var accessToken = this.getTokenMethod.Invoke(this.Credential, new object[] { this.tokenRequestContext, cancellationToken });
var tokenProperty = accessToken.GetType().GetProperty("Token");
return (string)tokenProperty.GetValue(accessToken);
}

public async Task<string> GetTokenAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return await Task.Run(() => this.GetToken(cancellationToken));

// TODO: THIS DOESN'T WORK. CAN CIRCLE BACK AND INVESTIGATE THIS LATER.
// 'Unable to cast object of type 'System.Threading.Tasks.ValueTask`1[Azure.Core.AccessToken]' to type 'System.Threading.Tasks.Task'.'
// (https://stackoverflow.com/questions/39674988/how-to-call-a-generic-async-method-using-reflection/39679855).
//var task = (Task)this.getTokenAsyncMethod.Invoke(this.Credential, new object[] { this.tokenRequestContext, cancellationToken });
//await task.ConfigureAwait(false);
//var resultProperty = task.GetType().GetProperty("Result");
//var accessToken = resultProperty.GetValue(task);
//var tokenProperty = accessToken.GetType().GetProperty("Token");
//return (string)tokenProperty.GetValue(accessToken);
}

/// <summary>
/// Checks if the input inherits <see cref="Azure.Core.TokenCredential"/>
/// </summary>
private bool IsTokenCredential(Type inputType) => inputType.IsSubclassOf(Type.GetType("Azure.Core.TokenCredential, Azure.Core"));
}
}
Loading