Skip to content

Commit 539c837

Browse files
Send auth flow events before early exiting when no success events exist (#145)
1 parent b591a3d commit 539c837

3 files changed

Lines changed: 63 additions & 84 deletions

File tree

src/AzureAuth.Test/CommandMainTest.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.Authentication.AzureAuth.Test
1111
using System.Runtime.InteropServices;
1212
using FluentAssertions;
1313
using Microsoft.Authentication.MSALWrapper;
14+
using Microsoft.Authentication.MSALWrapper.AuthFlow;
1415
using Microsoft.Extensions.DependencyInjection;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.Identity.Client;
@@ -61,9 +62,9 @@ internal class CommandMainTest
6162
private IFileSystem fileSystem;
6263
private IServiceProvider serviceProvider;
6364
private MemoryTarget logTarget;
64-
private Mock<ITokenFetcher> tokenFetcherMock;
6565
private Mock<IEnv> envMock;
6666
private Mock<ITelemetryService> telemetryServiceMock;
67+
private Mock<IAuthFlow> authFlowMock;
6768

6869
/// <summary>
6970
/// The setup.
@@ -82,14 +83,9 @@ public void Setup()
8283
loggingConfig.AddTarget(this.logTarget);
8384
loggingConfig.AddRuleForAllLevels(this.logTarget);
8485

85-
// Setup moq token fetcher
86-
// When registering the token fetcher here the DI framework will match against
87-
// the most specific constructor (i.e. most params) that it knows how to construct.
88-
// Meaning all param types are also registered with the DI service provider.
89-
this.tokenFetcherMock = new Mock<ITokenFetcher>(MockBehavior.Strict);
90-
9186
this.envMock = new Mock<IEnv>(MockBehavior.Strict);
9287
this.telemetryServiceMock = new Mock<ITelemetryService>(MockBehavior.Strict);
88+
this.authFlowMock = new Mock<IAuthFlow>(MockBehavior.Strict);
9389

9490
// Environment variables should be null by default.
9591
this.envMock.Setup(env => env.Get(It.IsAny<string>())).Returns((string)null);
@@ -104,9 +100,9 @@ public void Setup()
104100
})
105101
.AddSingleton(this.eventData)
106102
.AddSingleton(this.fileSystem)
107-
.AddSingleton<ITokenFetcher>(this.tokenFetcherMock.Object)
108103
.AddSingleton<IEnv>(this.envMock.Object)
109104
.AddSingleton<ITelemetryService>(this.telemetryServiceMock.Object)
105+
.AddSingleton<IAuthFlow>(this.authFlowMock.Object)
110106
.AddTransient<CommandMain>()
111107
.BuildServiceProvider();
112108
}
@@ -575,6 +571,39 @@ public void TestGenerateEvent_From_AuthFlowResult_With_Errors_And_Null_TokenResu
575571
eventData.Measures.Should().ContainKey("duration_milliseconds");
576572
}
577573

574+
[Test]
575+
public void TestSendEvent_From_AuthFlowResult_With_Errors_And_Null_TokenResult()
576+
{
577+
var errors = new[]
578+
{
579+
new Exception("Exception 1."),
580+
};
581+
582+
AuthFlowResult authFlowResult = new AuthFlowResult(null, errors, "Sample");
583+
584+
this.authFlowMock.Setup((a) => a.GetTokenAsync()).ReturnsAsync(authFlowResult);
585+
586+
// It would be nice to increase the assertion level here to include specific attributes on the eventData sent.
587+
// The mock setup matching with strict behavior is difficult to get right, and we have other tests already validating the contents
588+
// of events generated have what we expect. This validates we are in fact sending them.
589+
this.telemetryServiceMock.Setup(s => s.SendEvent("authflow_Sample", It.IsAny<EventData>()));
590+
591+
var subject = this.serviceProvider.GetService<CommandMain>();
592+
593+
// mock valid args
594+
subject.Resource = "f0e8d801-3a50-48fd-b2da-6476d6e832a2";
595+
subject.Client = "e19f71ed-3b14-448d-9346-9eff9753646b";
596+
subject.Tenant = "9f6227ee-3d14-473e-8bed-1281171ef8c9";
597+
subject.EvaluateOptions().Should().BeTrue();
598+
599+
// Act
600+
// Note: If the mock telem service matching fails, the OnExecute's global try-catch will hide that failure.
601+
subject.OnExecute().Should().Be(1);
602+
603+
// Assert
604+
this.telemetryServiceMock.VerifyAll();
605+
}
606+
578607
/// <summary>
579608
/// Test to generate event data from an authflow result with token result and msal errors.
580609
/// </summary>

src/AzureAuth/CommandMain.cs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,9 @@ private int GetToken()
483483
this.eventData.Add("error_count", errors.Length);
484484
this.eventData.Add("authflow_count", results.Length);
485485

486+
// Send custom telemetry events for each authflow result.
487+
this.SendAuthFlowTelemetryEvents(results);
488+
486489
if (successfulResult == null)
487490
{
488491
this.logger.LogError("Authentication failed. Re-run with '--verbosity debug' to get see more info.");
@@ -507,9 +510,6 @@ private int GetToken()
507510
case OutputMode.None:
508511
break;
509512
}
510-
511-
// Send custom telemetry events for each authflow result.
512-
this.SendAuthFlowTelemetryEvents(results);
513513
}
514514
catch (Exception ex)
515515
{
@@ -535,24 +535,31 @@ private void SendAuthFlowTelemetryEvents(AuthFlowResult[] results)
535535

536536
private AuthFlowExecutor AuthFlowExecutor()
537537
{
538-
if (this.authFlowExecutor == null)
538+
// TODO: Really we need to get rid of Resource
539+
var scopes = this.Scopes ?? new string[] { $"{this.authSettings.Resource}/.default" };
540+
541+
IEnumerable<IAuthFlow> authFlows = null;
542+
if (this.authFlow != null)
539543
{
540-
// TODO: Really we need to get rid of Resource
541-
var scopes = this.Scopes ?? new string[] { $"{this.authSettings.Resource}/.default" };
542-
543-
var authFlows = AuthFlowFactory.Create(
544-
this.logger,
545-
this.CombinedAuthMode,
546-
new Guid(this.authSettings.Client),
547-
new Guid(this.authSettings.Tenant),
548-
scopes,
549-
this.CacheFilePath,
550-
this.PreferredDomain,
551-
PrefixedPromptHint(this.authSettings.PromptHint),
552-
Constants.AuthOSXKeyChainSuffix);
553-
554-
this.authFlowExecutor = new AuthFlowExecutor(this.logger, authFlows, this.StopwatchTracker());
544+
// if this.authFlow has been injected - use that.
545+
authFlows = new[] { this.authFlow };
555546
}
547+
else
548+
{
549+
// Normal production flow
550+
authFlows = AuthFlowFactory.Create(
551+
this.logger,
552+
this.CombinedAuthMode,
553+
new Guid(this.authSettings.Client),
554+
new Guid(this.authSettings.Tenant),
555+
scopes,
556+
this.CacheFilePath,
557+
this.PreferredDomain,
558+
PrefixedPromptHint(this.authSettings.PromptHint),
559+
Constants.AuthOSXKeyChainSuffix);
560+
}
561+
562+
this.authFlowExecutor = new AuthFlowExecutor(this.logger, authFlows, this.StopwatchTracker());
556563

557564
return this.authFlowExecutor;
558565
}

src/MSALWrapper/ITokenFetcher.cs

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)