Skip to content

Commit fc1a231

Browse files
authored
ManagedIdentityCredential honors CancellationTokens (Azure#47171)
1 parent 4d42d7b commit fc1a231

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

sdk/identity/Azure.Identity/CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
### Breaking Changes
88

99
### Bugs Fixed
10-
- Fixed an issue where setting `DefaultAzureCredentialOptions.TenantId` twice throws an `InvalidOperationException`. ([#47035](https://github.com/Azure/azure-sdk-for-net/issues/47035))
11-
- Fixed an issue where some credentials in `DefaultAzureCredential` would not fall through to the next credential in the chain under certain exception conditions.
10+
11+
- Fixed an issue where setting `DefaultAzureCredentialOptions.TenantId` twice throws an `InvalidOperationException` ([#47035](https://github.com/Azure/azure-sdk-for-net/issues/47035))
12+
- Fixed an issue where `ManagedIdentityCredential` does not honor the `CancellationToken` passed to `GetToken` and `GetTokenAsync`. ([#47156](https://github.com/Azure/azure-sdk-for-net/issues/47156))
13+
- Fixed an issue where some credentials in `DefaultAzureCredential` would not fall through to the next credential in the chain under certain exception conditions.
1214

1315
### Other Changes
1416

sdk/identity/Azure.Identity/src/MsalManagedIdentityClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenForManagedIdent
9999
}
100100
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult().
101101
return async ?
102-
await builder.ExecuteAsync().ConfigureAwait(false) :
103-
builder.ExecuteAsync().GetAwaiter().GetResult();
102+
await builder.ExecuteAsync(cancellationToken).ConfigureAwait(false) :
103+
builder.ExecuteAsync(cancellationToken).GetAwaiter().GetResult();
104104
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult().
105105
}
106106
}

sdk/identity/Azure.Identity/tests/ImdsManagedIdentitySourceTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Text;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Azure.Core;
1011
using Azure.Core.Pipeline;
@@ -174,6 +175,64 @@ public void ManagedIdentityCredentialUsesDefaultTimeoutAndRetries()
174175
CollectionAssert.AreEqual(expectedTimeouts, networkTimeouts);
175176
}
176177

178+
[Test]
179+
public void ManagedIdentityCredentialRetryBehaviorIsOverriddenWithOptions()
180+
{
181+
int callCount = 0;
182+
List<TimeSpan?> networkTimeouts = new();
183+
184+
var mockTransport = MockTransport.FromMessageCallback(msg =>
185+
{
186+
callCount++;
187+
networkTimeouts.Add(msg.NetworkTimeout);
188+
Assert.IsTrue(msg.Request.Headers.TryGetValue(ImdsManagedIdentitySource.metadataHeaderName, out _));
189+
return CreateMockResponse(500, "Error").WithHeader("Content-Type", "application/json");
190+
});
191+
192+
var options = new TokenCredentialOptions()
193+
{
194+
Transport = mockTransport,
195+
RetryPolicy = new RetryPolicy(1, DelayStrategy.CreateFixedDelayStrategy(TimeSpan.Zero))
196+
};
197+
options.Retry.MaxDelay = TimeSpan.Zero;
198+
199+
var cred = new ManagedIdentityCredential(
200+
"testCLientId", options);
201+
202+
Assert.ThrowsAsync<AuthenticationFailedException>(async () => await cred.GetTokenAsync(new(new[] { "test" })));
203+
204+
var expectedTimeouts = new TimeSpan?[] { null, null };
205+
CollectionAssert.AreEqual(expectedTimeouts, networkTimeouts);
206+
}
207+
208+
[Test]
209+
public void ManagedIdentityCredentialRespectsCancellationToken()
210+
{
211+
int callCount = 0;
212+
213+
var mockTransport = MockTransport.FromMessageCallback(msg =>
214+
{
215+
Task.Delay(1000).GetAwaiter().GetResult();
216+
callCount++;
217+
return CreateMockResponse(500, "Error").WithHeader("Content-Type", "application/json");
218+
});
219+
220+
var options = new TokenCredentialOptions() { Transport = mockTransport };
221+
options.Retry.MaxDelay = TimeSpan.FromSeconds(1);
222+
223+
var cred = new ManagedIdentityCredential(
224+
"testCLientId", options);
225+
226+
var cts = new CancellationTokenSource();
227+
cts.CancelAfter(TimeSpan.Zero);
228+
var ex = Assert.CatchAsync(async () => await cred.GetTokenAsync(new(new[] { "test" }), cts.Token));
229+
Assert.IsTrue(ex is TaskCanceledException || ex is OperationCanceledException, "Expected TaskCanceledException or OperationCanceledException but got " + ex.GetType().ToString());
230+
231+
// Default number of retries is 5, so we should just ensure we have less than that.
232+
// Timing on some platforms makes this test somewhat non-deterministic, so we just ensure we have less than 2 calls.
233+
Assert.Less(callCount, 2);
234+
}
235+
177236
private MockResponse CreateMockResponse(int responseCode, string token)
178237
{
179238
var response = new MockResponse(responseCode);

0 commit comments

Comments
 (0)