Skip to content

Commit be89fc1

Browse files
authored
Az.accounts 2.1.2 (#13397)
* fix issue: the error message is unclear if browser is unavailable for Interactive scenario * fix issues: a. Token is not renewed after expiring for LRO; b. AccountId is not respected in MSI * code refactoring * remove unused code * fix typo * update version * update for 2.1.2 * update version to 2.1.2
1 parent 90dc114 commit be89fc1

File tree

123 files changed

+317
-234
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+317
-234
lines changed

src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
using System.Threading;
2020
using System.Threading.Tasks;
2121

22+
using Azure.Identity;
23+
2224
using Microsoft.Azure.Commands.Common.Authentication;
2325
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
2426
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core;
@@ -29,6 +31,7 @@
2931
using Microsoft.Azure.Commands.Profile.Properties;
3032
using Microsoft.Azure.Commands.ResourceManager.Common;
3133
using Microsoft.Azure.PowerShell.Authenticators;
34+
using Microsoft.Identity.Client;
3235
using Microsoft.WindowsAzure.Commands.Common;
3336
using Microsoft.WindowsAzure.Commands.Utilities.Common;
3437

@@ -398,12 +401,36 @@ public override void ExecuteCmdlet()
398401
}
399402

400403
HandleActions();
401-
var result = (PSAzureProfile) (task.ConfigureAwait(false).GetAwaiter().GetResult());
402-
WriteObject(result);
404+
405+
try
406+
{
407+
var result = (PSAzureProfile)(task.ConfigureAwait(false).GetAwaiter().GetResult());
408+
WriteObject(result);
409+
}
410+
catch (AuthenticationFailedException ex)
411+
{
412+
if(IsUnableToOpenWebPageError(ex))
413+
{
414+
WriteWarning(Resources.InteractiveAuthNotSupported);
415+
WriteDebug(ex.ToString());
416+
}
417+
else
418+
{
419+
WriteWarning(Resources.SuggestToUseDeviceCodeAuth);
420+
WriteDebug(ex.ToString());
421+
throw;
422+
}
423+
}
403424
});
404425
}
405426
}
406427

428+
private bool IsUnableToOpenWebPageError(AuthenticationFailedException exception)
429+
{
430+
return exception.InnerException is MsalClientException && ((MsalClientException)exception.InnerException)?.ErrorCode == MsalError.LinuxXdgOpen
431+
|| (exception.Message?.ToLower()?.Contains("unable to open a web page") ?? false);
432+
}
433+
407434
private ConcurrentQueue<Task> _tasks = new ConcurrentQueue<Task>();
408435

409436
private void HandleActions()

src/Accounts/Accounts/Az.Accounts.psd1

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# Generated by: Microsoft Corporation
55
#
6-
# Generated on: 10/23/2020
6+
# Generated on: 11/2/2020
77
#
88

99
@{
@@ -12,7 +12,7 @@
1212
# RootModule = ''
1313

1414
# Version number of this module.
15-
ModuleVersion = '2.1.0'
15+
ModuleVersion = '2.1.2'
1616

1717
# Supported PSEditions
1818
CompatiblePSEditions = 'Core', 'Desktop'
@@ -143,8 +143,7 @@ PrivateData = @{
143143
# IconUri = ''
144144

145145
# ReleaseNotes of this module
146-
ReleaseNotes = '* [Breaking Change] Removed ''Get-AzProfile'' and ''Select-AzProfile''
147-
* Replaced Azure Directory Authentication Library with Microsoft Authentication Library(MSAL)'
146+
ReleaseNotes = '* Fixed one issue related to MSI'
148147

149148
# Prerelease string of this module
150149
# Prerelease = ''

src/Accounts/Accounts/ChangeLog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
* Fixed an issue causing `Connect-AzAccount -KeyVaultAccessToken` not working [#13127]
2222
* Fixed null reference and method case insensitive in `Invoke-AzRestMethod`
2323

24+
## Version 2.1.2
25+
* Fixed one issue related to MSI
26+
27+
## Version 2.1.1
28+
* Fixed the issue that token is not renewed after expiring for LRO [#13367]
29+
* Fixed the issue that AccountId is not respected in MSI [#13376]
30+
* Fixed the issue that error message is unclear if browser is not avaialable for Interactive auth [#13340]
31+
2432
## Version 2.1.0
2533
* [Breaking Change] Removed `Get-AzProfile` and `Select-AzProfile`
2634
* Replaced Azure Directory Authentication Library with Microsoft Authentication Library(MSAL)

src/Accounts/Accounts/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
// You can specify all the values or you can default the Build and Revision Numbers
4444
// by using the '*' as shown below:
4545

46-
[assembly: AssemblyVersion("2.1.0")]
47-
[assembly: AssemblyFileVersion("2.1.0")]
46+
[assembly: AssemblyVersion("2.1.2")]
47+
[assembly: AssemblyFileVersion("2.1.2")]
4848
#if !SIGN
4949
[assembly: InternalsVisibleTo("Microsoft.Azure.PowerShell.Cmdlets.Accounts.Test")]
5050
#endif

src/Accounts/Accounts/Properties/Resources.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Accounts/Accounts/Properties/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,10 @@
513513
<data name="SendFeedbackOpenLinkManually" xml:space="preserve">
514514
<value>Use a web browser to open the page {0}.</value>
515515
</data>
516+
<data name="InteractiveAuthNotSupported" xml:space="preserve">
517+
<value>Interactive authentication is not supported in this session, please run Connect-AzAccount using switch -DeviceCode.</value>
518+
</data>
519+
<data name="SuggestToUseDeviceCodeAuth" xml:space="preserve">
520+
<value>Please run 'Connect-AzAccount -DeviceCode' if browser is not supported in this session.</value>
521+
</data>
516522
</root>

src/Accounts/Accounts/help/Connect-AzAccount.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,7 @@ Accept wildcard characters: False
610610

611611
### -UseDeviceAuthentication
612612

613-
Use device code authentication instead of a browser control. This is the default authentication type
614-
for PowerShell version 6 and higher.
613+
Use device code authentication instead of a browser control.
615614

616615
```yaml
617616
Type: System.Management.Automation.SwitchParameter

src/Accounts/Authentication/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@
4343
// You can specify all the values or you can default the Build and Revision Numbers
4444
// by using the '*' as shown below:
4545
// [assembly: AssemblyVersion("1.0.*")]
46-
[assembly: AssemblyVersion("2.1.0")]
47-
[assembly: AssemblyFileVersion("2.1.0")]
46+
[assembly: AssemblyVersion("2.1.2")]
47+
[assembly: AssemblyFileVersion("2.1.2")]

src/Accounts/Authenticators/DeviceCodeAuthenticator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet
5757
var authTask = codeCredential.AuthenticateAsync(requestContext, source.Token);
5858
return MsalAccessToken.GetAccessTokenAsync(
5959
authTask,
60-
() => codeCredential.GetTokenAsync(requestContext, source.Token),
60+
codeCredential,
61+
requestContext,
6162
source.Token);
6263
}
6364

src/Accounts/Authenticators/InteractiveUserAuthenticator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet
7171

7272
return MsalAccessToken.GetAccessTokenAsync(
7373
authTask,
74-
() => browserCredential.GetTokenAsync(requestContext, source.Token),
74+
browserCredential,
75+
requestContext,
7576
source.Token);
7677
}
7778

src/Accounts/Authenticators/ManagedServiceIdentityAuthenticator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using System.Text.RegularExpressions;
1516
using System.Threading;
1617
using System.Threading.Tasks;
1718

@@ -30,16 +31,18 @@ public class ManagedServiceIdentityAuthenticator : DelegatingAuthenticator
3031
DefaultMSILoginUri = "http://169.254.169.254/metadata/identity/oauth2/token",
3132
DefaultBackupMSILoginUri = "http://localhost:50342/oauth2/token";
3233

34+
private static Regex SystemMsiNameRegex = new Regex(@"MSI@\d+");
3335

3436
public override Task<IAccessToken> Authenticate(AuthenticationParameters parameters, CancellationToken cancellationToken)
3537
{
3638
var msiParameters = parameters as ManagedServiceIdentityParameters;
3739

3840
var scopes = new[] { GetResourceId(msiParameters.ResourceId, msiParameters.Environment) };
3941
var requestContext = new TokenRequestContext(scopes);
40-
ManagedIdentityCredential identityCredential = new ManagedIdentityCredential();
41-
var tokenTask = identityCredential.GetTokenAsync(requestContext);
42-
return MsalAccessToken.GetAccessTokenAsync(tokenTask, msiParameters.TenantId, msiParameters.Account.Id);
42+
var userAccountId = SystemMsiNameRegex.IsMatch(msiParameters.Account.Id) ? null : msiParameters.Account.Id;
43+
ManagedIdentityCredential identityCredential = new ManagedIdentityCredential(userAccountId);
44+
return MsalAccessToken.GetAccessTokenAsync(identityCredential, requestContext, cancellationToken,
45+
msiParameters.TenantId, msiParameters.Account.Id);
4346
}
4447

4548
public override bool CanAuthenticate(AuthenticationParameters parameters)

src/Accounts/Authenticators/MsalAccessToken.cs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace Microsoft.Azure.PowerShell.Authenticators
2727
{
2828
public class MsalAccessToken : IAccessToken
2929
{
30-
public string AccessToken { get; }
30+
public string AccessToken { get; private set; }
3131

3232
public string UserId { get; }
3333

@@ -39,50 +39,79 @@ public class MsalAccessToken : IAccessToken
3939

4040
public IDictionary<string, string> ExtendedProperties { get; } = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
4141

42-
public MsalAccessToken(string token, string tenantId, string userId = null, string homeAccountId = null)
42+
private DateTimeOffset ExpiredOn { get; set; }
43+
44+
private readonly static TimeSpan ExpirationThreshold = TimeSpan.FromMinutes(5);
45+
46+
private TokenCredential TokenCredential { get; set; }
47+
48+
private TokenRequestContext TokenRequestContext { get; set; }
49+
50+
public MsalAccessToken(TokenCredential tokenCredential, TokenRequestContext tokenRequestContext,
51+
string token, DateTimeOffset expiresOn, string tenantId, string userId = null, string homeAccountId = null)
4352
{
53+
TokenCredential = tokenCredential;
54+
TokenRequestContext = tokenRequestContext;
4455
AccessToken = token;
56+
ExpiredOn = expiresOn;
4557
UserId = userId;
4658
TenantId = tenantId;
4759
HomeAccountId = homeAccountId;
4860
}
4961

5062
public void AuthorizeRequest(Action<string, string> authTokenSetter)
5163
{
64+
Renew();
5265
authTokenSetter("Bearer", AccessToken);
5366
}
5467

5568
public static async Task<IAccessToken> GetAccessTokenAsync(
56-
ValueTask<AccessToken> result,
69+
TokenCredential tokenCredential,
70+
TokenRequestContext requestContext,
71+
CancellationToken cancellationToken,
5772
string tenantId = null,
5873
string userId = null,
5974
string homeAccountId = "")
6075
{
61-
var token = await result;
62-
return new MsalAccessToken(token.Token, tenantId, userId, homeAccountId);
76+
var token = await tokenCredential.GetTokenAsync(requestContext, cancellationToken);
77+
return new MsalAccessToken(tokenCredential, requestContext, token.Token, token.ExpiresOn, tenantId, userId, homeAccountId);
6378
}
6479

65-
public static async Task<IAccessToken> GetAccessTokenAsync(
66-
ValueTask<AccessToken> result,
67-
Action action,
68-
string tenantId = null,
69-
string userId = null)
70-
{
71-
var token = await result;
72-
action();
73-
return new MsalAccessToken(token.Token, tenantId, userId);
74-
}
7580

7681
public static async Task<IAccessToken> GetAccessTokenAsync(
7782
Task<AuthenticationRecord> authTask,
78-
Func<ValueTask<AccessToken>> getTokenAction,
79-
CancellationToken cancellationToken = default(CancellationToken))
83+
TokenCredential tokenCredential,
84+
TokenRequestContext requestContext,
85+
CancellationToken cancellationToken)
8086
{
8187
var record = await authTask;
8288
cancellationToken.ThrowIfCancellationRequested();
83-
var token = await getTokenAction();
89+
var token = await tokenCredential.GetTokenAsync(requestContext, cancellationToken);
8490

85-
return new MsalAccessToken(token.Token, record.TenantId, record.Username, record.HomeAccountId);
91+
return new MsalAccessToken(tokenCredential, requestContext, token.Token, token.ExpiresOn, record.TenantId, record.Username, record.HomeAccountId);
92+
}
93+
94+
95+
private void Renew()
96+
{
97+
if(IsNearExpiration())
98+
{
99+
var token = TokenCredential.GetToken(TokenRequestContext, default(CancellationToken));
100+
AccessToken = token.Token;
101+
ExpiredOn = token.ExpiresOn;
102+
}
103+
}
104+
105+
private bool IsNearExpiration()
106+
{
107+
#if DEBUG
108+
if (Environment.GetEnvironmentVariable("FORCE_EXPIRED_ACCESS_TOKEN") != null)
109+
{
110+
return true;
111+
}
112+
#endif
113+
var timeUntilExpiration = ExpiredOn - DateTimeOffset.UtcNow;
114+
return timeUntilExpiration < ExpirationThreshold;
86115
}
87116
}
88117
}

src/Accounts/Authenticators/ServicePrincipalAuthenticator.cs

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ namespace Microsoft.Azure.PowerShell.Authenticators
3030
public class ServicePrincipalAuthenticator : DelegatingAuthenticator
3131
{
3232
private const string AuthenticationFailedMessage = "No certificate thumbprint or secret provided for the given service principal '{0}'.";
33-
private ConcurrentDictionary<string, ClientCertificateCredential> ClientCertCredentialMap = new ConcurrentDictionary<string, ClientCertificateCredential>(StringComparer.OrdinalIgnoreCase);
3433

3534
//MSAL doesn't cache Service Principal into msal.cache
3635
public override Task<IAccessToken> Authenticate(AuthenticationParameters parameters, CancellationToken cancellationToken)
@@ -54,31 +53,23 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet
5453
if (!string.IsNullOrEmpty(spParameters.Thumbprint))
5554
{
5655
//Service Principal with Certificate
57-
ClientCertificateCredential certCredential;
58-
if (!ClientCertCredentialMap.TryGetValue(spParameters.ApplicationId, out certCredential))
59-
{
60-
//first time login
61-
var certificate = AzureSession.Instance.DataStore.GetCertificate(spParameters.Thumbprint);
62-
certCredential = new ClientCertificateCredential(tenantId, spParameters.ApplicationId, certificate, options);
63-
var tokenTask = certCredential.GetTokenAsync(requestContext, cancellationToken);
64-
return MsalAccessToken.GetAccessTokenAsync(tokenTask,
65-
() => { ClientCertCredentialMap[spParameters.ApplicationId] = certCredential; },
66-
spParameters.TenantId,
67-
spParameters.ApplicationId);
68-
}
69-
else
70-
{
71-
var tokenTask = certCredential.GetTokenAsync(requestContext, cancellationToken);
72-
return MsalAccessToken.GetAccessTokenAsync(tokenTask, spParameters.TenantId, spParameters.ApplicationId);
73-
}
56+
var certificate = AzureSession.Instance.DataStore.GetCertificate(spParameters.Thumbprint);
57+
ClientCertificateCredential certCredential = new ClientCertificateCredential(tenantId, spParameters.ApplicationId, certificate, options);
58+
return MsalAccessToken.GetAccessTokenAsync(
59+
certCredential,
60+
requestContext,
61+
cancellationToken,
62+
spParameters.TenantId,
63+
spParameters.ApplicationId);
7464
}
7565
else if (spParameters.Secret != null)
7666
{
7767
// service principal with secret
7868
var secretCredential = new ClientSecretCredential(tenantId, spParameters.ApplicationId, spParameters.Secret.ConvertToString(), options);
79-
var tokenTask = secretCredential.GetTokenAsync(requestContext, cancellationToken);
8069
return MsalAccessToken.GetAccessTokenAsync(
81-
tokenTask,
70+
secretCredential,
71+
requestContext,
72+
cancellationToken,
8273
spParameters.TenantId,
8374
spParameters.ApplicationId);
8475
}

src/Accounts/Authenticators/SilentAuthenticator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet
4949
var cacheCredential = new SharedTokenCacheCredential(options);
5050
var requestContext = new TokenRequestContext(scopes);
5151
var tokenTask = cacheCredential.GetTokenAsync(requestContext);
52-
return MsalAccessToken.GetAccessTokenAsync(tokenTask, silentParameters.TenantId, silentParameters.UserId, silentParameters.HomeAccountId);
52+
return MsalAccessToken.GetAccessTokenAsync(cacheCredential, requestContext, cancellationToken, silentParameters.TenantId, silentParameters.UserId, silentParameters.HomeAccountId);
5353
}
5454

5555
public override bool CanAuthenticate(AuthenticationParameters parameters)

src/Accounts/Authenticators/UsernamePasswordAuthenticator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public override Task<IAccessToken> Authenticate(AuthenticationParameters paramet
6565
var authTask = passwordCredential.AuthenticateAsync(requestContext, cancellationToken);
6666
return MsalAccessToken.GetAccessTokenAsync(
6767
authTask,
68-
() => passwordCredential.GetTokenAsync(requestContext, cancellationToken),
68+
passwordCredential,
69+
requestContext,
6970
cancellationToken);
7071
}
7172
else

0 commit comments

Comments
 (0)