From bb36b69a84ae13b64f2675c331b3b7b0bab0d7e1 Mon Sep 17 00:00:00 2001 From: Scott Schaab Date: Wed, 23 Sep 2020 16:42:24 -0700 Subject: [PATCH] [Identity] Added default console output for DeviceCodeCredential (#15266) * [Identity] Added default console output for DeviceCodeCredential * mark older ctor overloads non-browsable * updating changelog --- sdk/identity/Azure.Identity/CHANGELOG.md | 3 ++ .../api/Azure.Identity.netstandard2.0.cs | 5 +- .../src/DeviceCodeCredential.cs | 49 +++++++++++++------ .../tests/DeviceCodeCredentialTests.cs | 37 ++++++++++++++ 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 2d601ec341341..f84cdbfe2454c 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -1,6 +1,9 @@ # Release History ## 1.3.0-beta.2 (Unreleased) +### New Features +- Update `DeviceCodeCredential` to output device code information and authentication instructions in the console, in the case no `deviceCodeCallback` is specified. + ## 1.3.0-beta.1 (2020-09-11) diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index f78ff1e66288b..c7694438bbb8e 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -116,9 +116,12 @@ public DefaultAzureCredentialOptions() { } } public partial class DeviceCodeCredential : Azure.Core.TokenCredential { - protected DeviceCodeCredential() { } + public DeviceCodeCredential() { } + public DeviceCodeCredential(Azure.Identity.DeviceCodeCredentialOptions options) { } public DeviceCodeCredential(System.Func deviceCodeCallback, Azure.Identity.DeviceCodeCredentialOptions options = null) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public DeviceCodeCredential(System.Func deviceCodeCallback, string clientId, Azure.Identity.TokenCredentialOptions options = null) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public DeviceCodeCredential(System.Func deviceCodeCallback, string tenantId, string clientId, Azure.Identity.TokenCredentialOptions options = null) { } public virtual Azure.Identity.AuthenticationRecord Authenticate(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Identity.AuthenticationRecord Authenticate(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs index 3fe9ef5adebbd..6186e7fec9484 100644 --- a/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs +++ b/sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs @@ -5,6 +5,7 @@ using Azure.Core.Pipeline; using Microsoft.Identity.Client; using System; +using System.ComponentModel; using System.Threading; using System.Threading.Tasks; @@ -26,19 +27,43 @@ public class DeviceCodeCredential : TokenCredential private const string NoDefaultScopeMessage = "Authenticating in this environment requires specifying a TokenRequestContext."; /// - /// Protected constructor for mocking + /// Creates a new , which will authenticate users using the device code flow, printing the device code message to stdout. /// - protected DeviceCodeCredential() + public DeviceCodeCredential() : + this(DefaultDeviceCodeHandler, null, null, null, null) { } + /// + /// Creates a new with the specified options, which will authenticate users using the device code flow, printing the device code message to stdout. + /// + /// The client options for the newly created . + public DeviceCodeCredential(DeviceCodeCredentialOptions options) + : this(DefaultDeviceCodeHandler, options?.TenantId, options?.ClientId, options, null) + { + + } + + /// + /// Creates a new DeviceCodeCredential with the specified options, which will authenticate users using the device code flow. + /// + /// The callback to be executed to display the device code to the user. + /// The client options for the newly created . + public DeviceCodeCredential(Func deviceCodeCallback, DeviceCodeCredentialOptions options = default) + : this(deviceCodeCallback, options?.TenantId, options?.ClientId, options, null) + { + _disableAutomaticAuthentication = options?.DisableAutomaticAuthentication ?? false; + _record = options?.AuthenticationRecord; + } + /// /// Creates a new DeviceCodeCredential with the specified options, which will authenticate users with the specified application. /// /// The callback to be executed to display the device code to the user /// The client id of the application to which the users will authenticate /// The client options for the newly created DeviceCodeCredential + [EditorBrowsable(EditorBrowsableState.Never)] public DeviceCodeCredential(Func deviceCodeCallback, string clientId, TokenCredentialOptions options = default) : this(deviceCodeCallback, null, clientId, options, null) { @@ -52,23 +77,12 @@ public DeviceCodeCredential(Func device /// The tenant id of the application to which users will authenticate. This can be null for multi-tenanted applications. /// The client id of the application to which the users will authenticate /// The client options for the newly created DeviceCodeCredential + [EditorBrowsable(EditorBrowsableState.Never)] public DeviceCodeCredential(Func deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options = default) : this(deviceCodeCallback, tenantId, clientId, options, null) { } - /// - /// Creates a new DeviceCodeCredential with the specified options, which will authenticate users using the device code flow. - /// - /// The callback to be executed to display the device code to the user. - /// The client options for the newly created . - public DeviceCodeCredential(Func deviceCodeCallback, DeviceCodeCredentialOptions options = default) - : this(deviceCodeCallback, options?.TenantId, options?.ClientId, options, null) - { - _disableAutomaticAuthentication = options?.DisableAutomaticAuthentication ?? false; - _record = options?.AuthenticationRecord; - } - internal DeviceCodeCredential(Func deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline) : this(deviceCodeCallback, tenantId, clientId, options, pipeline, null) { @@ -76,7 +90,7 @@ internal DeviceCodeCredential(Func devi internal DeviceCodeCredential(Func deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client) { - _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + _clientId = clientId ?? Constants.DeveloperSignOnClientId; _deviceCodeCallback = deviceCodeCallback ?? throw new ArgumentNullException(nameof(deviceCodeCallback)); @@ -222,6 +236,11 @@ private Task DeviceCodeCallback(DeviceCodeResult deviceCode, CancellationToken c return _deviceCodeCallback(new DeviceCodeInfo(deviceCode), cancellationToken); } + private static Task DefaultDeviceCodeHandler(DeviceCodeInfo deviceCodeInfo, CancellationToken cancellationToken) + { + Console.WriteLine(deviceCodeInfo.Message); + return Task.CompletedTask; + } } } diff --git a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs index 094e1003d3b2d..3d180fdb81188 100644 --- a/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -95,6 +96,42 @@ public async Task AuthenticateWithDeviceCodeMockAsync2() Assert.AreEqual(token.Token, expectedToken); } + [Test] + [NonParallelizable] + public async Task AuthenticateWithDeviceCodeNoCallback() + { + var capturedOut = new StringBuilder(); + + var capturedOutWriter = new StringWriter(capturedOut); + + var stdOut = Console.Out; + + Console.SetOut(capturedOutWriter); + + try + { + var expectedCode = Guid.NewGuid().ToString(); + + var expectedToken = Guid.NewGuid().ToString(); + + var mockTransport = new MockTransport(request => ProcessMockRequest(request, expectedCode, expectedToken)); + + var options = new DeviceCodeCredentialOptions() { Transport = mockTransport }; + + var cred = InstrumentClient(new DeviceCodeCredential(options)); + + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); + + Assert.AreEqual(token.Token, expectedToken); + + Assert.AreEqual(expectedCode + Environment.NewLine, capturedOut.ToString()); + } + finally + { + Console.SetOut(stdOut); + } + } + [Test] public async Task AuthenticateWithDeviceCodeMockVerifyMsalCancellationAsync() {