Skip to content

Commit

Permalink
[Identity] Added default console output for DeviceCodeCredential (#15266
Browse files Browse the repository at this point in the history
)

* [Identity] Added default console output for DeviceCodeCredential

* mark older ctor overloads non-browsable

* updating changelog
  • Loading branch information
schaabs authored Sep 23, 2020
1 parent 3329cad commit bb36b69
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 16 deletions.
3 changes: 3 additions & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> deviceCodeCallback, Azure.Identity.DeviceCodeCredentialOptions options = null) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public DeviceCodeCredential(System.Func<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> deviceCodeCallback, string clientId, Azure.Identity.TokenCredentialOptions options = null) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public DeviceCodeCredential(System.Func<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> 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; }
Expand Down
49 changes: 34 additions & 15 deletions sdk/identity/Azure.Identity/src/DeviceCodeCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Azure.Core.Pipeline;
using Microsoft.Identity.Client;
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -26,19 +27,43 @@ public class DeviceCodeCredential : TokenCredential
private const string NoDefaultScopeMessage = "Authenticating in this environment requires specifying a TokenRequestContext.";

/// <summary>
/// Protected constructor for mocking
/// Creates a new <see cref="DeviceCodeCredential"/>, which will authenticate users using the device code flow, printing the device code message to stdout.
/// </summary>
protected DeviceCodeCredential()
public DeviceCodeCredential() :
this(DefaultDeviceCodeHandler, null, null, null, null)
{

}

/// <summary>
/// Creates a new <see cref="DeviceCodeCredential"/> with the specified options, which will authenticate users using the device code flow, printing the device code message to stdout.
/// </summary>
/// <param name="options">The client options for the newly created <see cref="DeviceCodeCredential"/>.</param>
public DeviceCodeCredential(DeviceCodeCredentialOptions options)
: this(DefaultDeviceCodeHandler, options?.TenantId, options?.ClientId, options, null)
{

}

/// <summary>
/// Creates a new DeviceCodeCredential with the specified options, which will authenticate users using the device code flow.
/// </summary>
/// <param name="deviceCodeCallback">The callback to be executed to display the device code to the user.</param>
/// <param name="options">The client options for the newly created <see cref="DeviceCodeCredential"/>.</param>
public DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback, DeviceCodeCredentialOptions options = default)
: this(deviceCodeCallback, options?.TenantId, options?.ClientId, options, null)
{
_disableAutomaticAuthentication = options?.DisableAutomaticAuthentication ?? false;
_record = options?.AuthenticationRecord;
}

/// <summary>
/// Creates a new DeviceCodeCredential with the specified options, which will authenticate users with the specified application.
/// </summary>
/// <param name="deviceCodeCallback">The callback to be executed to display the device code to the user</param>
/// <param name="clientId">The client id of the application to which the users will authenticate</param>
/// <param name="options">The client options for the newly created DeviceCodeCredential</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback, string clientId, TokenCredentialOptions options = default)
: this(deviceCodeCallback, null, clientId, options, null)
{
Expand All @@ -52,31 +77,20 @@ public DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> device
/// <param name="tenantId">The tenant id of the application to which users will authenticate. This can be null for multi-tenanted applications.</param>
/// <param name="clientId">The client id of the application to which the users will authenticate</param>
/// <param name="options">The client options for the newly created DeviceCodeCredential</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options = default)
: this(deviceCodeCallback, tenantId, clientId, options, null)
{
}

/// <summary>
/// Creates a new DeviceCodeCredential with the specified options, which will authenticate users using the device code flow.
/// </summary>
/// <param name="deviceCodeCallback">The callback to be executed to display the device code to the user.</param>
/// <param name="options">The client options for the newly created <see cref="DeviceCodeCredential"/>.</param>
public DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback, DeviceCodeCredentialOptions options = default)
: this(deviceCodeCallback, options?.TenantId, options?.ClientId, options, null)
{
_disableAutomaticAuthentication = options?.DisableAutomaticAuthentication ?? false;
_record = options?.AuthenticationRecord;
}

internal DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback, string tenantId, string clientId, TokenCredentialOptions options, CredentialPipeline pipeline)
: this(deviceCodeCallback, tenantId, clientId, options, pipeline, null)
{
}

internal DeviceCodeCredential(Func<DeviceCodeInfo, CancellationToken, Task> 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));

Expand Down Expand Up @@ -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;
}
}
}
37 changes: 37 additions & 0 deletions sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
{
Expand Down

0 comments on commit bb36b69

Please sign in to comment.