Example #1
0
        public async Task ClientCreds_Expired_NeedsRefresh_AADInvalidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT = expired and needs refersh");
                ConfidentialClientApplication app = SetupCca(harness);
                var atItem = TokenCacheHelper.CreateAccessTokenItem();
                UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1), true);
                TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("2. Configure AAD to be unavaiable");
                harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503);
                harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503);

                // Act
                MsalServiceException ex = await AssertException.TaskThrowsAsync <MsalServiceException>(() => app
                                                                                                       .AcquireTokenForClient(TestConstants.s_scope)
                                                                                                       .ExecuteAsync())
                                          .ConfigureAwait(false);

                Assert.IsFalse(ex is MsalUiRequiredException, "5xx exceptions do not translate to MsalUIRequired");
                Assert.AreEqual(503, ex.StatusCode);
                cacheAccess.AssertAccessCounts(1, 0);
            }
        }
Example #2
0
        public async Task TelemetryCacheRefreshTestAsync()
        {
            using (_harness = CreateTestHarness())
            {
                _harness.HttpManager.AddInstanceDiscoveryMockHandler();

                _app = PublicClientApplicationBuilder.Create(TestConstants.ClientId)
                       .WithHttpManager(_harness.HttpManager)
                       .WithDefaultRedirectUri()
                       .WithLogging((lvl, msg, pii) => Trace.WriteLine($"[MSAL_LOG][{lvl}] {msg}"))
                       .BuildConcrete();
                TokenCacheHelper.PopulateCache(_app.UserTokenCacheInternal.Accessor, addSecondAt: false);

                Trace.WriteLine("Step 1. Acquire Token Interactive successful");
                var result = await RunAcquireTokenInteractiveAsync(AcquireTokenInteractiveOutcome.Success).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenInteractive, CacheRefreshReason.NotApplicable);
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0);

                Trace.WriteLine("Step 2. Acquire Token Silent successful - AT served from cache");
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessFromCache).ConfigureAwait(false);

                Assert.IsNull(result.HttpRequest, "No calls are made to the token endpoint");

                Trace.WriteLine("Step 3. Acquire Token Silent successful - via expired token");
                UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, expired: true);
                TokenCacheAccessRecorder cacheAccess = _app.UserTokenCache.RecordAccess();
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.Expired, isCacheSerialized: true);
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 1);

                Trace.WriteLine("Step 4. Acquire Token Silent successful - via refresh on");
                UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor);
                cacheAccess = _app.UserTokenCache.RecordAccess();
                result      = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.ProactivelyRefreshed, isCacheSerialized: true);

                // Use reflection to get the value and wait till achieved
                HttpTelemetryManager httpTelemetryManager = (HttpTelemetryManager)_app.ServiceBundle.HttpTelemetryManager;
                Type      httpTeleMgrType = typeof(HttpTelemetryManager);
                FieldInfo field           = httpTeleMgrType.GetField("_successfullSilentCallCount", BindingFlags.NonPublic | BindingFlags.Instance);
                Assert.IsTrue(YieldTillSatisfied(() =>
                {
                    var actual = (int)field.GetValue(httpTelemetryManager);
                    return(actual == 0);
                }));

                Trace.WriteLine("Step 5. Acquire Token Silent with force_refresh = true");
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh, forceRefresh : true).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.ForceRefreshOrClaims, isCacheSerialized: true);
                Assert.IsTrue(YieldTillSatisfied(() =>
                {
                    var actual = (int)field.GetValue(httpTelemetryManager);
                    return(actual == 0);
                }));
            }
        }
        public async Task ATS_NonExpired_NeedsRefresh_AADInvalidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();


                Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error");
                AddHttpMocks(TokenResponseType.Invalid_AADAvailable, harness.HttpManager, pca: true);
                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);

                // Act
                await AssertException.TaskThrowsAsync <MsalUiRequiredException>(() => app
                                                                                .AcquireTokenSilent(
                                                                                    TestConstants.s_scope.ToArray(),
                                                                                    account)
                                                                                .ExecuteAsync())
                .ConfigureAwait(false);
            }
        }
Example #4
0
        public async Task ClientCreds_NonExpired_NeedsRefresh_AADInvalidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app");
                ConfidentialClientApplication app = SetupCca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");

                MsalAccessTokenCacheItem atItem = TokenCacheHelper.CreateAccessTokenItem();
                UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error");
                harness.HttpManager.AddAllMocks(TokenResponseType.InvalidGrant);

                // Act
                await AssertException.TaskThrowsAsync <MsalUiRequiredException>(() =>
                                                                                app.AcquireTokenForClient(TestConstants.s_scope)
                                                                                .ExecuteAsync())
                .ConfigureAwait(false);

                cacheAccess.AssertAccessCounts(1, 0);
            }
        }
        public async Task ClientCreds_NonExpired_NeedsRefresh_ValidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                ConfidentialClientApplication app = SetupCca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow");
                AddHttpMocks(TokenResponseType.Valid, harness.HttpManager, pca: false);

                // Act
                Trace.WriteLine("4. ATS - should perform an RT refresh");
                AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope)
                                              .ExecuteAsync()
                                              .ConfigureAwait(false);

                // Assert
                Assert.IsNotNull(result);
                Assert.AreEqual(0, harness.HttpManager.QueueSize,
                                "MSAL should have refreshed the token because the original AT was marked for refresh");
                cacheAccess.AssertAccessCounts(1, 1);
            }
        }
        public async Task ClientCreds_Expired_NeedsRefresh_AADInvalidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                ConfidentialClientApplication app = SetupCca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed, but is also expired");
                UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true);
                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to be unavaiable");
                AddHttpMocks(TokenResponseType.Invalid_AADUnavailable, harness.HttpManager, pca: false);

                // Act
                MsalServiceException ex = await AssertException.TaskThrowsAsync <MsalServiceException>(() => app
                                                                                                       .AcquireTokenForClient(TestConstants.s_scope)
                                                                                                       .ExecuteAsync())
                                          .ConfigureAwait(false);

                Assert.IsFalse(ex is MsalUiRequiredException, "5xx exceptions do not translate to MsalUIRequired");
                Assert.AreEqual(504, ex.StatusCode);
                cacheAccess.AssertAccessCounts(1, 0);
            }
        }
Example #7
0
        public async Task ATS_Expired_NeedsRefresh_AADInvalidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed, but is also expired");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true);
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to be unavaiable");
                harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503);
                harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503);

                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);

                // Act
                MsalServiceException ex = await AssertException.TaskThrowsAsync <MsalServiceException>(() => app
                                                                                                       .AcquireTokenSilent(
                                                                                                           TestConstants.s_scope.ToArray(),
                                                                                                           account)
                                                                                                       .ExecuteAsync())
                                          .ConfigureAwait(false);

                Assert.IsFalse(ex is MsalUiRequiredException, "5xx exceptions do not translate to MsalUIRequired");
                Assert.AreEqual(503, ex.StatusCode);
            }
        }
Example #8
0
        [Ignore] // unstable, see https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2918
        public async Task ATS_NonExpired_NeedsRefresh_ValidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                var refreshOn = UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor).RefreshOn;
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow");
                harness.HttpManager.AddAllMocks(TokenResponseType.Valid_UserFlows);

                // Act
                Trace.WriteLine("4. ATS - should perform an RT refresh");
                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);
                AuthenticationResult result = await app
                                              .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                                              .ExecuteAsync(CancellationToken.None)
                                              .ConfigureAwait(false);

                // Assert token is proactivly refreshed
                Assert.IsNotNull(result);
                Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.ProactivelyRefreshed);
                Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == refreshOn);

                // The following can be indeterministic due to background threading nature
                // So it is verified on check and wait basis

                YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0);

                Assert.AreEqual(0, harness.HttpManager.QueueSize,
                                "MSAL should have refreshed the token because the original AT was marked for refresh");
                cacheAccess.WaitTo_AssertAcessCounts(1, 1);
                MsalAccessTokenCacheItem ati = app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Single();
                Assert.IsTrue(ati.RefreshOn > DateTime.UtcNow + TimeSpan.FromMinutes(10));

                // New ATS does not refresh the AT because RefreshIn is in the future
                Trace.WriteLine("5. ATS - should fetch from the cache only");
                result = await app
                         .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

                Assert.IsNotNull(result);
                Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == ati.RefreshOn);
                Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.NotApplicable);

                cacheAccess.WaitTo_AssertAcessCounts(2, 1);
            }
        }
Example #9
0
        public async Task Interactive_SSHCert_Async()
        {
            LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false);

            IPublicClientApplication pca = PublicClientApplicationBuilder
                                           .Create(labResponse.AppId)
                                           .WithRedirectUri(SeleniumWebUI.FindFreeLocalhostRedirectUri())
                                           .Build();

            TokenCacheAccessRecorder userCacheAccess = pca.UserTokenCache.RecordAccess();

            Trace.WriteLine("Part 1 - Acquire an SSH cert interactively ");
            string jwk = CreateJwk();

            AuthenticationResult result = await pca
                                          .AcquireTokenInteractive(s_scopes)
                                          .WithCustomWebUi(CreateSeleniumCustomWebUI(labResponse.User, Prompt.ForceLogin))
                                          .WithSSHCertificateAuthenticationScheme(jwk, "key1")
                                          .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD
                                          .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token)
                                          .ConfigureAwait(false);

            userCacheAccess.AssertAccessCounts(0, 1);
            Assert.AreEqual("ssh-cert", result.TokenType);
            IAccount account = await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false);

            userCacheAccess.AssertAccessCounts(1, 1); // the assert calls GetAccounts

            Trace.WriteLine("Part 2 - Acquire a token silent with the same keyID - should be served from the cache");
            result = await pca
                     .AcquireTokenSilent(s_scopes, account)
                     .WithSSHCertificateAuthenticationScheme(jwk, "key1")
                     .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD
                     .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token)
                     .ConfigureAwait(false);

            userCacheAccess.AssertAccessCounts(2, 1);

            account = await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false);

            userCacheAccess.AssertAccessCounts(3, 1);

            Trace.WriteLine("Part 3 - Acquire a token silent with a different keyID - should not sbe served from the cache");
            result = await pca
                     .AcquireTokenSilent(s_scopes, account)
                     .WithSSHCertificateAuthenticationScheme(jwk, "key2")
                     .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD
                     .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token)
                     .ConfigureAwait(false);

            Assert.AreEqual("ssh-cert", result.TokenType);
            userCacheAccess.AssertAccessCounts(4, 2);
            await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false);
        }
Example #10
0
 private static void AssertCancellationToken(TokenCacheAccessRecorder cacheAccessRecorder, CancellationTokenSource cancellationTokenSource, bool write = false)
 {
     Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastAfterAccessNotificationArgs.CancellationToken);
     Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastAfterAccessNotificationArgs.CancellationToken);
     Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastBeforeAccessNotificationArgs.CancellationToken);
     Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastBeforeAccessNotificationArgs.CancellationToken);
     if (write)
     {
         Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastBeforeWriteNotificationArgs.CancellationToken);
         Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastBeforeWriteNotificationArgs.CancellationToken);
     }
 }
 private static void AssertCacheKey(TokenCacheAccessRecorder cacheAccess, string expectedKey)
 {
     Assert.AreEqual(
         expectedKey,
         cacheAccess.LastAfterAccessNotificationArgs.SuggestedCacheKey);
     Assert.AreEqual(
         expectedKey,
         cacheAccess.LastBeforeAccessNotificationArgs.SuggestedCacheKey);
     Assert.AreEqual(
         expectedKey,
         cacheAccess.LastBeforeWriteNotificationArgs.SuggestedCacheKey);
 }
Example #12
0
        public async Task ATS_NonExpired_NeedsRefresh_AADUnavailableResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with a 500 error");
                harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503);
                harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503);


                // Act
                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);
                AuthenticationResult result = await app
                                              .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                                              .ExecuteAsync(CancellationToken.None)
                                              .ConfigureAwait(false);

                // Assert
                Assert.IsNotNull(result, "ATS still succeeds even though AAD is unavaible");
                Assert.AreEqual(0, harness.HttpManager.QueueSize);
                cacheAccess.AssertAccessCounts(1, 0); // the refresh failed, no new data is written to the cache

                // reset throttling, otherwise MSAL would block similar requests for 2 minutes
                // and we would still get a cached response
                SingletonThrottlingManager.GetInstance().ResetCache();

                // Now let AAD respond with tokens
                harness.HttpManager.AddTokenResponse(TokenResponseType.Valid);

                result = await app
                         .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

                Assert.IsNotNull(result);
                cacheAccess.AssertAccessCounts(2, 1); // new tokens written to cache
            }
        }
        public async Task TelemetryCacheRefreshTestAsync()
        {
            using (_harness = CreateTestHarness())
            {
                _harness.HttpManager.AddInstanceDiscoveryMockHandler();

                _app = PublicClientApplicationBuilder.Create(TestConstants.ClientId)
                       .WithHttpManager(_harness.HttpManager)
                       .WithDefaultRedirectUri()
                       .WithLogging((lvl, msg, pii) => Trace.WriteLine($"[MSAL_LOG][{lvl}] {msg}"))
                       .BuildConcrete();
                var tokenCacheHelper = new TokenCacheHelper();
                tokenCacheHelper.PopulateCache(_app.UserTokenCacheInternal.Accessor, addSecondAt: false);

                Trace.WriteLine("Step 1. Acquire Token Interactive successful");
                var result = await RunAcquireTokenInteractiveAsync(AcquireTokenInteractiveOutcome.Success).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenInteractive, forceRefresh: false);
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); // Previous_request = 2|0|||

                Trace.WriteLine("Step 2. Acquire Token Silent successful - AT served from cache");
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessFromCache).ConfigureAwait(false);

                Assert.IsNull(result.HttpRequest, "No calls are made to the token endpoint");

                Trace.WriteLine("Step 3. Acquire Token Silent successful - via expired token");
                UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true);
                TokenCacheAccessRecorder cacheAccess = _app.UserTokenCache.RecordAccess();
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: false, isCacheSerialized: true, cacheRefresh: "1");
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 1); // Previous_request = 2|1|||

                Trace.WriteLine("Step 4. Acquire Token Silent successful - via refresh on");
                UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                cacheAccess = _app.UserTokenCache.RecordAccess();
                result      = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: false, isCacheSerialized: true, cacheRefresh: "2");
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); // Previous_request = 2|1|||

                Trace.WriteLine("Step 5. Acquire Token Silent with force_refresh = true");
                result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh, forceRefresh : true).ConfigureAwait(false);

                AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: true, isCacheSerialized: true, cacheRefresh: "3"); // Current_request = 3 | ATS_ID, 1, 3 |
                AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0);                                                                   // Previous_request = 2|0|||
            }
        }
        public async Task ATS_NonExpired_NeedsRefresh_ValidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow");
                AddHttpMocks(TokenResponseType.Valid, harness.HttpManager, pca: true);

                // Act
                Trace.WriteLine("4. ATS - should perform an RT refresh");
                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);
                AuthenticationResult result = await app
                                              .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                                              .ExecuteAsync(CancellationToken.None)
                                              .ConfigureAwait(false);

                // Assert
                Assert.IsNotNull(result);
                Assert.AreEqual(0, harness.HttpManager.QueueSize,
                                "MSAL should have refreshed the token because the original AT was marked for refresh");
                cacheAccess.AssertAccessCounts(1, 1);
                MsalAccessTokenCacheItem ati = app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Single();
                Assert.IsTrue(ati.RefreshOn > DateTime.UtcNow + TimeSpan.FromMinutes(10));

                // New ATS does not refresh the AT because RefreshIn is in the future
                Trace.WriteLine("5. ATS - should fetch from the cache only");
                result = await app
                         .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

                Assert.IsNotNull(result);

                cacheAccess.AssertAccessCounts(2, 1);
            }
        }
Example #15
0
        public async Task ClientCreds_NonExpired_NeedsRefresh_AADUnavailableResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app ");
                ConfidentialClientApplication app = SetupCca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");

                MsalAccessTokenCacheItem atItem = TokenCacheHelper.CreateAccessTokenItem();
                UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1));
                TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                //UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1));


                Trace.WriteLine("3. Configure AAD to respond with an error");
                harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503);
                harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503);

                // Act
                AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope)
                                              .ExecuteAsync()
                                              .ConfigureAwait(false);

                // Assert
                Assert.IsNotNull(result, "ClientCreds should still succeeds even though AAD is unavaible");
                Assert.AreEqual(0, harness.HttpManager.QueueSize);
                cacheAccess.AssertAccessCounts(1, 0); // the refresh failed, no new data is written to the cache

                // Now let AAD respond with tokens
                harness.HttpManager.AddTokenResponse(TokenResponseType.Valid);

                result = await app.AcquireTokenForClient(TestConstants.s_scope)
                         .ExecuteAsync()
                         .ConfigureAwait(false);

                Assert.IsNotNull(result);
                cacheAccess.AssertAccessCounts(2, 1); // new tokens written to cache
            }
        }
Example #16
0
        public async Task ClientCreds_NonExpired_NeedsRefresh_ValidResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app");
                ConfidentialClientApplication app = SetupCca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                var refreshOn = UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor).RefreshOn;

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow");
                harness.HttpManager.AddAllMocks(TokenResponseType.Valid_ClientCredentials);

                // Act
                Trace.WriteLine("4. ATS - should perform an RT refresh");
                AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope)
                                              .ExecuteAsync()
                                              .ConfigureAwait(false);

                // Assert
                YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0);
                Assert.IsNotNull(result);
                Assert.AreEqual(0, harness.HttpManager.QueueSize,
                                "MSAL should have refreshed the token because the original AT was marked for refresh");
                cacheAccess.WaitTo_AssertAcessCounts(1, 1);
                Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.ProactivelyRefreshed);
                Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == refreshOn);

                result = await app.AcquireTokenForClient(TestConstants.s_scope)
                         .ExecuteAsync()
                         .ConfigureAwait(false);

                Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.NotApplicable);
            }
        }
Example #17
0
        public async Task ClientCreds_NonExpired_NeedsRefresh_AADInvalidResponse_Async()
        {
            bool wasErrorLogged = false;

            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app");
                ConfidentialClientApplication app = SetupCca(harness, LocalLogCallback);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");

                UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error");
                harness.HttpManager.AddAllMocks(TokenResponseType.InvalidGrant);

                // Act
                await app.AcquireTokenForClient(TestConstants.s_scope)
                .ExecuteAsync()
                .ConfigureAwait(false);

                Assert.IsTrue(YieldTillSatisfied(() => wasErrorLogged == true));
                cacheAccess.WaitTo_AssertAcessCounts(1, 0);
            }

            void LocalLogCallback(LogLevel level, string message, bool containsPii)
            {
                if (level == LogLevel.Error &&
                    message.Contains(BackgroundFetch_Failed))
                {
                    wasErrorLogged = true;
                }
            }
        }
Example #18
0
        public async Task ATS_NonExpired_NeedsRefresh_AADInvalidResponse_Async()
        {
            bool wasErrorLogged = false;

            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness, LocalLogCallback);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor);
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error");
                harness.HttpManager.AddAllMocks(TokenResponseType.InvalidGrant);

                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);

                await app.AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                .ExecuteAsync()
                .ConfigureAwait(false);

                Assert.IsTrue(YieldTillSatisfied(() => wasErrorLogged == true));
            }

            void LocalLogCallback(LogLevel level, string message, bool containsPii)
            {
                if (level == LogLevel.Error &&
                    message.Contains(BackgroundFetch_Failed))
                {
                    wasErrorLogged = true;
                }
            }
        }
Example #19
0
        public async Task ATS_NonExpired_NeedsRefresh_AADUnavailableResponse_Async()
        {
            // Arrange
            using (MockHttpAndServiceBundle harness = base.CreateTestHarness())
            {
                Trace.WriteLine("1. Setup an app with a token cache with one AT");
                PublicClientApplication app = SetupPca(harness);

                Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed");
                UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor);
                TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess();

                Trace.WriteLine("3. Configure AAD to respond with a 500 error");
                harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503);
                harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503);

                // Act
                var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null);
                AuthenticationResult result = await app
                                              .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                                              .ExecuteAsync(CancellationToken.None)
                                              .ConfigureAwait(false);

                // The following can be indeterministic due to background threading nature
                // So it is verified on check and wait basis
                Assert.IsTrue(YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0), "Background refresh 1 did not execute.");

                // Assert
                Assert.AreEqual(0, harness.HttpManager.QueueSize);
                Assert.AreEqual(CacheRefreshReason.ProactivelyRefreshed, result.AuthenticationResultMetadata.CacheRefreshReason);

                cacheAccess.WaitTo_AssertAcessCounts(1, 0); // the refresh failed, no new data is written to the cache

                // reset throttling, otherwise MSAL would block similar requests for 2 minutes
                // and we would still get a cached response
                SingletonThrottlingManager.GetInstance().ResetCache();

                // Now let AAD respond with tokens
                harness.HttpManager.AddTokenResponse(TokenResponseType.Valid_UserFlows);

                result = await app
                         .AcquireTokenSilent(
                    TestConstants.s_scope.ToArray(),
                    account)
                         .ExecuteAsync(CancellationToken.None)
                         .ConfigureAwait(false);

                Assert.AreEqual(CacheRefreshReason.ProactivelyRefreshed, result.AuthenticationResultMetadata.CacheRefreshReason);

                Assert.IsTrue(YieldTillSatisfied(
                                  () => harness.HttpManager.QueueSize == 0),
                              "Background refresh 2 did not execute.");
                Assert.IsTrue(
                    YieldTillSatisfied(() => cacheAccess.AfterAccessTotalCount == 3),
                    "The background refresh executed, but the cache was not updated");

                cacheAccess.WaitTo_AssertAcessCounts(2, 1); // new tokens written to cache
            }
        }