From 8e5dca3aea1c5668f4a3b3f9488ec140b2ac0966 Mon Sep 17 00:00:00 2001 From: Scott Schaab Date: Thu, 6 Jun 2019 14:15:59 -0700 Subject: [PATCH] Initial MSI credential implementation (#6464) * initial MSI credential implementation * removing uneeded TokenResponse class * adding mock msi credential tests * adding mock transport msi credential test * updating System.Text.Json package reference accross all projects * removing comments from errantly commented asserts * upgrading System.Threading.Tasks.Extensions to match version in Azure.Core * fixing datetime / datetime offset discrepency in SecretsTests.cs --- ...zure.ApplicationModel.Configuration.csproj | 2 +- .../Azure.Core/tests/Azure.Core.Tests.csproj | 2 +- .../Azure.Identity/src/Azure.Identity.csproj | 8 +- .../Azure.Identity/src/AzureCredential.cs | 3 +- .../Azure.Identity/src/IdentityClient.cs | 66 +++++++++++- .../src/ManagedIdentityCredential.cs | 33 ++++++ .../src/Properties/AssemblyInfo.cs | 1 + .../Azure.Identity/src/ScopeUtilities.cs | 37 +++++++ .../tests/Azure.Identity.Tests.csproj | 9 +- .../tests/AzureCredentialTests.cs | 10 +- .../EnvironmentCredentialProviderTests.cs | 17 ++- .../tests/Mock/MockIdentityClient.cs | 100 ++++++++++++++++++ .../MockManagedIdentityCredentialTests.cs | 97 +++++++++++++++++ .../Azure.Identity/tests/Mock/MockScopes.cs | 35 ++++++ .../Azure.Identity/tests/Mock/MockToken.cs | 58 ++++++++++ .../tests/TokenCredentialProviderTests.cs | 22 ++-- .../src/Azure.Security.Keyvault.Keys.csproj | 2 +- .../Azure.Security.KeyVault.Secrets.csproj | 4 +- ...ure.Security.KeyVault.Secrets.Tests.csproj | 2 +- .../tests/SecretsTests.cs | 2 +- 20 files changed, 469 insertions(+), 41 deletions(-) create mode 100644 sdk/identity/Azure.Identity/src/ManagedIdentityCredential.cs create mode 100644 sdk/identity/Azure.Identity/src/ScopeUtilities.cs create mode 100644 sdk/identity/Azure.Identity/tests/Mock/MockIdentityClient.cs create mode 100644 sdk/identity/Azure.Identity/tests/Mock/MockManagedIdentityCredentialTests.cs create mode 100644 sdk/identity/Azure.Identity/tests/Mock/MockScopes.cs create mode 100644 sdk/identity/Azure.Identity/tests/Mock/MockToken.cs diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj index 4133517ce4fa9..c67ad1fbc4a7f 100644 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj @@ -17,7 +17,7 @@ - + diff --git a/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj b/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj index 0ee29b55465ee..574e41dd4510e 100644 --- a/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj +++ b/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index f5a815e69abcf..58e1ce4fc055b 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -18,10 +18,14 @@ - - + + + + + + \ No newline at end of file diff --git a/sdk/identity/Azure.Identity/src/AzureCredential.cs b/sdk/identity/Azure.Identity/src/AzureCredential.cs index 91ab2cdf093b1..ffcb28683843d 100644 --- a/sdk/identity/Azure.Identity/src/AzureCredential.cs +++ b/sdk/identity/Azure.Identity/src/AzureCredential.cs @@ -84,7 +84,8 @@ public override string GetToken(string[] scopes, CancellationToken cancellationT protected abstract AccessToken GetTokenCore(string[] scopes, CancellationToken cancellationToken); - internal IdentityClient Client => _client; + internal IdentityClient Client { get => _client; set => _client = value; } + public static TokenCredential Default { get; set; } diff --git a/sdk/identity/Azure.Identity/src/IdentityClient.cs b/sdk/identity/Azure.Identity/src/IdentityClient.cs index 225f056bc6fe3..fdc5647a88a60 100644 --- a/sdk/identity/Azure.Identity/src/IdentityClient.cs +++ b/sdk/identity/Azure.Identity/src/IdentityClient.cs @@ -20,6 +20,8 @@ internal class IdentityClient { private readonly IdentityClientOptions _options; private readonly HttpPipeline _pipeline; + private readonly Uri ImdsEndptoint = new Uri("http://169.254.169.254/metadata/identity/oauth2/token"); + private readonly string MsiApiVersion = "2018-02-01"; public IdentityClient(IdentityClientOptions options = null) { @@ -32,7 +34,7 @@ public IdentityClient(IdentityClientOptions options = null) BufferResponsePolicy.Singleton); } - public async Task AuthenticateAsync(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) + public virtual async Task AuthenticateAsync(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) { using (Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes)) { @@ -49,7 +51,7 @@ public async Task AuthenticateAsync(string tenantId, string clientI } } - public AccessToken Authenticate(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) + public virtual AccessToken Authenticate(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) { using (Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes)) { @@ -66,6 +68,66 @@ public AccessToken Authenticate(string tenantId, string clientId, string clientS } } + public virtual async Task AuthenticateManagedIdentityAsync(string[] scopes, string clientId = null, CancellationToken cancellationToken = default) + { + using (Request request = CreateManagedIdentityAuthRequest(scopes, clientId)) + { + var response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + + if (response.Status == 200 || response.Status == 201) + { + var result = await DeserializeAsync(response.ContentStream, cancellationToken).ConfigureAwait(false); + + return new Response(response, result); + } + + throw response.CreateRequestFailedException(); + } + } + + public virtual AccessToken AuthenticateManagedIdentity(string[] scopes, string clientId = null, CancellationToken cancellationToken = default) + { + using (Request request = CreateManagedIdentityAuthRequest(scopes, clientId)) + { + var response = _pipeline.SendRequest(request, cancellationToken); + + if (response.Status == 200 || response.Status == 201) + { + var result = Deserialize(response.ContentStream); + + return new Response(response, result); + } + + throw response.CreateRequestFailedException(); + } + } + + private Request CreateManagedIdentityAuthRequest(string[] scopes, string clientId = null) + { + // covert the scopes to a resource string + string resource = ScopeUtilities.ScopesToResource(scopes); + + Request request = _pipeline.CreateRequest(); + + request.Method = HttpPipelineMethod.Get; + + request.Headers.Add("Metadata", "true"); + + // TODO: support MSI for hosted services + request.UriBuilder.Uri = ImdsEndptoint; + + request.UriBuilder.AppendQuery("api-version", MsiApiVersion); + + request.UriBuilder.AppendQuery("resource", Uri.EscapeDataString(resource)); + + if (!string.IsNullOrEmpty(clientId)) + { + request.UriBuilder.AppendQuery("client_id", Uri.EscapeDataString(clientId)); + } + + return request; + } + private Request CreateClientSecretAuthRequest(string tenantId, string clientId, string clientSecret, string[] scopes) { Request request = _pipeline.CreateRequest(); diff --git a/sdk/identity/Azure.Identity/src/ManagedIdentityCredential.cs b/sdk/identity/Azure.Identity/src/ManagedIdentityCredential.cs new file mode 100644 index 0000000000000..1b7316818132f --- /dev/null +++ b/sdk/identity/Azure.Identity/src/ManagedIdentityCredential.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Identity +{ + public class ManagedIdentityCredential : AzureCredential + { + private string _clientId; + + public ManagedIdentityCredential(string clientId = null, IdentityClientOptions options = null) + : base(options) + { + _clientId = clientId; + } + + protected override async Task GetTokenCoreAsync(string[] scopes, CancellationToken cancellationToken = default) + { + return await this.Client.AuthenticateManagedIdentityAsync(scopes, _clientId, cancellationToken).ConfigureAwait(false); + } + + protected override AccessToken GetTokenCore(string[] scopes, CancellationToken cancellationToken = default) + { + return this.Client.AuthenticateManagedIdentity(scopes, _clientId, cancellationToken); + } + } +} diff --git a/sdk/identity/Azure.Identity/src/Properties/AssemblyInfo.cs b/sdk/identity/Azure.Identity/src/Properties/AssemblyInfo.cs index 2d117cc96646d..6918e65aa1d52 100644 --- a/sdk/identity/Azure.Identity/src/Properties/AssemblyInfo.cs +++ b/sdk/identity/Azure.Identity/src/Properties/AssemblyInfo.cs @@ -2,3 +2,4 @@ using System.Runtime.CompilerServices; [assembly:AzureSdkClientLibrary("identity")] +[assembly: InternalsVisibleTo("Azure.Identity.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] diff --git a/sdk/identity/Azure.Identity/src/ScopeUtilities.cs b/sdk/identity/Azure.Identity/src/ScopeUtilities.cs new file mode 100644 index 0000000000000..e3095c834f25e --- /dev/null +++ b/sdk/identity/Azure.Identity/src/ScopeUtilities.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Azure.Identity +{ + internal static class ScopeUtilities + { + private const string DefaultSuffix = "/.defualt"; + + + public static string ScopesToResource(string[] scopes) + { + if (scopes == null) throw new ArgumentNullException(nameof(scopes)); + + if (scopes.Length != 1) throw new ArgumentException("To convert to a resource string the specified array must be exactly length 1", nameof(scopes)); + + if (!scopes[0].EndsWith(DefaultSuffix)) + { + return scopes[0]; + } + + return scopes[0].Remove(scopes[0].LastIndexOf(DefaultSuffix)); + } + + public static string[] ResourceToScopes(string resource) + { + return new string[] { resource + "/.default" }; + } + + } +} diff --git a/sdk/identity/Azure.Identity/tests/Azure.Identity.Tests.csproj b/sdk/identity/Azure.Identity/tests/Azure.Identity.Tests.csproj index 4b94e4da42847..28b93d2cf25b5 100644 --- a/sdk/identity/Azure.Identity/tests/Azure.Identity.Tests.csproj +++ b/sdk/identity/Azure.Identity/tests/Azure.Identity.Tests.csproj @@ -6,11 +6,16 @@ + + - - + + + + + diff --git a/sdk/identity/Azure.Identity/tests/AzureCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCredentialTests.cs index 55031afc70d22..d556497d62d33 100644 --- a/sdk/identity/Azure.Identity/tests/AzureCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/AzureCredentialTests.cs @@ -3,7 +3,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Xunit; +using NUnit.Framework; namespace Azure.Identity.Tests { @@ -38,7 +38,7 @@ protected override async Task GetTokenCoreAsync(string[] scopes, Ca } } - [Fact] + [Test] public async Task RefreshLogicDefaultAsync() { TimeSpan refreshBuffer = new IdentityClientOptions().RefreshBuffer; @@ -56,9 +56,9 @@ public async Task RefreshLogicDefaultAsync() await cred.GetTokenAsync(new string[] { "mockscope" }); } - Assert.Equal(2, refreshCred1.AuthCount); - Assert.Equal(2, refreshCred2.AuthCount); - Assert.Equal(1, notRefreshCred1.AuthCount); + Assert.AreEqual(2, refreshCred1.AuthCount); + Assert.AreEqual(2, refreshCred2.AuthCount); + Assert.AreEqual(1, notRefreshCred1.AuthCount); } } } diff --git a/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs b/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs index 6ed747d09aaee..e4d365ea678d0 100644 --- a/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs +++ b/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Reflection; using System.Text; -using Xunit; +using NUnit.Framework; namespace Azure.Identity.Tests { @@ -15,16 +15,11 @@ public static TokenCredential _credential(this EnvironmentCredential provider) } } - [CollectionDefinition("EnvironmentTests", DisableParallelization = true)] - public class EnvironmentTestsCollection - { - } - - [Collection("EnvironmentTests")] public class EnvironmentCredentialProviderTests { - [Fact] + [NonParallelizable] + [Test] public void CredentialConstruction() { string clientIdBackup = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); @@ -45,11 +40,11 @@ public void CredentialConstruction() Assert.NotNull(cred); - Assert.Equal("mockclientid", cred.ClientId); + Assert.AreEqual("mockclientid", cred.ClientId); - Assert.Equal("mocktenantid", cred.TenantId); + Assert.AreEqual("mocktenantid", cred.TenantId); - Assert.Equal("mockclientsecret", cred.ClientSecret); + Assert.AreEqual("mockclientsecret", cred.ClientSecret); } finally { diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockIdentityClient.cs b/sdk/identity/Azure.Identity/tests/Mock/MockIdentityClient.cs new file mode 100644 index 0000000000000..8ff756e559f1e --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/Mock/MockIdentityClient.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Identity.Tests.Mock +{ + internal class MockIdentityClient : IdentityClient + { + private static AccessToken ExpiredTokenFactory(string[] scopes, string tenantId, string clientId, string clientSecret, CancellationToken cancellationToken) + { + return CreateAccessToken(scopes, tenantId, clientId, clientSecret, DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); + } + + private static AccessToken LiveTokenFactory(string[] scopes, string tenantId, string clientId, string clientSecret, CancellationToken cancellationToken) + { + return CreateAccessToken(scopes, tenantId, clientId, clientSecret, DateTimeOffset.UtcNow + TimeSpan.FromHours(1)); + } + + private static AccessToken CreateAccessToken(string[] scopes, string tenantId, string clientId, string clientSecret, DateTimeOffset expires) + { + MockToken token = CreateMockToken(scopes, tenantId, clientId, clientSecret); + + return new AccessToken(token.ToString(), expires); + } + + private static MockToken CreateMockToken(string[] scopes, string tenantId, string clientId, string clientSecret) + { + return new MockToken().WithField("scopes", string.Join("+", scopes)).WithField("tenantId", tenantId).WithField("clientId", clientId).WithField("clientSecret", clientSecret); + } + + public static MockIdentityClient ExpiredTokenClient { get; } = new MockIdentityClient(ExpiredTokenFactory); + + public static MockIdentityClient LiveTokenClient { get; } = new MockIdentityClient(LiveTokenFactory); + + private Func _tokenFactory; + + public MockIdentityClient() + : this(LiveTokenFactory) + { + + } + + public MockIdentityClient(AccessToken token) + : this(() => token) + { + } + + public MockIdentityClient(Func tokenFactory) + : this((scopes, tenantId, clientId, clientSecret, cancellationToken) => tokenFactory()) + { + } + + public MockIdentityClient(Func tokenFactory) + { + _tokenFactory = tokenFactory; + } + + public override AccessToken Authenticate(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) + { + return CreateToken(scopes, clientId: clientId, tenantId: tenantId, clientSecret: clientSecret, cancellationToken: cancellationToken); + } + + public async override Task AuthenticateAsync(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default) + { + return await CreateTokenAsync(scopes, clientId: clientId, tenantId: tenantId, clientSecret: clientSecret, cancellationToken: cancellationToken); + } + public override AccessToken AuthenticateManagedIdentity(string[] scopes, string clientId = null, CancellationToken cancellationToken = default) + { + return CreateToken(scopes, clientId: clientId, cancellationToken: cancellationToken); + } + + public async override Task AuthenticateManagedIdentityAsync(string[] scopes, string clientId = null, CancellationToken cancellationToken = default) + { + + return await CreateTokenAsync(scopes, clientId: clientId, cancellationToken: cancellationToken); + } + + private async Task CreateTokenAsync(string[] scopes, string tenantId = default, string clientId = default, string clientSecret = default, CancellationToken cancellationToken = default) + { + if (cancellationToken != default) + { + await Task.Delay(1000, cancellationToken); + } + + return _tokenFactory(scopes, tenantId, clientId, clientSecret, cancellationToken); + } + + private AccessToken CreateToken(string[] scopes, string tenantId = default, string clientId = default, string clientSecret = default, CancellationToken cancellationToken = default) + { + if (cancellationToken != default) + { + Task.Delay(1000, cancellationToken).GetAwaiter().GetResult(); + } + + return _tokenFactory(scopes, tenantId, clientId, clientSecret, cancellationToken); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockManagedIdentityCredentialTests.cs b/sdk/identity/Azure.Identity/tests/Mock/MockManagedIdentityCredentialTests.cs new file mode 100644 index 0000000000000..9a5b4510b3713 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/Mock/MockManagedIdentityCredentialTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using Azure.Core.Testing; + +namespace Azure.Identity.Tests.Mock +{ + public class MockManagedIdentityCredentialTests + { + [Test] + public async Task TokenCacheRefresh() + { + // ensure expired tokens are refreshed + var expired = new ManagedIdentityCredential() { Client = MockIdentityClient.ExpiredTokenClient }; + + HashSet tokens = new HashSet(); + + for (int i = 0; i < 100; i++) + { + Assert.IsTrue(tokens.Add(await expired.GetTokenAsync(MockScopes.Default)), "token failed to refresh"); + } + + // ensure non expired tokens are not refeshed + var live = new ManagedIdentityCredential() { Client = MockIdentityClient.LiveTokenClient }; + + tokens.Clear(); + + tokens.Add(await live.GetTokenAsync(MockScopes.Default)); + + for (int i = 0; i < 100; i++) + { + Assert.IsFalse(tokens.Add(await live.GetTokenAsync(MockScopes.Default))); + } + } + + [Test] + public async Task CancellationTokenHonoredAsync() + { + var credential = new ManagedIdentityCredential() { Client = new MockIdentityClient() }; + + var cancellation = new CancellationTokenSource(); + + ValueTask getTokenComplete = credential.GetTokenAsync(MockScopes.Default, cancellation.Token); + + cancellation.Cancel(); + + Assert.ThrowsAsync(async () => await getTokenComplete, "failed to cancel GetToken call"); + + await Task.CompletedTask; + } + + [Test] + public async Task ScopesHonoredAsync() + { + var credential = new ManagedIdentityCredential() { Client = new MockIdentityClient() }; + + string defaultScopeToken = await credential.GetTokenAsync(MockScopes.Default); + + Assert.IsTrue(new MockToken(defaultScopeToken).HasField("scopes", MockScopes.Default.ToString())); + } + + [Test] + public async Task VerifyMSIRequest() + { + var response = new MockResponse(200); + + var expectedToken = "mock-msi-access-token"; + + response.SetContent($"{{ \"access_token\": \"{expectedToken}\", \"expires_in\": 3600 }}"); + + var mockTransport = new MockTransport(response); + + var options = new IdentityClientOptions() { Transport = mockTransport }; + + var credential = new ManagedIdentityCredential(options: options); + + string actualToken = await credential.GetTokenAsync(MockScopes.Default); + + Assert.AreEqual(expectedToken, actualToken); + + MockRequest request = mockTransport.SingleRequest; + + string query = request.UriBuilder.Query; + + Assert.IsTrue(query.Contains("api-version=2018-02-01")); + + Assert.IsTrue(query.Contains($"resource={Uri.EscapeDataString(ScopeUtilities.ScopesToResource(MockScopes.Default))}")); + + Assert.IsTrue(request.Headers.TryGetValue("Metadata", out string metadataValue)); + + Assert.AreEqual("true", metadataValue); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockScopes.cs b/sdk/identity/Azure.Identity/tests/Mock/MockScopes.cs new file mode 100644 index 0000000000000..2f65957b5d898 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/Mock/MockScopes.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Azure.Identity.Tests.Mock +{ + class MockScopes + { + private string[] _scopes; + + private MockScopes(string[] scopes) + { + _scopes = scopes; + } + + public static MockScopes Default = new MockScopes(new string[] { "https://default.mock.auth.scope/,defualt" }); + + public static MockScopes Alternate = new MockScopes(new string[] { "https://alternate.mock.auth.scope/,defualt" }); + + public override string ToString() + { + return ToString(_scopes); + } + + public static string ToString(string[] scopes) + { + return string.Join("+", scopes); + } + + public static implicit operator string[](MockScopes scopes) + { + return scopes._scopes; + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/Mock/MockToken.cs b/sdk/identity/Azure.Identity/tests/Mock/MockToken.cs new file mode 100644 index 0000000000000..ce27fd99ff115 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/Mock/MockToken.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Azure.Identity.Tests.Mock +{ + class MockToken + { + private StringBuilder _tokenBuilder; + private string _token; + + public MockToken() + { + _tokenBuilder = new StringBuilder(Guid.NewGuid().ToString() + ";"); + } + + public MockToken(string token) + { + _token = token; + _tokenBuilder = new StringBuilder(token); + } + + public MockToken WithField(string name, string value) + { + if (!string.IsNullOrEmpty(value)) + { + _tokenBuilder.Append($"{name}={value};"); + } + + + return this; + } + + public bool HasField(string name, string value) + { + return Token.Contains($"{name}={value};"); + } + + + public override string ToString() + { + return Token; + } + + public string Token + { + get + { + if (_token == null || _token.Length != _tokenBuilder.Length) + { + _token = _tokenBuilder.ToString(); + } + + return _token; + } + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/TokenCredentialProviderTests.cs b/sdk/identity/Azure.Identity/tests/TokenCredentialProviderTests.cs index 39c9ae3cd40a7..8e1b736f4695d 100644 --- a/sdk/identity/Azure.Identity/tests/TokenCredentialProviderTests.cs +++ b/sdk/identity/Azure.Identity/tests/TokenCredentialProviderTests.cs @@ -1,5 +1,5 @@ using System; -using Xunit; +using NUnit.Framework; using Azure.Core; using System.Threading; using System.Threading.Tasks; @@ -50,7 +50,7 @@ public override ValueTask GetTokenAsync(string[] scopes, CancellationTok } } - [Fact] + [Test] public void CtorInvalidInput() { Assert.Throws(() => new AggregateCredential(null)); @@ -62,7 +62,7 @@ public void CtorInvalidInput() Assert.Throws(() => new AggregateCredential(new TokenCredential[] { })); } - [Fact] + [Test] public async Task CredentialSequenceValid() { var cred1 = new SimpleMockTokenCredential("scopeA", "tokenA"); @@ -71,13 +71,13 @@ public async Task CredentialSequenceValid() var cred4 = new SimpleMockTokenCredential("scopeC", "tokenC"); var provider = new AggregateCredential(cred1, cred2, cred3, cred4); - Assert.Equal("tokenA", await provider.GetTokenAsync(new string[] { "scopeA" })); - Assert.Equal("tokenB", await provider.GetTokenAsync(new string[] { "scopeB" })); - Assert.Equal("tokenC", await provider.GetTokenAsync(new string[] { "scopeC" })); - await Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "scopeD" })); + Assert.AreEqual("tokenA", await provider.GetTokenAsync(new string[] { "scopeA" })); + Assert.AreEqual("tokenB", await provider.GetTokenAsync(new string[] { "scopeB" })); + Assert.AreEqual("tokenC", await provider.GetTokenAsync(new string[] { "scopeC" })); + Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "scopeD" })); } - [Fact] + [Test] public async Task CredentialThrows() { var cred1 = new SimpleMockTokenCredential("scopeA", "tokenA"); @@ -85,9 +85,9 @@ public async Task CredentialThrows() var cred3 = new SimpleMockTokenCredential("scopeB", "tokenB"); var provider = new AggregateCredential(cred1, cred2, cred3); - Assert.Equal("tokenA", await provider.GetTokenAsync(new string[] { "scopeA" })); - await Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "ScopeB" })); - await Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "ScopeC" })); + Assert.AreEqual("tokenA", await provider.GetTokenAsync(new string[] { "scopeA" })); + Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "ScopeB" })); + Assert.ThrowsAsync(async () => await provider.GetTokenAsync(new string[] { "ScopeC" })); } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.Keyvault.Keys.csproj b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.Keyvault.Keys.csproj index 9c18cd4ec65e5..5bdca66153cf2 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.Keyvault.Keys.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.Keyvault.Keys.csproj @@ -21,6 +21,6 @@ - + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj index c945e156f7bcc..915a7254cbd84 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/Azure.Security.KeyVault.Secrets.Tests.csproj b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/Azure.Security.KeyVault.Secrets.Tests.csproj index 5f53e4b1beacc..154189330c9e9 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/Azure.Security.KeyVault.Secrets.Tests.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/Azure.Security.KeyVault.Secrets.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretsTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretsTests.cs index 09e11de52e78e..4f879ccf5fb18 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretsTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretsTests.cs @@ -59,7 +59,7 @@ public async Task CrudBasic() [Test] public async Task CrudWithExtendedProps() { - var exp = new DateTime(637027248124480000, DateTimeKind.Utc); + var exp = new DateTimeOffset(new DateTime(637027248124480000, DateTimeKind.Utc)); var nbf = exp.AddDays(-30); var client = GetClient();