diff --git a/src/Accounts/Accounts/Account/DisconnectAzureRmAccount.cs b/src/Accounts/Accounts/Account/DisconnectAzureRmAccount.cs index 2d101f9802f7..1dda4d6ffd71 100644 --- a/src/Accounts/Accounts/Account/DisconnectAzureRmAccount.cs +++ b/src/Accounts/Accounts/Account/DisconnectAzureRmAccount.cs @@ -124,21 +124,20 @@ public override void ExecuteCmdlet() if (ShouldProcess(string.Format("Log out principal '{0}'", azureAccount.Id), "log out")) { - if (GetContextModificationScope() == ContextModificationScope.CurrentUser) - { - AzureSession.Instance.AuthenticationFactory.RemoveUser(azureAccount, null); - } - if (AzureRmProfileProvider.Instance.Profile != null) { ModifyContext((localProfile, profileClient) => - { - var matchingContexts = localProfile.Contexts?.Values?.Where((c) => c != null && c.Account != null && string.Equals(c.Account.Id, azureAccount.Id, StringComparison.CurrentCultureIgnoreCase)); - foreach (var context in matchingContexts) - { - profileClient.TryRemoveContext(context); - } - }); + { + var matchingContexts = localProfile.Contexts?.Values?.Where((c) => c != null && c.Account != null && string.Equals(c.Account.Id, azureAccount.Id, StringComparison.CurrentCultureIgnoreCase)); + foreach (var context in matchingContexts) + { + if (GetContextModificationScope() == ContextModificationScope.CurrentUser) + { + AzureSession.Instance.AuthenticationFactory.RemoveUser(azureAccount, context.Environment.ActiveDirectoryAuthority); + } + profileClient.TryRemoveContext(context); + } + }); } WriteObject(new PSAzureRmAccount(azureAccount)); diff --git a/src/Accounts/Accounts/Context/ClearAzureRmContext.cs b/src/Accounts/Accounts/Context/ClearAzureRmContext.cs index 4da48f432e4b..ab329cebc28a 100644 --- a/src/Accounts/Accounts/Context/ClearAzureRmContext.cs +++ b/src/Accounts/Accounts/Context/ClearAzureRmContext.cs @@ -65,29 +65,29 @@ void ClearContext(AzureRmProfile profile, RMProfileClient client) bool result = false; if (profile != null) { + PowerShellTokenCacheProvider tokenCacheProvider = null; + if (!AzureSession.Instance.TryGetComponent(PowerShellTokenCacheProvider.PowerShellTokenCacheProviderKey, out tokenCacheProvider)) + { + WriteWarning(Resources.ClientFactoryNotRegisteredClear); + } + var contexts = profile.Contexts.Values; foreach (var context in contexts) { + tokenCacheProvider?.ClearCache(context.Environment.ActiveDirectoryAuthority); client.TryRemoveContext(context); } - PowerShellTokenCacheProvider tokenCacheProvider; - if (!AzureSession.Instance.TryGetComponent(PowerShellTokenCacheProvider.PowerShellTokenCacheProviderKey, out tokenCacheProvider)) + if (tokenCacheProvider != null) { - WriteWarning(Resources.ClientFactoryNotRegisteredClear); - } - else - { - tokenCacheProvider.ClearCache(); - var defaultContext = new AzureContext(); - profile.TrySetDefaultContext(defaultContext); + profile.TrySetDefaultContext(new AzureContext()); result = true; } + if (AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out AzKeyStore keyStore)) { keyStore?.Clear(); } - } AzureSession.Instance.RaiseContextClearedEvent(); diff --git a/src/Accounts/Accounts/Context/GetAzureRMContext.cs b/src/Accounts/Accounts/Context/GetAzureRMContext.cs index 4a8b652bb66a..ded3a84c900b 100644 --- a/src/Accounts/Accounts/Context/GetAzureRMContext.cs +++ b/src/Accounts/Accounts/Context/GetAzureRMContext.cs @@ -84,6 +84,7 @@ public override void ExecuteCmdlet() var defaultProfile = DefaultProfile as AzureRmProfile; if (defaultProfile != null && string.Equals(AzureSession.Instance?.ARMContextSaveMode, "CurrentUser")) { + AzureSession.Instance.SetProperty(AzureSession.Property.Environment, DefaultContext.Environment.Name); defaultProfile.RefreshContextsFromCache(_cmdletContext); } } diff --git a/src/Accounts/Accounts/Context/RemoveAzureRmContext.cs b/src/Accounts/Accounts/Context/RemoveAzureRmContext.cs index 58bd426291c6..dace41f2fcc1 100644 --- a/src/Accounts/Accounts/Context/RemoveAzureRmContext.cs +++ b/src/Accounts/Accounts/Context/RemoveAzureRmContext.cs @@ -12,17 +12,16 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; -using System.Linq; -using System.Management.Automation; - using Microsoft.Azure.Commands.Common.Authentication; using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.Commands.Common.Authentication.Models; using Microsoft.Azure.Commands.Profile.Common; using Microsoft.Azure.Commands.Profile.Models.Core; using Microsoft.Azure.Commands.Profile.Properties; -using Microsoft.WindowsAzure.Commands.Utilities.Common; + +using System; +using System.Linq; +using System.Management.Automation; namespace Microsoft.Azure.Commands.Profile.Context { @@ -91,7 +90,7 @@ public override void ExecuteCmdlet() } else { - if (!tokenCacheProvider.TryRemoveAccount(removedContext.Account.Id)) + if (!tokenCacheProvider.TryRemoveAccount(removedContext.Account.Id, removedContext.Environment.ActiveDirectoryAuthority)) { WriteWarning(string.Format(Resources.NoContextsRemain, removedContext.Account.Id)); } diff --git a/src/Accounts/Authentication.ResourceManager/AzureRmProfile.cs b/src/Accounts/Authentication.ResourceManager/AzureRmProfile.cs index 9183fa8a7e1e..9d0693f4a232 100644 --- a/src/Accounts/Authentication.ResourceManager/AzureRmProfile.cs +++ b/src/Accounts/Authentication.ResourceManager/AzureRmProfile.cs @@ -823,8 +823,9 @@ public void RefreshContextsFromCache(ICmdletContext cmdletContext) string authority = null; if (TryGetEnvironment(AzureSession.Instance.GetProperty(AzureSession.Property.Environment), out IAzureEnvironment sessionEnvironment)) { - authority = $"{sessionEnvironment.ActiveDirectoryAuthority}organizations"; + authority = $"{sessionEnvironment.ActiveDirectoryAuthority}/organizations"; } + //fixme, if Connect-AzAccount not run, when to get authority var accounts = tokenCacheProvider.ListAccounts(authority); if (!accounts.Any()) { diff --git a/src/Accounts/Authentication/Authentication/TokenCache/InMemoryTokenCacheProvider.cs b/src/Accounts/Authentication/Authentication/TokenCache/InMemoryTokenCacheProvider.cs index 668d64ae31c4..b48aebd57dd5 100644 --- a/src/Accounts/Authentication/Authentication/TokenCache/InMemoryTokenCacheProvider.cs +++ b/src/Accounts/Authentication/Authentication/TokenCache/InMemoryTokenCacheProvider.cs @@ -14,6 +14,7 @@ using Azure.Identity; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Identity.Client; namespace Microsoft.Azure.Commands.Common.Authentication @@ -46,7 +47,7 @@ public override void FlushTokenData() } } - public override void ClearCache() + public override void ClearCache(string authority) { InMemoryTokenCacheOptions = new InMemoryTokenCacheOptions(); } diff --git a/src/Accounts/Authentication/Authentication/TokenCache/PowerShellTokenCacheProvider.cs b/src/Accounts/Authentication/Authentication/TokenCache/PowerShellTokenCacheProvider.cs index 1717a80cc644..238985f5c609 100644 --- a/src/Accounts/Authentication/Authentication/TokenCache/PowerShellTokenCacheProvider.cs +++ b/src/Accounts/Authentication/Authentication/TokenCache/PowerShellTokenCacheProvider.cs @@ -12,10 +12,6 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; -using System.Collections.Generic; -using System.Linq; - using Azure.Identity; using Hyak.Common; @@ -24,13 +20,15 @@ using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Extensions; using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Interfaces; using Microsoft.Azure.Commands.Common.Authentication.Utilities; -using Microsoft.Azure.Commands.Shared.Config; using Microsoft.Azure.Internal.Subscriptions; using Microsoft.Azure.Internal.Subscriptions.Models; -using Microsoft.Azure.PowerShell.Common.Config; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Broker; +using System; +using System.Collections.Generic; +using System.Linq; + namespace Microsoft.Azure.Commands.Common.Authentication { public abstract class PowerShellTokenCacheProvider @@ -55,14 +53,14 @@ public virtual void FlushTokenData() _tokenCacheDataToFlush = null; } - public virtual void ClearCache() + public virtual void ClearCache(string authority = null) { } - public bool TryRemoveAccount(string accountId) + public bool TryRemoveAccount(string accountId, string authority = null) { TracingAdapter.Information(string.Format("[AuthenticationClientFactory] Calling GetAccountsAsync")); - var client = CreatePublicClient(); + var client = CreatePublicClient(authority); var account = client.GetAccountsAsync() .ConfigureAwait(false).GetAwaiter().GetResult() .FirstOrDefault(a => string.Equals(a.Username, accountId, StringComparison.OrdinalIgnoreCase)); @@ -89,7 +87,7 @@ public IEnumerable ListAccounts(string authority = null) { TracingAdapter.Information(string.Format("[PowerShellTokenCacheProvider] Calling GetAccountsAsync on {0}", authority ?? "AzureCloud")); - return CreatePublicClient(authority: authority) + return CreatePublicClient(authority) .GetAccountsAsync() .ConfigureAwait(false).GetAwaiter().GetResult(); } @@ -195,18 +193,7 @@ public virtual IPublicClientApplication CreatePublicClient(string authority, str /// public virtual IPublicClientApplication CreatePublicClient(string authority = null) { - var builder = PublicClientApplicationBuilder.Create(Constants.PowerShellClientId); - if (AzConfigReader.IsWamEnabled(authority)) - { - builder = builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)); - } - if (!string.IsNullOrEmpty(authority)) - { - builder.WithAuthority(authority); - } - var client = builder.Build(); - RegisterCache(client); - return client; + return CreatePublicClient(authority, organizationTenant); } public abstract TokenCachePersistenceOptions GetTokenCachePersistenceOptions(); diff --git a/src/Accounts/Authentication/Authentication/TokenCache/SharedTokenCacheProvider.cs b/src/Accounts/Authentication/Authentication/TokenCache/SharedTokenCacheProvider.cs index c2ab00bf3f87..2c9cfd346c48 100644 --- a/src/Accounts/Authentication/Authentication/TokenCache/SharedTokenCacheProvider.cs +++ b/src/Accounts/Authentication/Authentication/TokenCache/SharedTokenCacheProvider.cs @@ -12,13 +12,14 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; - using Azure.Identity; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; +using System; + namespace Microsoft.Azure.Commands.Common.Authentication { public class SharedTokenCacheProvider : PowerShellTokenCacheProvider @@ -103,9 +104,9 @@ protected override void RegisterCache(IPublicClientApplication client) } } - public override void ClearCache() + public override void ClearCache(string authority) { - var client = CreatePublicClient(); + var client = CreatePublicClient(authority); var accounts = client.GetAccountsAsync().GetAwaiter().GetResult(); foreach (var account in accounts) { diff --git a/src/Accounts/Authentication/Factories/AuthenticationFactory.cs b/src/Accounts/Authentication/Factories/AuthenticationFactory.cs index 998330d62644..ad11e4361b78 100644 --- a/src/Accounts/Authentication/Factories/AuthenticationFactory.cs +++ b/src/Accounts/Authentication/Factories/AuthenticationFactory.cs @@ -522,6 +522,47 @@ public void RemoveUser(IAzureAccount account, IAzureTokenCache tokenCache) } } + /// + /// Remove any stored credentials for the given user and the Azure environment used. + /// + /// The account to remove credentials for + /// The Microsoft Entra authority + public void RemoveUser(IAzureAccount account, string authority) + { + if (account != null && !string.IsNullOrEmpty(account.Id) && !string.IsNullOrWhiteSpace(account.Type)) + { + switch (account.Type) + { + case AzureAccount.AccountType.AccessToken: + account.SetProperty(AzureAccount.Property.AccessToken, null); + account.SetProperty(AzureAccount.Property.GraphAccessToken, null); + account.SetProperty(AzureAccount.Property.KeyVaultAccessToken, null); + break; + case AzureAccount.AccountType.ManagedService: + account.SetProperty(AzureAccount.Property.MSILoginUri, null); + break; + case AzureAccount.AccountType.ServicePrincipal: + try + { + KeyStore.RemoveSecureString(new ServicePrincipalKey(AzureAccount.Property.ServicePrincipalSecret, + account.Id, account.GetTenants().FirstOrDefault())); + KeyStore.RemoveSecureString(new ServicePrincipalKey(AzureAccount.Property.CertificatePassword, + account.Id, account.GetTenants().FirstOrDefault())); + } + catch + { + // make best effort to remove credentials + } + + RemoveFromTokenCache(account, authority); + break; + case AzureAccount.AccountType.User: + RemoveFromTokenCache(account, authority); + break; + } + } + } + private string GetResourceId(string resourceIdorEndpointName, IAzureEnvironment environment) { return environment.GetEndpoint(resourceIdorEndpointName) ?? resourceIdorEndpointName; @@ -558,7 +599,7 @@ private string GetEndpointToken(IAzureAccount account, string targetEndpoint) return account.GetProperty(tokenKey); } - private void RemoveFromTokenCache(IAzureAccount account) + private void RemoveFromTokenCache(IAzureAccount account, string authority = null) { PowerShellTokenCacheProvider tokenCacheProvider; if (!AzureSession.Instance.TryGetComponent(PowerShellTokenCacheProvider.PowerShellTokenCacheProviderKey, out tokenCacheProvider)) @@ -566,12 +607,12 @@ private void RemoveFromTokenCache(IAzureAccount account) throw new NullReferenceException(Resources.AuthenticationClientFactoryNotRegistered); } - var publicClient = tokenCacheProvider.CreatePublicClient(); + var publicClient = tokenCacheProvider.CreatePublicClient(authority); var accounts = publicClient.GetAccountsAsync() .ConfigureAwait(false).GetAwaiter().GetResult(); var tokenAccounts = accounts.Where(a => MatchCacheItem(account, a)); foreach (var tokenAccount in tokenAccounts) - { + { publicClient.RemoveAsync(tokenAccount) .ConfigureAwait(false).GetAwaiter().GetResult(); } diff --git a/tools/TestFx/Mocks/MockCertificateAuthenticationFactory.cs b/tools/TestFx/Mocks/MockCertificateAuthenticationFactory.cs index 074d04cf3a20..0afa4f99be7a 100644 --- a/tools/TestFx/Mocks/MockCertificateAuthenticationFactory.cs +++ b/tools/TestFx/Mocks/MockCertificateAuthenticationFactory.cs @@ -124,5 +124,10 @@ public IAccessToken Authenticate(IAzureAccount account, IAzureEnvironment enviro { throw new NotImplementedException(); } + + public void RemoveUser(IAzureAccount account, string authority) + { + throw new NotImplementedException(); + } } } diff --git a/tools/TestFx/Mocks/MockTokenAuthenticationFactory.cs b/tools/TestFx/Mocks/MockTokenAuthenticationFactory.cs index 7febbd8ca52e..32b6eaa9a493 100644 --- a/tools/TestFx/Mocks/MockTokenAuthenticationFactory.cs +++ b/tools/TestFx/Mocks/MockTokenAuthenticationFactory.cs @@ -148,5 +148,10 @@ public ServiceClientCredentials GetServiceClientCredentials(IAzureContext contex { return GetServiceClientCredentials(context, targetEndpoint, AzureCmdletContext.CmdletNone); } + + public void RemoveUser(IAzureAccount account, string authority) + { + throw new NotImplementedException(); + } } }