Skip to content

Commit 6cf6a66

Browse files
authored
Extend support to read auth mode from environment variables for ado subcommands. (#424)
* Read auth mode from env for all sub commands * Update changelog * Use env extension class instead of creating a helper class * remove eventdata object from read env var method * Fix tests
1 parent a151ef0 commit 6cf6a66

7 files changed

Lines changed: 118 additions & 42 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88
### Added
9-
- Added support for reading auth mode from the environment variable `AZUREAUTH_MODE` for aad subcommands.
9+
- Added support for reading auth mode from the environment variable `AZUREAUTH_MODE`.
1010

1111
## [0.9.1] - 2024-12-09
1212
### Changed

src/AzureAuth.Test/IEnvExtensionsTest.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,26 @@ namespace AzureAuth.Test
66
using FluentAssertions;
77

88
using Microsoft.Authentication.AzureAuth;
9+
using Microsoft.Authentication.MSALWrapper;
10+
using Microsoft.Authentication.TestHelper;
11+
using Microsoft.Extensions.Logging;
912
using Microsoft.Office.Lasso.Interfaces;
10-
13+
using Microsoft.Office.Lasso.Telemetry;
1114
using Moq;
12-
15+
using NLog.Targets;
1316
using NUnit.Framework;
1417

1518
public class IEnvExtensionsTest
1619
{
1720
private Mock<IEnv> envMock;
21+
private ILogger logger;
22+
private MemoryTarget logTarget;
1823

1924
[SetUp]
2025
public void SetUp()
2126
{
2227
this.envMock = new Mock<IEnv>();
28+
(this.logger, this.logTarget) = MemoryLogger.Create();
2329
}
2430

2531
[TestCase("1", true)]
@@ -51,5 +57,44 @@ public void InteractiveAuth_IsEnabledIfEnvVarsAreNotSet()
5157
this.envMock.Setup(env => env.Get(It.IsAny<string>())).Returns((string)null);
5258
IEnvExtensions.InteractiveAuthDisabled(this.envMock.Object).Should().BeFalse();
5359
}
60+
61+
[Test]
62+
public void ReadAuthModeFromEnvOrSetDefault_ReturnsDefault_WhenEnvVarIsEmpty()
63+
{
64+
// Arrange
65+
envMock.Setup(e => e.Get(It.IsAny<string>())).Returns(string.Empty);
66+
67+
// Act
68+
var result = IEnvExtensions.ReadAuthModeFromEnvOrSetDefault(envMock.Object);
69+
70+
// Assert
71+
result.Should().BeEquivalentTo(new[] { AuthMode.Default });
72+
}
73+
74+
[Test]
75+
public void ReadAuthModeFromEnvOrSetDefault_ReturnsParsedAuthModes_WhenEnvVarIsValid()
76+
{
77+
// Arrange
78+
envMock.Setup(e => e.Get(It.IsAny<string>())).Returns("Web,DeviceCode");
79+
80+
// Act
81+
var result = IEnvExtensions.ReadAuthModeFromEnvOrSetDefault(envMock.Object);
82+
83+
// Assert
84+
result.Should().BeEquivalentTo(new[] { AuthMode.Web, AuthMode.DeviceCode });
85+
}
86+
87+
[Test]
88+
public void ReadAuthModeFromEnvOrSetDefault_ReturnsEmpty_WhenEnvVarIsInvalid()
89+
{
90+
// Arrange
91+
envMock.Setup(e => e.Get(It.IsAny<string>())).Returns("InvalidMode");
92+
93+
// Act
94+
var result = IEnvExtensions.ReadAuthModeFromEnvOrSetDefault(envMock.Object);
95+
96+
// Assert
97+
result.Should().BeEmpty();
98+
}
5499
}
55100
}

src/AzureAuth/Commands/Ado/CommandPat.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ namespace Microsoft.Authentication.AzureAuth.Commands.Ado
77
using System.Collections.Generic;
88
using System.Collections.Immutable;
99
using System.IO;
10+
using System.Linq;
1011
using McMaster.Extensions.CommandLineUtils;
1112

1213
using Microsoft.Authentication.AdoPat;
1314
using Microsoft.Authentication.AzureAuth.Ado;
1415
using Microsoft.Authentication.MSALWrapper;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.Identity.Client.Extensions.Msal;
18+
using Microsoft.Office.Lasso.Interfaces;
1719
using Microsoft.Office.Lasso.Telemetry;
1820
using Microsoft.VisualStudio.Services.DelegatedAuthorization;
1921
using Microsoft.VisualStudio.Services.OAuth;
@@ -92,7 +94,7 @@ private enum OutputMode
9294
private string Tenant { get; set; } = AzureAuth.Ado.Constants.Tenant.Microsoft;
9395

9496
[Option(CommandAad.ModeOption, CommandAad.AuthModeHelperText, CommandOptionType.MultipleValue)]
95-
private IEnumerable<AuthMode> AuthModes { get; set; } = new[] { AuthMode.Default };
97+
private IEnumerable<AuthMode> AuthModes { get; set; }
9698

9799
[Option(CommandAad.DomainOption, $"{CommandAad.DomainHelpText}\n[default: {AzureAuth.Ado.Constants.PreferredDomain}]", CommandOptionType.SingleValue)]
98100
private string Domain { get; set; } = AzureAuth.Ado.Constants.PreferredDomain;
@@ -120,14 +122,23 @@ private ImmutableSortedSet<string> Scopes
120122
/// <param name="logger">The <see cref="ILogger{T}"/> instance that is used for logging.</param>
121123
/// <param name="publicClientAuth">An <see cref="IPublicClientAuth"/>.</param>
122124
/// <param name="eventData">Lasso injected command event data.</param>
125+
/// <param name="env">An <see cref="IEnv"/> to use.</param>
123126
/// <returns>An integer status code. 0 for success and non-zero for failure.</returns>
124-
public int OnExecute(ILogger<CommandPat> logger, IPublicClientAuth publicClientAuth, CommandExecuteEventData eventData)
127+
public int OnExecute(ILogger<CommandPat> logger, IPublicClientAuth publicClientAuth, CommandExecuteEventData eventData, IEnv env)
125128
{
126129
if (!this.ValidOptions(logger))
127130
{
128131
return 1;
129132
}
130133

134+
// If command line options for mode are not specified, then use the environment variables.
135+
this.AuthModes ??= env.ReadAuthModeFromEnvOrSetDefault();
136+
if (!this.AuthModes.Any())
137+
{
138+
logger.LogError($"Invalid value specified for environment variable {EnvVars.AuthMode}. Allowed values are: {CommandAad.AuthModeHelperText}");
139+
return 1;
140+
}
141+
131142
var accessToken = this.AccessToken(publicClientAuth, eventData);
132143
if (accessToken == null)
133144
{

src/AzureAuth/Commands/Ado/CommandToken.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Microsoft.Authentication.AzureAuth.Commands.Ado
55
{
66
using System;
77
using System.Collections.Generic;
8-
8+
using System.Linq;
99
using McMaster.Extensions.CommandLineUtils;
1010

1111
using Microsoft.Authentication.AzureAuth.Ado;
@@ -52,7 +52,7 @@ public enum OutputMode
5252
private string Tenant { get; set; } = AzureAuth.Ado.Constants.Tenant.Microsoft;
5353

5454
[Option(CommandAad.ModeOption, CommandAad.AuthModeHelperText, CommandOptionType.MultipleValue)]
55-
private IEnumerable<AuthMode> AuthModes { get; set; } = new[] { AuthMode.Default };
55+
private IEnumerable<AuthMode> AuthModes { get; set; }
5656

5757
[Option(CommandAad.DomainOption, Description = DomainOptionDescription)]
5858
private string Domain { get; set; } = AzureAuth.Ado.Constants.PreferredDomain;
@@ -98,6 +98,14 @@ public int OnExecute(ILogger<CommandToken> logger, IEnv env, ITelemetryService t
9898
return 0;
9999
}
100100

101+
// If command line options for mode are not specified, then use the environment variables.
102+
this.AuthModes ??= env.ReadAuthModeFromEnvOrSetDefault();
103+
if (!this.AuthModes.Any())
104+
{
105+
logger.LogError($"Invalid value specified for environment variable {EnvVars.AuthMode}. Allowed values are: {CommandAad.AuthModeHelperText}");
106+
return 1;
107+
}
108+
101109
// If no PAT then use AAD AT.
102110
TokenResult token = publicClientAuth.Token(
103111
AzureAuth.Ado.AuthParameters.AdoParameters(this.Tenant),

src/AzureAuth/Commands/CommandAad.cs

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ public class CommandAad
8484
/// </summary>
8585
public static readonly TimeSpan GlobalTimeout = TimeSpan.FromMinutes(15);
8686

87+
/// <summary>
88+
/// The allowed values for the <see cref="AuthMode"/> option.
89+
/// </summary>
8790
#if PlatformWindows
88-
private const string AuthModeAllowedValues = "all, iwa, broker, web, devicecode";
91+
public const string AuthModeAllowedValues = "all, iwa, broker, web, devicecode";
8992
#else
90-
private const string AuthModeAllowedValues = "all, web, devicecode";
93+
public const string AuthModeAllowedValues = "all, web, devicecode";
9194
#endif
9295

9396
private const string ResourceOption = "--resource";
@@ -279,9 +282,11 @@ public bool EvaluateOptions()
279282
}
280283
}
281284

282-
if (this.AuthModes is null && !this.TrySetAuthModeFromEnvOrDefault())
285+
// If command line options for mode are not specified, then use the environment variables.
286+
this.AuthModes ??= env.ReadAuthModeFromEnvOrSetDefault();
287+
if (!this.AuthModes.Any())
283288
{
284-
this.logger.LogError($"Invalid value specified for environment variable {EnvVars.AuthMode}. Allowed values are: {AuthModeAllowedValues}");
289+
this.logger.LogError($"Invalid value specified for environment variable {EnvVars.AuthMode}. Allowed values are: {CommandAad.AuthModeHelperText}");
285290
return false;
286291
}
287292

@@ -418,36 +423,5 @@ private int GetToken(IPublicClientAuth publicClientAuth)
418423

419424
return 0;
420425
}
421-
422-
/// <summary>
423-
/// Sets the <see cref="AuthMode"/> from the environment variable and sets a default if not set.
424-
/// </summary>
425-
/// <returns>True if authmode is set.</returns>
426-
public bool TrySetAuthModeFromEnvOrDefault()
427-
{
428-
var authModesFromEnv = this.env.Get(EnvVars.AuthMode);
429-
if (string.IsNullOrEmpty(authModesFromEnv))
430-
{
431-
this.AuthModes = new[] { AuthMode.Default };
432-
return true;
433-
}
434-
435-
var result = new List<AuthMode>();
436-
foreach(var val in authModesFromEnv.Split(','))
437-
{
438-
if (Enum.TryParse<AuthMode>(val, ignoreCase: true, out var mode))
439-
{
440-
result.Add(mode);
441-
}
442-
else
443-
{
444-
return false;
445-
}
446-
}
447-
448-
this.AuthModes = result;
449-
this.eventData.Add($"env_{EnvVars.AuthMode}", authModesFromEnv);
450-
return true;
451-
}
452426
}
453427
}

src/AzureAuth/IEnvExtensions.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
namespace Microsoft.Authentication.AzureAuth
55
{
6+
using Microsoft.Authentication.MSALWrapper;
67
using Microsoft.Office.Lasso.Interfaces;
8+
using Microsoft.Office.Lasso.Telemetry;
9+
using System.Collections.Generic;
10+
using System;
711

812
/// <summary>
913
/// Extension methods to Lasso's <see cref="IEnv"/> interface.
@@ -22,5 +26,38 @@ public static bool InteractiveAuthDisabled(this IEnv env)
2226
return !string.IsNullOrEmpty(env.Get(EnvVars.NoUser)) ||
2327
string.Equals(CorextPositiveValue, env.Get(EnvVars.CorextNonInteractive));
2428
}
29+
30+
/// <summary>
31+
/// Get the auth modes from the environment or set the default.
32+
/// </summary>
33+
/// <param name="env">The <see cref="IEnv"/> to use.</param>
34+
/// <param name="eventData">Event data to add the auth mode to.</param>
35+
/// <returns>AuthModes.</returns>
36+
public static IEnumerable<AuthMode> ReadAuthModeFromEnvOrSetDefault(this IEnv env)
37+
{
38+
var authModesFromEnv = env.Get(EnvVars.AuthMode);
39+
40+
// If auth modes are not specified in the environment, then return the default.
41+
if (string.IsNullOrEmpty(authModesFromEnv))
42+
{
43+
return new[] { AuthMode.Default };
44+
}
45+
46+
var result = new List<AuthMode>();
47+
foreach (var val in authModesFromEnv.Split(','))
48+
{
49+
if (Enum.TryParse<AuthMode>(val, ignoreCase: true, out var mode))
50+
{
51+
result.Add(mode);
52+
}
53+
else
54+
{
55+
// If the environment variable is not a valid auth mode, then return an empty list.
56+
return new List<AuthMode>();
57+
}
58+
}
59+
60+
return result;
61+
}
2562
}
2663
}

src/AzureAuth/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ private static void Main(string[] args)
6868
EnvVars.CloudBuild,
6969
EnvVars.NoUser,
7070
EnvVars.CorextNonInteractive,
71+
EnvVars.AuthMode,
7172
};
7273

7374
TelemetryConfig telemetryConfig = new TelemetryConfig(

0 commit comments

Comments
 (0)