Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Account]Redirect DeviceCode Info from warning stream to information stream in Connect-AzAccount #23665

Merged
merged 9 commits into from
Jan 30, 2024
35 changes: 35 additions & 0 deletions src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using Microsoft.Azure.PowerShell.Common.Share.Survey;
using Microsoft.Azure.Commands.Profile.Utilities;
using System.Management.Automation.Runspaces;

namespace Microsoft.Azure.Commands.Profile
{
Expand Down Expand Up @@ -249,6 +250,7 @@ protected override IAzureContext DefaultContext
protected override void BeginProcessing()
{
base.BeginProcessing();
ValidateActionRequiredMessageCanBePresented();
if (AzureEnvironment.PublicEnvironments.ContainsKey(EnvironmentName.AzureCloud))
{
_environment = AzureEnvironment.PublicEnvironments[EnvironmentName.AzureCloud];
Expand All @@ -273,11 +275,19 @@ protected override void BeginProcessing()

_writeWarningEvent -= WriteWarningSender;
_writeWarningEvent += WriteWarningSender;
_writeInformationEvent -= WriteInformationSender;
_writeInformationEvent += WriteInformationSender;

// store the original write warning handler, register a thread safe one
AzureSession.Instance.TryGetComponent(WriteWarningKey, out _originalWriteWarning);
AzureSession.Instance.UnregisterComponent<EventHandler<StreamEventArgs>>(WriteWarningKey);
AzureSession.Instance.RegisterComponent(WriteWarningKey, () => _writeWarningEvent);

// store the original write information handler, register a thread safe one
AzureSession.Instance.TryGetComponent(WriteInformationKey, out _originalWriteInformation);
AzureSession.Instance.UnregisterComponent<EventHandler<StreamEventArgs>>(WriteInformationKey);
AzureSession.Instance.RegisterComponent(WriteInformationKey, () => _writeInformationEvent);

// todo: ideally cancellation token should be passed to authentication factory as a parameter
// however AuthenticationFactory.Authenticate does not support it
// so I store it in AzureSession.Instance as a global variable
Expand All @@ -289,11 +299,19 @@ protected override void BeginProcessing()
private event EventHandler<StreamEventArgs> _writeWarningEvent;
private event EventHandler<StreamEventArgs> _originalWriteWarning;

private event EventHandler<StreamEventArgs> _writeInformationEvent;
private event EventHandler<StreamEventArgs> _originalWriteInformation;

private void WriteWarningSender(object sender, StreamEventArgs args)
{
_tasks.Enqueue(new Task(() => this.WriteWarning(args.Message)));
}

private void WriteInformationSender(object sender, StreamEventArgs args)
{
_tasks.Enqueue(new Task(() => this.WriteInformation(args.Message)));
}

protected override void StopProcessing()
{
if (AzureSession.Instance.TryGetComponent("LoginCancellationToken", out CancellationTokenSource cancellationTokenSource))
Expand Down Expand Up @@ -562,6 +580,20 @@ public override void ExecuteCmdlet()
}
}

private void ValidateActionRequiredMessageCanBePresented()
{
if (UseDeviceAuthentication.IsPresent && IsWriteInformationIgnored())
{
throw new ActionPreferenceStopException(Resources.DoNotIgnoreInformationIfUserDeviceAuth);
}
}

private bool IsWriteInformationIgnored()
Copy link
Contributor

@msJinLei msJinLei Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private bool IsWriteInformationIgnored()
private static bool IsWriteInformationIgnored()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can not be static as it's bounded with cmdlet's MyInvocation.BoundParameters and SessionState

{
return !MyInvocation.BoundParameters.ContainsKey("InformationAction") && ActionPreference.Ignore.ToString().Equals(SessionState?.PSVariable?.GetValue("InformationPreference", ActionPreference.SilentlyContinue)?.ToString() ?? "") ||
MyInvocation.BoundParameters.TryGetValue("InformationAction", out var value) && ActionPreference.Ignore.ToString().Equals(value?.ToString() ?? "", StringComparison.InvariantCultureIgnoreCase);
}

private string PreProcessAuthScope()
{
string mappedScope = AuthScope;
Expand Down Expand Up @@ -774,6 +806,9 @@ protected override void EndProcessing()
// unregister the thread-safe write warning, because it won't work out of this cmdlet
AzureSession.Instance.UnregisterComponent<EventHandler<StreamEventArgs>>(WriteWarningKey);
AzureSession.Instance.RegisterComponent(WriteWarningKey, () => _originalWriteWarning);
// unregister the thread-safe write information, because it won't work out of this cmdlet
AzureSession.Instance.UnregisterComponent<EventHandler<StreamEventArgs>>(WriteInformationKey);
AzureSession.Instance.RegisterComponent(WriteInformationKey, () => _originalWriteInformation);
}
}
}
5 changes: 4 additions & 1 deletion src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
-->

## Upcoming Release
* Redirected device code login messages from warning stream to information stream if use device authentication in `Connect-AzAccount`.
BethanyZhou marked this conversation as resolved.
Show resolved Hide resolved
* Adjusted output format to be more user-friendly for `Get-AzContext/Tenant/Subscription` and `Invoke-AzRestMethod`, including
- ordering and grouping output items to make items easy to find.
- re-prioritizing positions for output properties to highlight valuable properties.
* Upgraded the reference of Azure PowerShell Common to 1.3.90-preview.
* Upgraded Azure.Identity to 1.10.3 [#23018].
- Renamed token cache from `msal.cache` to `msal.cache.cae` or `masl.cache.nocae`.
* Enabled Continue Access Evaluation (CAE) for all Service Principals login methods.
* Supported signing in with Microsoft Account (MSA) via Web Account Manager (WAM). Enable it by `Set-AzConfig -EnableLoginByWam $true`.
* Adjusted output format to be more user-friendly for `Get-AzContext/Tenant/Subscription` and `Invoke-AzRestMethod`.
* Fixed the multiple `x-ms-unique-id` values issue.

## Version 2.15.0
Expand Down
9 changes: 9 additions & 0 deletions src/Accounts/Accounts/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Accounts/Accounts/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,7 @@
<data name="ProfileCredentialsWriteWarning" xml:space="preserve">
<value>Personally identifiable information and confidential data may be written to the file located at '{0}'. Please ensure that appropriate access controls are assigned to the saved file.</value>
</data>
<data name="DoNotIgnoreInformationIfUserDeviceAuth" xml:space="preserve">
<value>Please do not set InformationAction or $InformationPreference to Ignore if you want to use device code authentication.</value>
</data>
</root>
37 changes: 28 additions & 9 deletions src/Accounts/Authenticators/DeviceCodeAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Threading;
using System.Threading.Tasks;

using Azure.Core;
using Azure.Identity;

Expand All @@ -24,6 +20,12 @@
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.ResourceManager.Common;
using Microsoft.WindowsAzure.Commands.Common;

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.PowerShell.Authenticators
{
Expand Down Expand Up @@ -64,7 +66,7 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet

private Task DeviceCodeFunc(DeviceCodeInfo info, CancellationToken cancellation)
{
WriteWarning(info.Message);
WriteInfomartion(info.Message, info.UserCode);
return Task.CompletedTask;
}

Expand All @@ -73,12 +75,29 @@ public override bool CanAuthenticate(AuthenticationParameters parameters)
return (parameters as DeviceCodeParameters) != null;
}

private void WriteWarning(string message)

private void WriteInfomartion(string message, string userCode)
{
EventHandler<StreamEventArgs> writeWarningEvent;
if (AzureSession.Instance.TryGetComponent(AzureRMCmdlet.WriteWarningKey, out writeWarningEvent))

var loginInfo = new StringBuilder();
string LoginToAzurePhrase = $"{PSStyle.Bold}{PSStyle.BackgroundColor.Blue}[Login to Azure]{PSStyle.Reset} ";
loginInfo.Append(LoginToAzurePhrase);

if (!string.IsNullOrEmpty(userCode))
{
var formattedUserCode = $"{PSStyle.Underline}{userCode}{PSStyle.Reset}";
var formattedMessage = message.Replace(userCode, formattedUserCode);
loginInfo.Append(formattedMessage);
}
else
{
loginInfo.Append(message);
}

EventHandler<StreamEventArgs> writeInforamtionEvent;
if (AzureSession.Instance.TryGetComponent(AzureRMCmdlet.WriteInformationKey, out writeInforamtionEvent))
{
writeWarningEvent(this, new StreamEventArgs() { Message = message });
writeInforamtionEvent(this, new StreamEventArgs() { Message = loginInfo.ToString() });
}
}
}
Expand Down
Loading