diff --git a/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs b/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs new file mode 100644 index 000000000000..318889ab2825 --- /dev/null +++ b/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs @@ -0,0 +1,181 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; + +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.PowerShell.Authenticators; +using Microsoft.WindowsAzure.Commands.ScenarioTest; + +using Xunit; +using Xunit.Abstractions; + +namespace Common.Authenticators.Test +{ + /// + /// Tests for . + /// Covers the bug reported in https://github.com/Azure/azure-powershell/issues/28028 where + /// Get-AzAccessToken -ResourceUrl "https://management.azure.com/" failed when signed in via + /// Connect-AzAccount -AccessToken, even though the stored ARM token is valid for that audience. + /// + public class AccessTokenAuthenticatorTests + { + private const string TestTenantId = "test-tenant-id"; + private const string TestUserId = "testuser@contoso.com"; + private const string FakeArmToken = "fake-arm-access-token"; + private const string FakeGraphToken = "fake-graph-access-token"; + private const string FakeKeyVaultToken = "fake-keyvault-access-token"; + + private readonly IAzureEnvironment _azureCloud = AzureEnvironment.PublicEnvironments["AzureCloud"]; + + public AccessTokenAuthenticatorTests(ITestOutputHelper output) + { + AzureSessionInitializer.InitializeAzureSession(); + } + + private AzureAccount CreateAccessTokenAccount() + { + var account = new AzureAccount + { + Id = TestUserId, + Type = AzureAccount.AccountType.AccessToken, + }; + account.SetProperty(AzureAccount.Property.AccessToken, FakeArmToken); + account.SetProperty(AzureAccount.Property.GraphAccessToken, FakeGraphToken); + account.SetProperty(AzureAccount.Property.KeyVaultAccessToken, FakeKeyVaultToken); + return account; + } + + private AccessTokenParameters CreateParameters(string resourceId, AzureAccount account = null) + { + return new AccessTokenParameters( + new InMemoryTokenCacheProvider(), + _azureCloud, + null, + TestTenantId, + resourceId, + account ?? CreateAccessTokenAccount()); + } + + /// + /// Verifies that the stored ARM access token is returned when the resource URL is + /// the legacy management endpoint (https://management.core.windows.net/). + /// This is the pre-existing working case. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsArmToken_WhenResourceIsManagementCoreWindowsNet() + { + var resourceId = "https://management.core.windows.net/"; + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeArmToken, token.AccessToken); + Assert.Equal(TestUserId, token.UserId); + Assert.Equal(TestTenantId, token.TenantId); + } + + /// + /// Verifies that the stored ARM access token is returned when the resource URL is + /// https://management.azure.com/ — the Azure Resource Manager endpoint URL. + /// This covers the bug in https://github.com/Azure/azure-powershell/issues/28028. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsArmToken_WhenResourceIsManagementAzureCom() + { + // This is the resource URL that previously caused the error: + // "[AccessTokenAuthenticator] failed to retrieve access token for resource 'https://management.azure.com/'" + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.ResourceManager); + Assert.Equal("https://management.azure.com/", resourceId, StringComparer.OrdinalIgnoreCase); + + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeArmToken, token.AccessToken); + Assert.Equal(TestUserId, token.UserId); + Assert.Equal(TestTenantId, token.TenantId); + } + + /// + /// Verifies that the KeyVault access token is returned when requesting the KeyVault resource. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsKeyVaultToken_WhenResourceIsKeyVaultEndpoint() + { + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId); + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeKeyVaultToken, token.AccessToken); + } + + /// + /// Verifies that the AAD Graph access token is returned when requesting the Graph resource. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsGraphToken_WhenResourceIsGraphEndpoint() + { + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.GraphEndpointResourceId); + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeGraphToken, token.AccessToken); + } + + /// + /// Verifies that an InvalidOperationException is thrown when requesting a resource + /// for which no token was provided at login time. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ThrowsInvalidOperationException_WhenResourceIsUnsupported() + { + var resourceId = "https://unsupported.resource.example.com/"; + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + await Assert.ThrowsAsync(() => authenticator.Authenticate(parameters)); + } + + /// + /// Verifies that CanAuthenticate returns true only for AccessTokenParameters. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanAuthenticate_ReturnsTrueForAccessTokenParameters() + { + var parameters = CreateParameters("https://management.azure.com/"); + var authenticator = new AccessTokenAuthenticator(); + + Assert.True(authenticator.CanAuthenticate(parameters)); + } + } +} diff --git a/src/Accounts/Authenticators/AccessTokenAuthenticator.cs b/src/Accounts/Authenticators/AccessTokenAuthenticator.cs index 5ba7c72b6f92..b2dcceb5ee1f 100644 --- a/src/Accounts/Authenticators/AccessTokenAuthenticator.cs +++ b/src/Accounts/Authenticators/AccessTokenAuthenticator.cs @@ -68,7 +68,8 @@ public override Task Authenticate(AuthenticationParameters paramet else if ((resourceId.EqualsInsensitively(environment.ActiveDirectoryServiceEndpointResourceId) || resourceId.EqualsInsensitively(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId) || resourceId.EqualsInsensitively(environment.GetEndpoint(environment.ActiveDirectoryServiceEndpointResourceId)) || - resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId))) + resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId)) || + resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ResourceManager))) && account.IsPropertySet(AzureAccount.Property.AccessToken)) { TracingAdapter.Information($"{DateTime.Now:T} - [AccessTokenAuthenticator] Creating access token - Tenant: '{tenant}', ResourceId: '{resourceId}', UserId: '{account.Id}'");