Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using Microsoft.Azure.Commands.Common.Exceptions;

namespace Microsoft.Azure.Commands.ResourceManager.Common.Test
{
Expand All @@ -55,9 +56,11 @@ public class SilentReAuthByTenantCmdletTest
private const string fakeToken = "fakertoken";

private const string body200 = @"{{""value"":[{{""id"":""/tenants/{0}"",""tenantId"":""{0}"",""countryCode"":""US"",""displayName"":""AzureSDKTeam"",""domains"":[""AzureSDKTeam.onmicrosoft.com"",""azdevextest.com""],""tenantCategory"":""Home""}}]}}";
private const string body401 = @"{""error"":{""code"":""AuthenticationFailed"",""message"":""Authentication failed.""}}";
private const string WwwAuthenticateIP = @"Bearer authorization_uri=""https://login.windows.net/"", error=""invalid_token"", error_description=""Tenant IP Policy validate failed."", claims=""eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0=""";

private const string bodyErrorMessage401 = "Authentication failed.";
private const string body401 = @"{""error"":{""code"":""AuthenticationFailed"",""message"":"""+bodyErrorMessage401+@"""}}";
private const string claimsChallengeBase64 = "eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0=";
private const string WwwAuthenticateIP = @"Bearer authorization_uri=""https://login.windows.net/"", error=""invalid_token"", error_description=""Tenant IP Policy validate failed."", claims="""+ claimsChallengeBase64+@"""";
private const string identityExceptionMessage = "Exception from Azure Identity.";
XunitTracingInterceptor xunitLogger;

public class GetAzureRMTenantCommandMock : GetAzureRMTenantCommand
Expand Down Expand Up @@ -171,7 +174,7 @@ public void SilentReauthenticateFailure()
{
return new ValueTask<AccessToken>(new AccessToken(fakeToken, DateTimeOffset.Now.AddHours(1)));
}
throw new CredentialUnavailableException("Exception from Azure Identity.");
throw new CredentialUnavailableException(identityExceptionMessage);
}
));
AzureSession.Instance.RegisterComponent(nameof(AzureCredentialFactory), () => mockAzureCredentialFactory.Object, true);
Expand All @@ -190,9 +193,11 @@ public void SilentReauthenticateFailure()

// Act
cmdlet.InvokeBeginProcessing();
AuthenticationFailedException e = Assert.Throws<AuthenticationFailedException>(() => cmdlet.ExecuteCmdlet());
string errorMessage = $"Exception from Azure Identity.{Environment.NewLine}authorization_uri: https://login.windows.net/{Environment.NewLine}error: invalid_token{Environment.NewLine}error_description: Tenant IP Policy validate failed.{Environment.NewLine}claims: eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0={Environment.NewLine}";
Assert.Equal(errorMessage, e.Message);
AzPSAuthenticationFailedException e = Assert.Throws<AzPSAuthenticationFailedException>(() => cmdlet.ExecuteCmdlet());
Assert.DoesNotContain(identityExceptionMessage, e.Message); // cause it's misleading
Assert.Contains(bodyErrorMessage401, e.Message);
Assert.Contains("Connect-AzAccount", e.Message);
Assert.Contains(claimsChallengeBase64, e.Message);
cmdlet.InvokeEndProcessing();
}
finally
Expand Down
18 changes: 14 additions & 4 deletions src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
// ----------------------------------------------------------------------------------

using Azure.Identity;

using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Interfaces;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Models;
using Microsoft.Azure.Commands.Common.Authentication.Config.Models;
using Microsoft.Azure.Commands.Common.Authentication.Factories;
Expand All @@ -41,7 +39,6 @@
using Microsoft.WindowsAzure.Commands.Common.Sanitizer;
using Microsoft.WindowsAzure.Commands.Common.Utilities;
using Microsoft.WindowsAzure.Commands.Utilities.Common;

using System;
using System.Collections.Concurrent;
using System.Linq;
Expand Down Expand Up @@ -237,6 +234,10 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod
[ValidateNotNullOrEmpty]
public string FederatedToken { get; set; }

[Parameter(ParameterSetName = UserParameterSet, Mandatory = false, HelpMessage = "Specifies the claims challenge with base64 encoding.")]
[ValidateNotNullOrEmpty]
public string ClaimsChallenge { get; set; }

protected override IAzureContext DefaultContext
{
get
Expand Down Expand Up @@ -353,7 +354,6 @@ public override void ExecuteCmdlet()
{
subscriptionName = Subscription;
}

}
else if (AzureSession.Instance.TryGetComponent<IConfigManager>(nameof(IConfigManager), out var configManager))
{
Expand All @@ -373,6 +373,15 @@ public override void ExecuteCmdlet()
}
}

string claimsChallenge = null;
if (this.IsParameterBound(c => c.ClaimsChallenge))
{
if (!ClaimsChallengeUtilities.TryParseClaimsChallenge(ClaimsChallenge, out claimsChallenge))
{
throw new PSArgumentException(Resources.InvalidClaimsChallenge, nameof(ClaimsChallenge));
}
}

var azureAccount = new AzureAccount();

switch (ParameterSetName)
Expand Down Expand Up @@ -548,6 +557,7 @@ public override void ExecuteCmdlet()
SkipValidation,
new OpenIDConfiguration(Tenant, baseUri: _environment.ActiveDirectoryAuthority, httpClientFactory: httpClientFactory),
WriteWarningEvent, //Could not use WriteWarning directly because it may be in worker thread
claimsChallenge,
name,
shouldPopulateContextList,
MaxContextPopulation,
Expand Down
2 changes: 2 additions & 0 deletions src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
-->

## Upcoming Release
* Added new parameter `-ClaimsChallenge` to `Connect-AzAccount` to support claims challenge authentication for MFA.
* Refined the error message when a cmdlet fails because of policy violations about Multi-Factor Authentication (MFA) to provide more actionable guidance.

## Version 5.1.1
* Updated the date in the message about multi-factor authentication (MFA). For more details, see https://go.microsoft.com/fwlink/?linkid=2276971
Expand Down
24 changes: 12 additions & 12 deletions src/Accounts/Accounts/CommonModule/ContextAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using System.Collections.Generic;
using Azure.Identity;
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core;
using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.Azure.Commands.Common.Utilities;
using Microsoft.Azure.Commands.Profile.Models;
using System.Globalization;
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Profile.Properties;
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.Profile.Properties;
using Azure.Identity;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.Commands.Common
{
Expand Down Expand Up @@ -200,14 +201,13 @@ internal async Task<HttpResponseMessage> AuthenticationHelper(IAzureContext cont
{
var response = await next(request, cancelToken, cancelAction, signal);

if (response.MatchClaimsChallengePattern())
if (response.MatchClaimsChallengePattern(out var claimsChallenge))
{
//get token again with claims challenge
if (accessToken is IClaimsChallengeProcessor processor)
{
try
{
var claimsChallenge = ClaimsChallengeUtilities.GetClaimsChallenge(response);
if (!string.IsNullOrEmpty(claimsChallenge))
{
await processor.OnClaimsChallenageAsync(newRequest, claimsChallenge, cancelToken).ConfigureAwait(false);
Expand All @@ -219,7 +219,7 @@ internal async Task<HttpResponseMessage> AuthenticationHelper(IAzureContext cont
}
catch (AuthenticationFailedException e)
{
throw e.WithAdditionalMessage(response?.GetWwwAuthenticateMessage());
throw new AzPSAuthenticationFailedException(ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync()), null, e);
}
}
}
Expand Down
50 changes: 27 additions & 23 deletions src/Accounts/Accounts/Models/RMProfileClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Interfaces;
Expand All @@ -22,13 +23,11 @@
using Microsoft.Azure.Commands.Profile.Utilities;
using Microsoft.Rest.Azure;
using Microsoft.WindowsAzure.Commands.Common;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Security;

using AuthenticationMessages = Microsoft.Azure.Commands.Common.Authentication.Properties.Resources;
using ProfileMessages = Microsoft.Azure.Commands.Profile.Properties.Resources;
using ResourceMessages = Microsoft.Azure.Commands.ResourceManager.Common.Properties.Resources;
Expand All @@ -52,7 +51,7 @@ private IAzureContext DefaultContext
{
get
{
if(_profile == null || _profile.DefaultContext == null || _profile.DefaultContext.Account == null)
if (_profile == null || _profile.DefaultContext == null || _profile.DefaultContext.Account == null)
{
throw new PSInvalidOperationException(ResourceMessages.RunConnectAccount);
}
Expand Down Expand Up @@ -130,21 +129,22 @@ public AzureRmProfile Login(
bool skipValidation,
IOpenIDConfiguration openIDConfigDoc,
Action<string> promptAction,
string claimsChallenge = null,
string name = null,
bool shouldPopulateContextList = true,
int maxContextPopulation = Profile.ConnectAzureRmAccountCommand.DefaultMaxContextPopulation,
string authScope = null,
bool IsInteractiveContextSelectionEnabled = true)
{

WriteInteractiveInformationMessage($"{PSStyle.ForegroundColor.BrightYellow}{Resources.PleaseSelectAccount}{PSStyle.Reset}{System.Environment.NewLine}");

IAzureSubscription defaultSubscription = null;
IAzureTenant defaultTenant = null;
List<AzureSubscription> subscriptions = new List<AzureSubscription>();
List<AzureSubscription> tempSubscriptions = null;
string tenantName = null;

bool selectSubscriptionFromList = AzureAccount.AccountType.User.Equals(account.Type) &&
IsInteractiveContextSelectionEnabled &&
string.IsNullOrEmpty(subscriptionId) &&
Expand All @@ -161,9 +161,9 @@ public AzureRmProfile Login(
SubscritpionClientCandidates.Reset();

bool needDataPlanAuthFirst = !string.IsNullOrEmpty(authScope);
if(needDataPlanAuthFirst)
if (needDataPlanAuthFirst)
{
var token = AcquireAccessToken(account, environment, tenantIdOrName, password, promptBehavior, promptAction, authScope);
var token = AcquireAccessToken(account, environment, tenantIdOrName, password, promptBehavior, promptAction, claimsChallenge, authScope);
promptBehavior = ShowDialog.Never;
}

Expand Down Expand Up @@ -202,7 +202,8 @@ public AzureRmProfile Login(
tenantIdOrName,
password,
promptBehavior,
promptAction);
promptAction,
claimsChallenge);

if (!Guid.TryParse(tenantIdOrName, out Guid _))
{
Expand All @@ -229,7 +230,7 @@ public AzureRmProfile Login(
}
}
}
catch(Exception e)
catch (Exception e)
{
string baseMessage = string.Format(ProfileMessages.TenantDomainNotFound, tenantIdOrName);
var typeMessageMap = new Dictionary<string, string>
Expand Down Expand Up @@ -293,7 +294,7 @@ public AzureRmProfile Login(

try
{
token = AcquireAccessToken(account, environment, tenant.Id, password, ShowDialog.Auto, null);
token = AcquireAccessToken(account, environment, tenant.Id, password, ShowDialog.Auto, null, claimsChallenge);
if (accountId == null)
{
accountId = account.Id;
Expand All @@ -314,7 +315,7 @@ public AzureRmProfile Login(
token = null;
}
}
catch(Exception e)
catch (Exception e)
{
WriteWarningMessage(string.Format(ProfileMessages.UnableToAqcuireToken, tenant.Id, e.Message));
WriteDebugMessage(string.Format(ProfileMessages.UnableToAqcuireToken, tenant.Id, e.ToString()));
Expand All @@ -334,7 +335,7 @@ public AzureRmProfile Login(
defaultTenant = tempTenant;
}
}
if(tempSubscription != null)
if (tempSubscription != null)
{
subscriptions.AddRange(tempSubscriptions);
}
Expand Down Expand Up @@ -397,7 +398,7 @@ public AzureRmProfile Login(
{
var defaultContext = _profile.DefaultContext;
var populatedSubscriptions = (maxContextPopulation < 0 || selectSubscriptionFromList) ? ListSubscriptions(tenantIdOrName) : ListSubscriptions(tenantIdOrName).Take(maxContextPopulation);

foreach (var subscription in populatedSubscriptions)
{
IAzureTenant tempTenant = InteractiveSubscriptionSelectionHelper.GetDetailedTenantFromQueryHistory(_queriedTenants, subscription.GetProperty(AzureSubscription.Property.Tenants)) ?? new AzureTenant()
Expand Down Expand Up @@ -449,7 +450,7 @@ public IAzureContext SetCurrentContext(string subscriptionNameOrId, string tenan
}

var tenantFromSubscription = subscription.GetTenant();
tenant = string.IsNullOrWhiteSpace(tenantId) ? (string.IsNullOrEmpty(tenantFromSubscription) ? context.Tenant : CreateTenant(tenantFromSubscription)): CreateTenant(tenantId);
tenant = string.IsNullOrWhiteSpace(tenantId) ? (string.IsNullOrEmpty(tenantFromSubscription) ? context.Tenant : CreateTenant(tenantFromSubscription)) : CreateTenant(tenantId);
}
else if (!string.IsNullOrWhiteSpace(tenantId))
{
Expand Down Expand Up @@ -536,14 +537,14 @@ public bool TryGetSubscriptionListByName(string tenantId, string subscriptionNam
HashSet<Guid> existedSubscriptionIds = new HashSet<Guid>();

// Consider subscription in Home tenant first, exclude duplicate subscriptions by id.
foreach(IAzureSubscription subscription in subscriptions)
foreach (IAzureSubscription subscription in subscriptions)
{
if (subscription is PSAzureSubscription && subscription.GetTenant() != null
if (subscription is PSAzureSubscription && subscription.GetTenant() != null
&& subscription.GetHomeTenant().Equals(subscription.GetTenant()) && existedSubscriptionIds.Add(subscription.GetId()))
{
subscriptionList.Add(subscription);
}

}
// Consider other subscriptions.
foreach (IAzureSubscription subscription in subscriptions)
Expand Down Expand Up @@ -679,6 +680,7 @@ private IAccessToken AcquireAccessToken(
SecureString password,
string promptBehavior,
Action<string> promptAction,
string claimsChallenge = null,
string resourceId = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId)
{
if (account.Type == AzureAccount.AccountType.AccessToken)
Expand All @@ -689,11 +691,13 @@ private IAccessToken AcquireAccessToken(

var optionalParameters = new Dictionary<string, object>()
{
{AuthenticationFactory.TokenCacheParameterName, _cache},
{AuthenticationFactory.ResourceIdParameterName, resourceId },
{AuthenticationFactory.CmdletContextParameterName, CmdletContext }
{ AuthenticationFactory.ResourceIdParameterName, resourceId },
{ AuthenticationFactory.ClaimsChallengeParameterName, claimsChallenge },
{ AuthenticationFactory.TokenCacheParameterName, _cache },
{ AuthenticationFactory.CmdletContextParameterName, CmdletContext }
};


return AzureSession.Instance.AuthenticationFactory.Authenticate(
account,
environment,
Expand Down Expand Up @@ -814,7 +818,7 @@ private List<AzureTenant> ListAccountTenants(

result = SubscriptionAndTenantClient?.ListAccountTenants(commonTenantToken, environment);
}
catch(Exception e)
catch (Exception e)
{
WriteWarningMessage(string.Format(ProfileMessages.UnableToAqcuireToken, commonTenant, e.Message));
WriteDebugMessage(string.Format(ProfileMessages.UnableToAqcuireToken, commonTenant, e.ToString()));
Expand Down Expand Up @@ -861,7 +865,7 @@ private IEnumerable<AzureSubscription> ListAllSubscriptionsForTenant(
{
accessToken = AcquireAccessToken(account, environment, tenantId, password, promptBehavior, null);
}
catch(Exception e)
catch (Exception e)
{
WriteWarningMessage(string.Format(ProfileMessages.UnableToAqcuireToken, tenantId, e.Message));
WriteDebugMessage(string.Format(ProfileMessages.UnableToAqcuireToken, tenantId, e.ToString()));
Expand All @@ -881,7 +885,7 @@ private void WriteWarningMessage(string message)

private void WriteDebugMessage(string message)
{
if(DebugLog != null)
if (DebugLog != null)
{
DebugLog(message);
}
Expand Down
Loading
Loading