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