public void TestDeviceCodeCancel()
        {
            using (var httpManager = new MockHttpManager())
            {
                var       serviceBundle = ServiceBundle.CreateWithCustomHttpManager(httpManager);
                const int NumberOfAuthorizationPendingRequestsToInject = 0;
                var       parameters = CreateAuthenticationParametersAndSetupMocks(
                    httpManager,
                    NumberOfAuthorizationPendingRequestsToInject,
                    out HashSet <string> expectedScopes);

                _cache.ServiceBundle = serviceBundle;

                var cancellationSource = new CancellationTokenSource();

                DeviceCodeResult actualDeviceCodeResult = null;
                var request = new DeviceCodeRequest(
                    serviceBundle,
                    parameters,
                    ApiEvent.ApiIds.None,
                    async result =>
                {
                    await Task.Delay(200, CancellationToken.None).ConfigureAwait(false);
                    actualDeviceCodeResult = result;
                });

                // We setup the cancel before calling the RunAsync operation since we don't check the cancel
                // until later and the mock network calls run insanely fast for us to timeout for them.
                cancellationSource.Cancel();
                AssertException.TaskThrows <OperationCanceledException>(() => request.RunAsync(cancellationSource.Token));
            }
        }
Example #2
0
        public void TestDeviceCodeAuthSuccess()
        {
            const int NumberOfAuthorizationPendingRequestsToInject = 1;

            using (var harness = CreateTestHarness())
            {
                var parameters = CreateAuthenticationParametersAndSetupMocks(
                    harness,
                    NumberOfAuthorizationPendingRequestsToInject,
                    out HashSet <string> expectedScopes);

                var cache = parameters.CacheSessionManager.TokenCacheInternal;

                // Check that cache is empty
                Assert.AreEqual(0, cache.Accessor.GetAllAccessTokens().Count());
                Assert.AreEqual(0, cache.Accessor.GetAllRefreshTokens().Count());
                Assert.AreEqual(0, cache.Accessor.GetAllIdTokens().Count());
                Assert.AreEqual(0, cache.Accessor.GetAllAccounts().Count());

                DeviceCodeResult actualDeviceCodeResult = null;

                var deviceCodeParameters = new AcquireTokenWithDeviceCodeParameters
                {
                    DeviceCodeResultCallback = result =>
                    {
                        actualDeviceCodeResult = result;
                        return(Task.FromResult(0));
                    }
                };


                var request = new DeviceCodeRequest(harness.ServiceBundle, parameters, deviceCodeParameters);

                Task <AuthenticationResult> task = request.RunAsync(CancellationToken.None);
                task.Wait();
                var authenticationResult = task.Result;
                Assert.IsNotNull(authenticationResult);
                Assert.IsNotNull(actualDeviceCodeResult);

                Assert.AreEqual(TestConstants.ClientId, actualDeviceCodeResult.ClientId);
                Assert.AreEqual(ExpectedDeviceCode, actualDeviceCodeResult.DeviceCode);
                Assert.AreEqual(ExpectedInterval, actualDeviceCodeResult.Interval);
                Assert.AreEqual(ExpectedMessage, actualDeviceCodeResult.Message);
                Assert.AreEqual(ExpectedUserCode, actualDeviceCodeResult.UserCode);
                Assert.AreEqual(ExpectedVerificationUrl, actualDeviceCodeResult.VerificationUrl);

                CoreAssert.AreScopesEqual(expectedScopes.AsSingleString(), actualDeviceCodeResult.Scopes.AsSingleString());

                // Validate that entries were added to cache
                Assert.AreEqual(1, cache.Accessor.GetAllAccessTokens().Count());
                Assert.AreEqual(1, cache.Accessor.GetAllRefreshTokens().Count());
                Assert.AreEqual(1, cache.Accessor.GetAllIdTokens().Count());
                Assert.AreEqual(1, cache.Accessor.GetAllAccounts().Count());
            }
        }
        public void TestDeviceCodeAuthSuccess()
        {
            const int NumberOfAuthorizationPendingRequestsToInject = 1;

            using (var httpManager = new MockHttpManager())
            {
                var serviceBundle = ServiceBundle.CreateWithCustomHttpManager(httpManager);

                var parameters = CreateAuthenticationParametersAndSetupMocks(
                    httpManager,
                    NumberOfAuthorizationPendingRequestsToInject,
                    out HashSet <string> expectedScopes);

                _cache.ServiceBundle = serviceBundle;

                // Check that cache is empty
                Assert.AreEqual(0, _cache.TokenCacheAccessor.AccessTokenCount);
                Assert.AreEqual(0, _cache.TokenCacheAccessor.AccountCount);
                Assert.AreEqual(0, _cache.TokenCacheAccessor.IdTokenCount);
                Assert.AreEqual(0, _cache.TokenCacheAccessor.RefreshTokenCount);

                DeviceCodeResult actualDeviceCodeResult = null;
                var request = new DeviceCodeRequest(
                    serviceBundle,
                    parameters,
                    ApiEvent.ApiIds.None,
                    result =>
                {
                    actualDeviceCodeResult = result;
                    return(Task.FromResult(0));
                });
                Task <AuthenticationResult> task = request.RunAsync(CancellationToken.None);
                task.Wait();
                var authenticationResult = task.Result;
                Assert.IsNotNull(authenticationResult);
                Assert.IsNotNull(actualDeviceCodeResult);

                Assert.AreEqual(MsalTestConstants.ClientId, actualDeviceCodeResult.ClientId);
                Assert.AreEqual(ExpectedDeviceCode, actualDeviceCodeResult.DeviceCode);
                Assert.AreEqual(ExpectedInterval, actualDeviceCodeResult.Interval);
                Assert.AreEqual(ExpectedMessage, actualDeviceCodeResult.Message);
                Assert.AreEqual(ExpectedUserCode, actualDeviceCodeResult.UserCode);
                Assert.AreEqual(ExpectedVerificationUrl, actualDeviceCodeResult.VerificationUrl);

                CoreAssert.AreScopesEqual(expectedScopes.AsSingleString(), actualDeviceCodeResult.Scopes.AsSingleString());

                // Validate that entries were added to cache
                Assert.AreEqual(1, _cache.TokenCacheAccessor.AccessTokenCount);
                Assert.AreEqual(1, _cache.TokenCacheAccessor.AccountCount);
                Assert.AreEqual(1, _cache.TokenCacheAccessor.IdTokenCount);
                Assert.AreEqual(1, _cache.TokenCacheAccessor.RefreshTokenCount);
            }
        }
Example #4
0
        public async Task <AuthenticationResult> ExecuteAsync(
            AcquireTokenCommonParameters commonParameters,
            AcquireTokenWithDeviceCodeParameters deviceCodeParameters,
            CancellationToken cancellationToken)
        {
            var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId);

            var requestParams = _publicClientApplication.CreateRequestParameters(
                commonParameters,
                requestContext,
                _publicClientApplication.UserTokenCacheInternal);

            var handler = new DeviceCodeRequest(
                ServiceBundle,
                requestParams,
                deviceCodeParameters);

            return(await handler.RunAsync(cancellationToken).ConfigureAwait(false));
        }
Example #5
0
        /// <summary>
        /// Acquires a security token on a device without a Web browser, by letting the user authenticate on
        /// another device, with possiblity of passing extra query parameters and cancelling the token acquisition before it times out. This is done in two steps:
        /// <list type="bullet">
        /// <item><description>the method first acquires a device code from the authority and returns it to the caller via
        /// the <paramref name="deviceCodeResultCallback"/>. This callback takes care of interacting with the user
        /// to direct them to authenticate (to a specific URL, with a code)</description></item>
        /// <item><description>The method then proceeds to poll for the security
        /// token which is granted upon successful login by the user based on the device code information. This step is cancelable</description></item>
        /// </list>
        /// See https://aka.ms/msal-device-code-flow.
        /// </summary>
        /// <param name="scopes">Scopes requested to access a protected API</param>
        /// <param name="extraQueryParameters">This parameter will be appended as is to the query string in the HTTP authentication request to the authority.
        /// This is expected to be a string of segments of the form <c>key=value</c> separated by an ampersand character.
        /// The parameter can be null.</param>
        /// <param name="deviceCodeResultCallback">The callback containing information to show the user about how to authenticate and enter the device code.</param>
        /// <param name="cancellationToken">A CancellationToken which can be triggered to cancel the operation in progress.</param>
        /// <returns>Authentication result containing a token for the requested scopes and for the user who has authenticated on another device with the code</returns>
        public async Task <AuthenticationResult> AcquireTokenWithDeviceCodeAsync(
            IEnumerable <string> scopes,
            string extraQueryParameters,
            Func <DeviceCodeResult, Task> deviceCodeResultCallback,
            CancellationToken cancellationToken)
        {
            Authority authority = Instance.Authority.CreateAuthority(ServiceBundle, Authority, ValidateAuthority);

            var requestParams = CreateRequestParameters(authority, scopes, null, UserTokenCache);

            requestParams.ExtraQueryParameters = extraQueryParameters;

            var handler = new DeviceCodeRequest(
                ServiceBundle,
                requestParams,
                ApiEvent.ApiIds.None,
                deviceCodeResultCallback);

            return(await handler.RunAsync(cancellationToken).ConfigureAwait(false));
        }
Example #6
0
        [WorkItem(1407)] // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/1407
        public async Task DeviceCodeExceptionsOn200OKAsync()
        {
            using (var harness = CreateTestHarness())
            {
                TestCommon.MockInstanceDiscoveryAndOpenIdRequest(harness.HttpManager);
                var handler = new MockHttpMessageHandler()
                {
                    ExpectedMethod  = HttpMethod.Post,
                    ResponseMessage = MockHelpers.CreateInvalidClientResponseMessage()
                };

                harness.HttpManager.AddMockHandler(handler);

                var parameters = harness.CreateAuthenticationRequestParameters(
                    TestConstants.AuthorityHomeTenant,
                    TestConstants.s_scope,
                    new TokenCache(harness.ServiceBundle, false),
                    account: null);

                DeviceCodeResult actualDeviceCodeResult = null;

                var deviceCodeParameters = new AcquireTokenWithDeviceCodeParameters
                {
                    DeviceCodeResultCallback = result =>
                    {
                        actualDeviceCodeResult = result;
                        return(Task.FromResult(0));
                    }
                };

                var request = new DeviceCodeRequest(harness.ServiceBundle, parameters, deviceCodeParameters);

                var ex = await AssertException.TaskThrowsAsync <MsalServiceException>(
                    () => request.RunAsync(CancellationToken.None)).ConfigureAwait(false);
            }
        }
Example #7
0
        public void VerifyAuthorizationPendingErrorDoesNotLogError()
        {
            // When calling DeviceCodeFlow, we poll for the authorization and if the user hasn't entered the code in yet
            // then we receive an error for authorization_pending.  This is thrown as an exception and logged as
            // errors.  This error is noisy and so it should be suppressed for this one case.
            // This test verifies that the error for authorization_pending is not logged as an error.

            var logCallbacks = new List <_LogData>();

            using (var harness = CreateTestHarness(logCallback: (level, message, pii) =>
            {
                if (level == LogLevel.Error)
                {
                    Assert.Fail(
                        "Received an error message {0} and the stack trace is {1}",
                        message,
                        new StackTrace(true));
                }

                logCallbacks.Add(
                    new _LogData
                {
                    Level = level,
                    Message = message,
                    IsPii = pii
                });
            }))
            {
                const int NumberOfAuthorizationPendingRequestsToInject = 2;
                var       parameters = CreateAuthenticationParametersAndSetupMocks(
                    harness,
                    NumberOfAuthorizationPendingRequestsToInject,
                    out HashSet <string> expectedScopes);

                var deviceCodeParameters = new AcquireTokenWithDeviceCodeParameters
                {
                    DeviceCodeResultCallback = result => Task.FromResult(0)
                };

                var request = new DeviceCodeRequest(harness.ServiceBundle, parameters, deviceCodeParameters);

                Task <AuthenticationResult> task = request.RunAsync(CancellationToken.None);
                task.Wait();

                // Ensure we got logs so the log callback is working.
                Assert.IsTrue(logCallbacks.Count > 0, "There should be data in logCallbacks");

                // Ensure we have authorization_pending data in the logs
                List <_LogData> authPendingLogs =
                    logCallbacks.Where(x => x.Message.Contains(OAuth2Error.AuthorizationPending)).ToList();
                Assert.AreEqual(2, authPendingLogs.Count, "authorization_pending logs should exist");

                // Ensure the authorization_pending logs are Info level and not Error
                Assert.AreEqual(
                    2,
                    authPendingLogs.Where(x => x.Level == LogLevel.Info).ToList().Count,
                    "authorization_pending logs should be INFO");

                // Ensure we don't have Error level logs in this scenario.
                string errorLogs = string.Join(
                    "--",
                    logCallbacks
                    .Where(x => x.Level == LogLevel.Error)
                    .Select(x => x.Message)
                    .ToArray());

                Assert.IsFalse(
                    logCallbacks.Any(x => x.Level == LogLevel.Error),
                    "Error level logs should not exist but got: " + errorLogs);
            }
        }
Example #8
0
        public async Task TestDeviceCodeCancelAsync()
        {
            using (var harness = CreateTestHarness())
            {
                const int NumberOfAuthorizationPendingRequestsToInject = 0;
                var       parameters = CreateAuthenticationParametersAndSetupMocks(
                    harness,
                    NumberOfAuthorizationPendingRequestsToInject,
                    out HashSet <string> expectedScopes);

                var cancellationSource = new CancellationTokenSource();

                DeviceCodeResult actualDeviceCodeResult = null;
                var deviceCodeParameters = new AcquireTokenWithDeviceCodeParameters
                {
                    DeviceCodeResultCallback = async result =>
                    {
                        await Task.Delay(200, CancellationToken.None).ConfigureAwait(false);

                        actualDeviceCodeResult = result;
                    }
                };

                var request = new DeviceCodeRequest(harness.ServiceBundle, parameters, deviceCodeParameters);

                // We setup the cancel before calling the RunAsync operation since we don't check the cancel
                // until later and the mock network calls run insanely fast for us to timeout for them.
                cancellationSource.Cancel();
                await AssertException.TaskThrowsAsync <OperationCanceledException>(() => request.RunAsync(cancellationSource.Token)).ConfigureAwait(false);
            }
        }