Skip to content

Commit 487978c

Browse files
authored
DAC CredentialUnavailableException attempts to parse the message content (Azure#21827)
1 parent 3a91b26 commit 487978c

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ protected override async ValueTask<AccessToken> HandleResponseAsync(bool async,
111111
.CreateRequestFailedMessageAsync(response, IdentityUnavailableError, null, null, async)
112112
.ConfigureAwait(false);
113113

114+
var errorContentMessage = await GetMessageFromResponse(response, async, cancellationToken).ConfigureAwait(false);
115+
if (errorContentMessage != null)
116+
{
117+
message = message + Environment.NewLine + errorContentMessage;
118+
}
114119
Interlocked.CompareExchange(ref _identityUnavailableErrorMessage, message, null);
115120
throw new CredentialUnavailableException(message);
116121
}

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,49 @@ public virtual async ValueTask<AccessToken> AuthenticateAsync(bool async, TokenR
4242

4343
protected virtual async ValueTask<AccessToken> HandleResponseAsync(bool async, TokenRequestContext context, Response response, CancellationToken cancellationToken)
4444
{
45+
using JsonDocument json = async
46+
? await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false)
47+
: JsonDocument.Parse(response.ContentStream);
4548
if (response.Status == 200)
4649
{
47-
using JsonDocument json = async
48-
? await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false)
49-
: JsonDocument.Parse(response.ContentStream);
50-
5150
return GetTokenFromResponse(json.RootElement);
5251
}
5352

53+
string message = GetMessageFromResponse(json.RootElement);
5454
throw async
55-
? await Pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false)
56-
: Pipeline.Diagnostics.CreateRequestFailedException(response);
55+
? await Pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response, message).ConfigureAwait(false)
56+
: Pipeline.Diagnostics.CreateRequestFailedException(response, message);
5757
}
5858

5959
protected abstract Request CreateRequest(string[] scopes);
6060

61+
protected static async Task<string> GetMessageFromResponse(Response response, bool async, CancellationToken cancellationToken)
62+
{
63+
if (response?.ContentStream == null)
64+
{
65+
return null;
66+
}
67+
response.ContentStream.Position = 0;
68+
using JsonDocument json = async
69+
? await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false)
70+
: JsonDocument.Parse(response.ContentStream);
71+
72+
return GetMessageFromResponse(json.RootElement);
73+
}
74+
75+
protected static string GetMessageFromResponse(in JsonElement root)
76+
{
77+
// Parse the error, if possible
78+
foreach (var prop in root.EnumerateObject())
79+
{
80+
if (prop.Name == "Message")
81+
{
82+
return prop.Value.GetString();
83+
}
84+
}
85+
return null;
86+
}
87+
6188
private static AccessToken GetTokenFromResponse(in JsonElement root)
6289
{
6390
string accessToken = null;

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ public async Task VerifyImdsRequestWithClientIdMockAsync()
5151
}
5252
}
5353

54+
[NonParallelizable]
55+
[Test]
56+
public void VerifyImdsRequestFailurePopulatesExceptionMessage()
57+
{
58+
using (new TestEnvVar(new() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null } }))
59+
{
60+
var expectedMessage = "No MSI found for specified ClientId/ResourceId.";
61+
var response = CreateErrorMockResponse(400, expectedMessage);
62+
var mockTransport = new MockTransport(response);
63+
var options = new TokenCredentialOptions() { Transport = mockTransport };
64+
var pipeline = CredentialPipeline.GetInstance(options);
65+
66+
var client = new MockManagedIdentityClient(pipeline, "mock-client-id") { ManagedIdentitySourceFactory = () => new ImdsManagedIdentitySource(pipeline, "mock-client-id") };
67+
68+
ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(client));
69+
70+
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
71+
Assert.That(ex.Message, Does.Contain(expectedMessage));
72+
}
73+
}
74+
5475
[NonParallelizable]
5576
[Test]
5677
[TestCase(400)]
@@ -70,7 +91,7 @@ public void VerifyImdsRequestHandlesFailedRequestWithCredentialUnavailableExcept
7091

7192
var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));
7293

73-
Assert.That(ex.Message.Contains(ImdsManagedIdentitySource.IdentityUnavailableError));
94+
Assert.That(ex.Message, Does.Contain(ImdsManagedIdentitySource.IdentityUnavailableError));
7495
}
7596
}
7697

@@ -314,5 +335,12 @@ private MockResponse CreateMockResponse(int responseCode, string token)
314335
response.SetContent($"{{ \"access_token\": \"{token}\", \"expires_on\": \"3600\" }}");
315336
return response;
316337
}
338+
339+
private MockResponse CreateErrorMockResponse(int responseCode, string message)
340+
{
341+
var response = new MockResponse(responseCode);
342+
response.SetContent($"{{\"StatusCode\":400,\"Message\":\"{message}\",\"CorrelationId\":\"f3c9aec0-7fa2-4184-ad0f-0c68ce5fc748\"}}");
343+
return response;
344+
}
317345
}
318346
}

0 commit comments

Comments
 (0)