Example #1
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 #2
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 refresh");
                ConfidentialClientApplication app = SetupCca(harness);
                UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, expired: true);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                Trace.WriteLine("2. Configure AAD to be unavailable");
                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.WaitTo_AssertAcessCounts(1, 0);
            }
        }
Example #3
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");

                UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor);

                TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess();

                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 unavailable");
                YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0);
                Assert.AreEqual(0, harness.HttpManager.QueueSize);
                cacheAccess.WaitTo_AssertAcessCounts(1, 0); // the refresh failed, no new data is written to the cache

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

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

                Assert.IsNotNull(result);

                cacheAccess.WaitTo_AssertAcessCounts(2, 1); // new tokens written to cache
            }
        }
Example #4
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 #5
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 #6
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
            }
        }