diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationRecordMatcher.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationRecordMatcher.cs index 9f9dd4d3e0f0..52eabd448da6 100644 --- a/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationRecordMatcher.cs +++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/tests/ConfigurationRecordMatcher.cs @@ -25,8 +25,8 @@ public ConfigurationRecordMatcher(RecordedTestSanitizer sanitizer) : base(saniti protected override bool IsBodyEquivalent(RecordEntry record, RecordEntry otherRecord) { - byte[] body = record.ResponseBody ?? Array.Empty(); - byte[] otherBody = record.ResponseBody ?? Array.Empty(); + byte[] body = record.Response.Body ?? Array.Empty(); + byte[] otherBody = record.Response.Body ?? Array.Empty(); if (body.SequenceEqual(otherBody)) return true; diff --git a/sdk/core/Azure.Core/tests/RecordSessionTests.cs b/sdk/core/Azure.Core/tests/RecordSessionTests.cs index 79ea229ce856..014bf0620272 100644 --- a/sdk/core/Azure.Core/tests/RecordSessionTests.cs +++ b/sdk/core/Azure.Core/tests/RecordSessionTests.cs @@ -29,16 +29,16 @@ public void CanRoundtripSessionRecord(string body, string contentType) session.Variables["b"] = "value b"; RecordEntry recordEntry = new RecordEntry(); - recordEntry.RequestHeaders.Add("Content-Type", new[] { contentType }); - recordEntry.RequestHeaders.Add("Other-Header", new[] { "multi", "value" }); - recordEntry.RequestBody = bodyBytes; + recordEntry.Request.Headers.Add("Content-Type", new[] { contentType }); + recordEntry.Request.Headers.Add("Other-Header", new[] { "multi", "value" }); + recordEntry.Request.Body = bodyBytes; recordEntry.RequestUri = "url"; recordEntry.RequestMethod = RequestMethod.Delete; - recordEntry.ResponseHeaders.Add("Content-Type", new[] { contentType }); - recordEntry.ResponseHeaders.Add("Other-Response-Header", new[] { "multi", "value" }); + recordEntry.Response.Headers.Add("Content-Type", new[] { contentType }); + recordEntry.Response.Headers.Add("Other-Response-Header", new[] { "multi", "value" }); - recordEntry.ResponseBody = bodyBytes; + recordEntry.Response.Body = bodyBytes; recordEntry.StatusCode = 202; session.Entries.Add(recordEntry); @@ -62,14 +62,14 @@ public void CanRoundtripSessionRecord(string body, string contentType) Assert.AreEqual("url", recordEntry.RequestUri); Assert.AreEqual(202, recordEntry.StatusCode); - CollectionAssert.AreEqual(new[] { contentType }, deserializedRecord.RequestHeaders["content-type"]); - CollectionAssert.AreEqual(new[] { "multi", "value" }, deserializedRecord.RequestHeaders["other-header"]); + CollectionAssert.AreEqual(new[] { contentType }, deserializedRecord.Request.Headers["content-type"]); + CollectionAssert.AreEqual(new[] { "multi", "value" }, deserializedRecord.Request.Headers["other-header"]); - CollectionAssert.AreEqual(new[] { contentType }, deserializedRecord.ResponseHeaders["content-type"]); - CollectionAssert.AreEqual(new[] { "multi", "value" }, deserializedRecord.ResponseHeaders["other-response-header"]); + CollectionAssert.AreEqual(new[] { contentType }, deserializedRecord.Response.Headers["content-type"]); + CollectionAssert.AreEqual(new[] { "multi", "value" }, deserializedRecord.Response.Headers["other-response-header"]); - CollectionAssert.AreEqual(bodyBytes, deserializedRecord.RequestBody); - CollectionAssert.AreEqual(bodyBytes, deserializedRecord.ResponseBody); + CollectionAssert.AreEqual(bodyBytes, deserializedRecord.Request.Body); + CollectionAssert.AreEqual(bodyBytes, deserializedRecord.Response.Body); } [Test] @@ -91,10 +91,13 @@ public void RecordMatcherThrowsExceptionsWithDetails() { RequestUri = "http://remote-host", RequestMethod = RequestMethod.Put, - RequestHeaders = + Request = { - { "Some-Header", new[] { "Non-Random value"}}, - { "Extra-Header", new[] { "Extra-Value" }} + Headers = + { + { "Some-Header", new[] { "Non-Random value"}}, + { "Extra-Header", new[] { "Extra-Value" }} + } } } }; @@ -130,9 +133,12 @@ public void RecordMatcherIgnoresIgnoredHeaders() { RequestUri = "http://localhost", RequestMethod = RequestMethod.Put, - RequestHeaders = + Request = { - { "Request-Id", new[] { "Non-Random value"}}, + Headers = + { + { "Request-Id", new[] { "Non-Random value"}}, + } } } }; diff --git a/sdk/core/Azure.Core/tests/TestFramework/PlaybackTransport.cs b/sdk/core/Azure.Core/tests/TestFramework/PlaybackTransport.cs index 2c2538387cfa..c8a315f68564 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/PlaybackTransport.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/PlaybackTransport.cs @@ -77,12 +77,12 @@ public Response GetResponse(RecordEntry recordEntry) { var response = new MockResponse(recordEntry.StatusCode); // TODO: Use non-seekable stream - if (recordEntry.ResponseBody != null) + if (recordEntry.Response.Body != null) { - response.ContentStream = new MemoryStream(recordEntry.ResponseBody); + response.ContentStream = new MemoryStream(recordEntry.Response.Body); } - foreach (KeyValuePair responseHeader in recordEntry.ResponseHeaders) + foreach (KeyValuePair responseHeader in recordEntry.Response.Headers) { foreach (string value in responseHeader.Value) { diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordEntry.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordEntry.cs index f53f8c1cbd00..a4bf2d1bbaf2 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/RecordEntry.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordEntry.cs @@ -11,17 +11,13 @@ namespace Azure.Core.Testing { public class RecordEntry { - public string RequestUri { get; set; } - - public RequestMethod RequestMethod { get; set; } - - public byte[] RequestBody { get; set; } + public RecordEntryMessage Request { get; } = new RecordEntryMessage(); - public SortedDictionary RequestHeaders { get; set; } = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); + public RecordEntryMessage Response { get; } = new RecordEntryMessage(); - public SortedDictionary ResponseHeaders { get; set; } = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); + public string RequestUri { get; set; } - public byte[] ResponseBody { get; set; } + public RequestMethod RequestMethod { get; set; } public int StatusCode { get; set; } @@ -39,14 +35,14 @@ public static RecordEntry Deserialize(JsonElement element) record.RequestUri = property.GetString(); } - if (element.TryGetProperty(nameof(RequestHeaders), out property)) + if (element.TryGetProperty("RequestHeaders", out property)) { - DeserializeHeaders(record.RequestHeaders, property); + DeserializeHeaders(record.Request.Headers, property); } - if (element.TryGetProperty(nameof(RequestBody), out property)) + if (element.TryGetProperty("RequestBody", out property)) { - record.RequestBody = DeserializeBody(record.RequestHeaders, property); + record.Request.Body = DeserializeBody(record.Request.Headers, property); } if (element.TryGetProperty(nameof(StatusCode), out property) && @@ -55,14 +51,14 @@ public static RecordEntry Deserialize(JsonElement element) record.StatusCode = statusCode; } - if (element.TryGetProperty(nameof(ResponseHeaders), out property)) + if (element.TryGetProperty("ResponseHeaders", out property)) { - DeserializeHeaders(record.ResponseHeaders, property); + DeserializeHeaders(record.Response.Headers, property); } - if (element.TryGetProperty(nameof(ResponseBody), out property)) + if (element.TryGetProperty("ResponseBody", out property)) { - record.ResponseBody = DeserializeBody(record.ResponseHeaders, property); + record.Response.Body = DeserializeBody(record.Response.Headers, property); } return record; @@ -137,19 +133,19 @@ public void Serialize(Utf8JsonWriter jsonWriter) jsonWriter.WriteString(nameof(RequestUri), RequestUri); jsonWriter.WriteString(nameof(RequestMethod), RequestMethod.Method); - jsonWriter.WriteStartObject(nameof(RequestHeaders)); - SerializeHeaders(jsonWriter, RequestHeaders); + jsonWriter.WriteStartObject("RequestHeaders"); + SerializeHeaders(jsonWriter, Request.Headers); jsonWriter.WriteEndObject(); - SerializeBody(jsonWriter, nameof(RequestBody), RequestBody, RequestHeaders); + SerializeBody(jsonWriter, "RequestBody", Request.Body, Request.Headers); jsonWriter.WriteNumber(nameof(StatusCode), StatusCode); - jsonWriter.WriteStartObject(nameof(ResponseHeaders)); - SerializeHeaders(jsonWriter, ResponseHeaders); + jsonWriter.WriteStartObject("ResponseHeaders"); + SerializeHeaders(jsonWriter, Response.Headers); jsonWriter.WriteEndObject(); - SerializeBody(jsonWriter, nameof(ResponseBody), ResponseBody, ResponseHeaders); + SerializeBody(jsonWriter, "ResponseBody", Response.Body, Response.Headers); jsonWriter.WriteEndObject(); } @@ -250,7 +246,7 @@ private void SerializeHeaders(Utf8JsonWriter jsonWriter, IDictionary requestHeaders, out string contentType) + public static bool TryGetContentType(IDictionary requestHeaders, out string contentType) { contentType = null; if (requestHeaders.TryGetValue("Content-Type", out var contentTypes) && @@ -262,71 +258,11 @@ private static bool TryGetContentType(IDictionary requestHeade return false; } - private static bool IsTextContentType(IDictionary requestHeaders, out Encoding encoding) + public static bool IsTextContentType(IDictionary requestHeaders, out Encoding encoding) { encoding = null; return TryGetContentType(requestHeaders, out string contentType) && TestFrameworkContentTypeUtilities.TryGetTextEncoding(contentType, out encoding); } - - public void Sanitize(RecordedTestSanitizer sanitizer) - { - RequestUri = sanitizer.SanitizeUri(RequestUri); - if (RequestBody != null) - { - int contentLength = RequestBody.Length; - TryGetContentType(RequestHeaders, out string contentType); - if (IsTextContentType(RequestHeaders, out Encoding encoding)) - { - RequestBody = Encoding.UTF8.GetBytes(sanitizer.SanitizeTextBody(contentType, encoding.GetString(RequestBody))); - } - else - { - RequestBody = sanitizer.SanitizeBody(contentType, RequestBody); - } - UpdateSanitizedContentLength(RequestHeaders, contentLength, RequestBody?.Length ?? 0); - } - - sanitizer.SanitizeHeaders(RequestHeaders); - - if (ResponseBody != null) - { - int contentLength = ResponseBody.Length; - TryGetContentType(ResponseHeaders, out string contentType); - if (IsTextContentType(ResponseHeaders, out Encoding encoding)) - { - ResponseBody = Encoding.UTF8.GetBytes(sanitizer.SanitizeTextBody(contentType, encoding.GetString(ResponseBody))); - } - else - { - ResponseBody = sanitizer.SanitizeBody(contentType, ResponseBody); - } - UpdateSanitizedContentLength(ResponseHeaders, contentLength, ResponseBody?.Length ?? 0); - } - - sanitizer.SanitizeHeaders(ResponseHeaders); - } - - /// - /// Optionally update the Content-Length header if we've sanitized it - /// and the new value is a different length from the original - /// Content-Length header. We don't add a Content-Length header if it - /// wasn't already present. - /// - /// The Request or Response headers - /// THe original Content-Length - /// The sanitized Content-Length - private static void UpdateSanitizedContentLength(IDictionary headers, int originalLength, int sanitizedLength) - { - // Note: If the RequestBody/ResponseBody was set to null by our - // sanitizer, we'll pass 0 as the sanitizedLength and use that as - // our new Content-Length. That's fine for all current scenarios - // (i.e., we never do that), but it's possible we may want to - // remove the Content-Length header in the future. - if (originalLength != sanitizedLength && headers.ContainsKey("Content-Length")) - { - headers["Content-Length"] = new string[] { sanitizedLength.ToString() }; - } - } } } diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordEntryMessage.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordEntryMessage.cs new file mode 100644 index 000000000000..743f1d91934f --- /dev/null +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordEntryMessage.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; +using Azure.Core.Pipeline; + +namespace Azure.Core.Testing +{ + public class RecordEntryMessage + { + public SortedDictionary Headers { get; set; } = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); + + public byte[] Body { get; set; } + + public bool TryGetContentType(out string contentType) + { + contentType = null; + if (Headers.TryGetValue("Content-Type", out var contentTypes) && + contentTypes.Length == 1) + { + contentType = contentTypes[0]; + return true; + } + return false; + } + + public bool IsTextContentType(out Encoding encoding) + { + encoding = null; + return TryGetContentType(out string contentType) && + TestFrameworkContentTypeUtilities.TryGetTextEncoding(contentType, out encoding); + } + + public bool TryGetBodyAsText(out string text) + { + text = null; + + if (IsTextContentType(out Encoding encoding)) + { + text = encoding.GetString(Body); + + return true; + } + + return false; + } + + } +} diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordMatcher.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordMatcher.cs index 1dbeca083767..577423b71d0a 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/RecordMatcher.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordMatcher.cs @@ -84,7 +84,7 @@ public virtual RecordEntry FindMatch(Request request, IList entries score++; } - score += CompareHeaderDictionaries(headers, entry.RequestHeaders, ExcludeHeaders); + score += CompareHeaderDictionaries(headers, entry.Request.Headers, ExcludeHeaders); if (score == 0) { @@ -108,7 +108,7 @@ public virtual bool IsEquivalentRecord(RecordEntry entry, RecordEntry otherEntry protected virtual bool IsEquivalentRequest(RecordEntry entry, RecordEntry otherEntry) => entry.RequestMethod == otherEntry.RequestMethod && IsEquivalentUri(entry.RequestUri, otherEntry.RequestUri) && - CompareHeaderDictionaries(entry.RequestHeaders, otherEntry.RequestHeaders, VolatileHeaders) == 0; + CompareHeaderDictionaries(entry.Request.Headers, otherEntry.Request.Headers, VolatileHeaders) == 0; private static bool AreUrisSame(string entryUri, string otherEntryUri) => // Some versions of .NET behave differently when calling new Uri("...") @@ -121,8 +121,8 @@ protected virtual bool IsEquivalentUri(string entryUri, string otherEntryUri) => protected virtual bool IsEquivalentResponse(RecordEntry entry, RecordEntry otherEntry) { - IEnumerable> entryHeaders = entry.ResponseHeaders.Where(h => !VolatileResponseHeaders.Contains(h.Key)); - IEnumerable> otherEntryHeaders = otherEntry.ResponseHeaders.Where(h => !VolatileResponseHeaders.Contains(h.Key)); + IEnumerable> entryHeaders = entry.Response.Headers.Where(h => !VolatileResponseHeaders.Contains(h.Key)); + IEnumerable> otherEntryHeaders = otherEntry.Response.Headers.Where(h => !VolatileResponseHeaders.Contains(h.Key)); return entry.StatusCode == otherEntry.StatusCode && @@ -132,8 +132,8 @@ protected virtual bool IsEquivalentResponse(RecordEntry entry, RecordEntry other protected virtual bool IsBodyEquivalent(RecordEntry record, RecordEntry otherRecord) { - return (record.ResponseBody ?? Array.Empty()).AsSpan() - .SequenceEqual((otherRecord.ResponseBody ?? Array.Empty())); + return (record.Response.Body ?? Array.Empty()).AsSpan() + .SequenceEqual((otherRecord.Response.Body ?? Array.Empty())); } private string GenerateException(RequestMethod requestMethod, string uri, SortedDictionary headers, RecordEntry bestScoreEntry) @@ -161,7 +161,7 @@ private string GenerateException(RequestMethod requestMethod, string uri, Sorted builder.AppendLine("Header differences:"); - CompareHeaderDictionaries(headers, bestScoreEntry.RequestHeaders, ExcludeHeaders, builder); + CompareHeaderDictionaries(headers, bestScoreEntry.Request.Headers, ExcludeHeaders, builder); return builder.ToString(); } diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordSession.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordSession.cs index ea3d1b74bcd4..37ffcc14a049 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/RecordSession.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordSession.cs @@ -80,7 +80,7 @@ public void Sanitize(RecordedTestSanitizer sanitizer) { foreach (RecordEntry entry in Entries) { - entry.Sanitize(sanitizer); + sanitizer.Sanitize(entry); } } } diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordTransport.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordTransport.cs index ca95c5baf2f7..347e6f14316a 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/RecordTransport.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordTransport.cs @@ -71,10 +71,14 @@ public RecordEntry CreateEntry(Request request, Response response) { RequestUri = request.Uri.ToString(), RequestMethod = request.Method, - RequestHeaders = new SortedDictionary(StringComparer.OrdinalIgnoreCase), - ResponseHeaders = new SortedDictionary(StringComparer.OrdinalIgnoreCase), - RequestBody = ReadToEnd(request.Content), - ResponseBody = ReadToEnd(response), + Request = + { + Body = ReadToEnd(request.Content), + }, + Response = + { + Body = ReadToEnd(response), + }, StatusCode = response.Status }; @@ -82,20 +86,20 @@ public RecordEntry CreateEntry(Request request, Response response) { var gotHeader = request.Headers.TryGetValues(requestHeader.Name, out IEnumerable headerValues); Debug.Assert(gotHeader); - entry.RequestHeaders.Add(requestHeader.Name, headerValues.ToArray()); + entry.Request.Headers.Add(requestHeader.Name, headerValues.ToArray()); } // Make sure we record Content-Length even if it's not set explicitly if (!request.Headers.TryGetValue("Content-Length", out _) && request.Content != null && request.Content.TryComputeLength(out long computedLength)) { - entry.RequestHeaders.Add("Content-Length", new[] { computedLength.ToString(CultureInfo.InvariantCulture) }); + entry.Request.Headers.Add("Content-Length", new[] { computedLength.ToString(CultureInfo.InvariantCulture) }); } foreach (HttpHeader responseHeader in response.Headers) { var gotHeader = response.Headers.TryGetValues(responseHeader.Name, out IEnumerable headerValues); Debug.Assert(gotHeader); - entry.ResponseHeaders.Add(responseHeader.Name, headerValues.ToArray()); + entry.Response.Headers.Add(responseHeader.Name, headerValues.ToArray()); } return entry; diff --git a/sdk/core/Azure.Core/tests/TestFramework/RecordedTestSanitizer.cs b/sdk/core/Azure.Core/tests/TestFramework/RecordedTestSanitizer.cs index c8a732aa7269..4d694681f26a 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/RecordedTestSanitizer.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/RecordedTestSanitizer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Text; namespace Azure.Core.Testing { @@ -41,5 +42,64 @@ public virtual byte[] SanitizeBody(string contentType, byte[] body) public virtual void SanitizeConnectionString(ConnectionString connectionString) { } + + public virtual void SanitizeBody(RecordEntryMessage message) + { + if (message.Body != null) + { + int contentLength = message.Body.Length; + + message.TryGetContentType(out string contentType); + + if (message.TryGetBodyAsText(out string text)) + { + message.Body = Encoding.UTF8.GetBytes(SanitizeTextBody(contentType, text)); + } + else + { + message.Body = SanitizeBody(contentType, message.Body); + } + + UpdateSanitizedContentLength(message.Headers, contentLength, message.Body?.Length ?? 0); + } + + } + + public virtual void Sanitize(RecordEntry entry) + { + entry.RequestUri = SanitizeUri(entry.RequestUri); + + SanitizeHeaders(entry.Request.Headers); + + SanitizeBody(entry.Request); + + SanitizeHeaders(entry.Response.Headers); + + SanitizeBody(entry.Response); + } + + + /// + /// Optionally update the Content-Length header if we've sanitized it + /// and the new value is a different length from the original + /// Content-Length header. We don't add a Content-Length header if it + /// wasn't already present. + /// + /// The Request or Response headers + /// THe original Content-Length + /// The sanitized Content-Length + protected static void UpdateSanitizedContentLength(IDictionary headers, int originalLength, int sanitizedLength) + { + // Note: If the RequestBody/ResponseBody was set to null by our + // sanitizer, we'll pass 0 as the sanitizedLength and use that as + // our new Content-Length. That's fine for all current scenarios + // (i.e., we never do that), but it's possible we may want to + // remove the Content-Length header in the future. + if (originalLength != sanitizedLength && headers.ContainsKey("Content-Length")) + { + headers["Content-Length"] = new string[] { sanitizedLength.ToString() }; + } + } + } } diff --git a/sdk/identity/Azure.Identity/Azure.Identity.sln b/sdk/identity/Azure.Identity/Azure.Identity.sln index c29ea9bc70ae..58c269a2e338 100644 --- a/sdk/identity/Azure.Identity/Azure.Identity.sln +++ b/sdk/identity/Azure.Identity/Azure.Identity.sln @@ -11,6 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Identity.Tests", "tes EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F748B65-7779-4A71-B2EC-9BA8B9526AB0}" ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md README.md = README.md EndProjectSection EndProject diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index bda8232e67a0..1989aa047230 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -1,5 +1,10 @@ # Release History +## Unreleased + +### Fixes and improvements +- Fix `UsernamePasswordCredential` constructor parameter mishandling + ## 1.1.0 ### Fixes and improvements diff --git a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs index cf2290b024fd..cbec45126d41 100644 --- a/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs +++ b/sdk/identity/Azure.Identity/src/UsernamePasswordCredential.cs @@ -40,7 +40,7 @@ protected UsernamePasswordCredential() /// The Azure Active Directory tenant (directory) ID or name. /// The client (application) ID of an App Registration in the tenant. public UsernamePasswordCredential(string username, string password, string tenantId, string clientId) - : this(username, password, clientId, tenantId, (TokenCredentialOptions)null) + : this(username, password, tenantId, clientId, (TokenCredentialOptions)null) { } diff --git a/sdk/identity/Azure.Identity/tests/IdentityRecordedTestSanitizer.cs b/sdk/identity/Azure.Identity/tests/IdentityRecordedTestSanitizer.cs new file mode 100644 index 000000000000..5477f0a49110 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/IdentityRecordedTestSanitizer.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using Azure.Core; +using Azure.Core.Testing; + +namespace Azure.Identity.Tests +{ + public class IdentityRecordedTestSanitizer : RecordedTestSanitizer + { + public override void Sanitize(RecordEntry entry) + { + if (entry.RequestUri.EndsWith("/token")) + { + SanitizeTokenRequest(entry); + SanitizeTokenResponse(entry); + } + + base.Sanitize(entry); + } + + private void SanitizeTokenRequest(RecordEntry entry) + { + entry.Request.Body = Encoding.UTF8.GetBytes("Sanitized"); + + UpdateSanitizedContentLength(entry.Request.Headers, 0, entry.Request.Body.Length); + + } + + private void SanitizeTokenResponse(RecordEntry entry) + { + var originalJson = JsonDocument.Parse(entry.Response.Body).RootElement; + + var writer = new ArrayBufferWriter(entry.Response.Body.Length); + + using var sanitizedJson = new Utf8JsonWriter(writer); + + sanitizedJson.WriteStartObject(); + + foreach (JsonProperty prop in originalJson.EnumerateObject()) + { + + sanitizedJson.WritePropertyName(prop.Name); + + switch (prop.Name) + { + case "refresh_token": + case "access_token": + sanitizedJson.WriteStringValue("Sanitized"); + break; + + default: + prop.Value.WriteTo(sanitizedJson); + break; + } + } + + sanitizedJson.WriteEndObject(); + + sanitizedJson.Flush(); + + entry.Response.Body = writer.WrittenMemory.ToArray(); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLive.json b/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLive.json new file mode 100644 index 000000000000..cdf14ac9c472 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLive.json @@ -0,0 +1,304 @@ +{ + "Entries": [ + { + "RequestUri": "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1\u0026authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2Fc54fac88-3dd3-461f-a7c4-8a368e0340b3%2Foauth2%2Fv2.0%2Fauthorize", + "RequestMethod": "GET", + "RequestHeaders": { + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Request-Id": "00-e0826eec3f870b45917623274c3e8e00-43bd87a4c0b6cb44-00", + "return-client-request-id": "true", + "traceparent": "00-e0826eec3f870b45917623274c3e8e00-43bd87a4c0b6cb44-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "33fd0c163d049f782d2aab8e56b4fcc8", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "max-age=86400, private", + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Content-Length": "980", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:35:59 GMT", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUA; expires=Sun, 05-Jan-2020 00:35:59 GMT; path=/; secure; HttpOnly; SameSite=None", + "esctx=AQABAAAAAACQN9QBRU3jT6bcBQLZNUj7Kz3PvImNtac_HeVg0FC4vM82kK1m8yK7MG1lIO2ymfeQy35q3Badm58-TUMHJXolRtfNb4z1pJ-i_1gmLPBsOHfkuhZ-apRSjTWu8zIkniDNUGkALZbaOwtd_L44p7Ptk7qMDdMVfD-dOZlQOjGc0Tew4aaHmi89pkJ7zcarI70gAA; domain=.login.microsoftonline.com; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; SameSite=None; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "f0401260-424c-43eb-a5bf-03995b6ed301" + }, + "ResponseBody": { + "tenant_discovery_endpoint": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/v2.0/.well-known/openid-configuration", + "api-version": "1.1", + "metadata": [ + { + "preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", + "aliases": [ + "login.microsoftonline.com", + "login.windows.net", + "login.microsoft.com", + "sts.windows.net" + ] + }, + { + "preferred_network": "login.partner.microsoftonline.cn", + "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": [ + "login.partner.microsoftonline.cn", + "login.chinacloudapi.cn" + ] + }, + { + "preferred_network": "login.microsoftonline.de", + "preferred_cache": "login.microsoftonline.de", + "aliases": [ + "login.microsoftonline.de" + ] + }, + { + "preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", + "aliases": [ + "login.microsoftonline.us", + "login.usgovcloudapi.net" + ] + }, + { + "preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", + "aliases": [ + "login-us.microsoftonline.com" + ] + } + ] + } + }, + { + "RequestUri": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/v2.0/.well-known/openid-configuration", + "RequestMethod": "GET", + "RequestHeaders": { + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Request-Id": "00-e0826eec3f870b45917623274c3e8e00-3b6ff1a9a0402f44-00", + "return-client-request-id": "true", + "traceparent": "00-e0826eec3f870b45917623274c3e8e00-3b6ff1a9a0402f44-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "b3bccfb96fca0a30d8f7d6b3c8ab3a55", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "max-age=86400, private", + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Content-Length": "1523", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:35:59 GMT", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUA; expires=Sun, 05-Jan-2020 00:36:00 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "d76a6053-1579-439e-86eb-327de3c7c301" + }, + "ResponseBody": { + "token_endpoint": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/oauth2/v2.0/token", + "token_endpoint_auth_methods_supported": [ + "client_secret_post", + "private_key_jwt", + "client_secret_basic" + ], + "jwks_uri": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/discovery/v2.0/keys", + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "subject_types_supported": [ + "pairwise" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "response_types_supported": [ + "code", + "id_token", + "code id_token", + "id_token token" + ], + "scopes_supported": [ + "openid", + "profile", + "email", + "offline_access" + ], + "issuer": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/v2.0", + "request_uri_parameter_supported": false, + "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", + "authorization_endpoint": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/oauth2/v2.0/authorize", + "http_logout_supported": true, + "frontchannel_logout_supported": true, + "end_session_endpoint": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/oauth2/v2.0/logout", + "claims_supported": [ + "sub", + "iss", + "cloud_instance_name", + "cloud_instance_host_name", + "cloud_graph_host_name", + "msgraph_host", + "aud", + "exp", + "iat", + "auth_time", + "acr", + "nonce", + "preferred_username", + "name", + "tid", + "ver", + "at_hash", + "c_hash", + "email" + ], + "tenant_region_scope": "NA", + "cloud_instance_name": "microsoftonline.com", + "cloud_graph_host_name": "graph.windows.net", + "msgraph_host": "graph.microsoft.com", + "rbac_url": "https://pas.windows.net" + } + }, + { + "RequestUri": "https://login.microsoftonline.com/common/userrealm/tempuser2@azuresdkplayground.onmicrosoft.com?api-version=1.0", + "RequestMethod": "GET", + "RequestHeaders": { + "Request-Id": "00-e0826eec3f870b45917623274c3e8e00-83074f805782fe48-00", + "traceparent": "00-e0826eec3f870b45917623274c3e8e00-83074f805782fe48-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "b556633aa52b6b5609aed00dc86cd1f8", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "Content-Disposition": "inline; filename=userrealm.json", + "Content-Length": "187", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:35:59 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUA; expires=Sun, 05-Jan-2020 00:36:00 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "36bb85fb-f615-422d-8561-49fb1001c501" + }, + "ResponseBody": { + "ver": "1.0", + "account_type": "Managed", + "domain_name": "azuresdkplayground.onmicrosoft.com", + "cloud_instance_name": "microsoftonline.com", + "cloud_audience_urn": "urn:federation:MicrosoftOnline" + } + }, + { + "RequestUri": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/oauth2/v2.0/token", + "RequestMethod": "POST", + "RequestHeaders": { + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Content-Length": "9", + "Request-Id": "00-e0826eec3f870b45917623274c3e8e00-4435b5290fdb2d42-00", + "return-client-request-id": "true", + "traceparent": "00-e0826eec3f870b45917623274c3e8e00-4435b5290fdb2d42-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "c1b89239168aa78edbf25b0c67c5d681", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": "U2FuaXRpemVk", + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "client-request-id": "f47942e3-79c2-4efd-b5d3-e4835b672ddc", + "Content-Length": "3691", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:36:00 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUCkbPf3AQAAAHGZe9UOAAAA; expires=Sun, 05-Jan-2020 00:36:01 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-clitelem": "1,0,0,,", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "e573f2b8-e4f3-429d-b6ba-7a4260bac901" + }, + "ResponseBody": { + "token_type": "Bearer", + "scope": "https://vault.azure.net/user_impersonation https://vault.azure.net/.default", + "expires_in": 3600, + "ext_expires_in": 3600, + "access_token": "Sanitized", + "refresh_token": "Sanitized", + "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzL3YyLjAiLCJpYXQiOjE1NzU1OTIyNjEsIm5iZiI6MTU3NTU5MjI2MSwiZXhwIjoxNTc1NTk2MTYxLCJhaW8iOiJBVFFBeS84TkFBQUFHUVI2YkVoK3FTOENxOHBJVDllZjNhNmNHd0RvcURUYm94ZWFTd2RRSWM5d3U0dkhrVG96Y05WYmNWU1E0ODJXIiwibmFtZSI6IlRlbXAgVXNlciAyIiwib2lkIjoiYzQ4Nzk1NDItODQxZi00MDc0LTk0MTMtZDk4MTM1MWU3MzI4IiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVtcHVzZXIyQGF6dXJlc2RrcGxheWdyb3VuZC5vbm1pY3Jvc29mdC5jb20iLCJzdWIiOiJlQzZ2b2RIMjk0VUhnTThvTTN0MlRqVHZrU0NOMThiTzAyZ2dLZ09mVDljIiwidGlkIjoiYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzIiwidXRpIjoidVBKejVmUGtuVUsydW5wQ1lMckpBUSIsInZlciI6IjIuMCJ9.hPR_MzOKempTBV7s-78uXfl760LalEGEtwQqLEKvg-xDkPKkyj8x1Ig6cUrZcri4v8Uu02Bnw6mEEuhgyW0BDlbETyYIRJN6kVPvQADPWfstUa7lehUjSzZNzaTItU_AHK050Mah50TNelovN0qD7cItNtxAQ62wh-xf8RszJ25NVqd3Jdu45vkvxliTEY5CP4mnJAkKagS7uupS8tnOXF6lCS0MkZetbk4uBBfGj_-MpWISC2ZKYVghptGz2eDU76iFdZps046DsodI-IR3bwnuAWyqer78aLmaNLv_7Qkf1014UwqML8l3AhGVvw1lv_feNukgQzsdGiszej2w0w", + "client_info": "eyJ1aWQiOiJjNDg3OTU0Mi04NDFmLTQwNzQtOTQxMy1kOTgxMzUxZTczMjgiLCJ1dGlkIjoiYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzIn0" + } + } + ], + "Variables": { + "IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_TENANTID": "c54fac88-3dd3-461f-a7c4-8a368e0340b3", + "IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_USERNAME": "tempuser2@azuresdkplayground.onmicrosoft.com", + "RandomSeed": "1640485913" + } +} \ No newline at end of file diff --git a/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLiveAsync.json b/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLiveAsync.json new file mode 100644 index 000000000000..5f1e8d790313 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/SessionRecords/UsernamePasswordCredentialLiveTests/AuthenticateUsernamePasswordLiveAsync.json @@ -0,0 +1,196 @@ +{ + "Entries": [ + { + "RequestUri": "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1\u0026authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2Fc54fac88-3dd3-461f-a7c4-8a368e0340b3%2Foauth2%2Fv2.0%2Fauthorize", + "RequestMethod": "GET", + "RequestHeaders": { + "client-request-id": "8782d97a-0eaa-4a83-81b1-cc6a4af871d5", + "Request-Id": "00-0763ddb813691f4392fc61a454fe3e1c-04250c9ba0b92e43-00", + "return-client-request-id": "true", + "traceparent": "00-0763ddb813691f4392fc61a454fe3e1c-04250c9ba0b92e43-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "04abe87d1e994cd9c8ffaf86d1a5283b", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "max-age=86400, private", + "client-request-id": "8782d97a-0eaa-4a83-81b1-cc6a4af871d5", + "Content-Length": "980", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:36:02 GMT", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUCkbPf3AQAAAHGZe9UOAAAA; expires=Sun, 05-Jan-2020 00:36:02 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "00ce3f26-0f8b-47e3-8b6c-2cb6befbca01" + }, + "ResponseBody": { + "tenant_discovery_endpoint": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/v2.0/.well-known/openid-configuration", + "api-version": "1.1", + "metadata": [ + { + "preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", + "aliases": [ + "login.microsoftonline.com", + "login.windows.net", + "login.microsoft.com", + "sts.windows.net" + ] + }, + { + "preferred_network": "login.partner.microsoftonline.cn", + "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": [ + "login.partner.microsoftonline.cn", + "login.chinacloudapi.cn" + ] + }, + { + "preferred_network": "login.microsoftonline.de", + "preferred_cache": "login.microsoftonline.de", + "aliases": [ + "login.microsoftonline.de" + ] + }, + { + "preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", + "aliases": [ + "login.microsoftonline.us", + "login.usgovcloudapi.net" + ] + }, + { + "preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", + "aliases": [ + "login-us.microsoftonline.com" + ] + } + ] + } + }, + { + "RequestUri": "https://login.microsoftonline.com/common/userrealm/tempuser2@azuresdkplayground.onmicrosoft.com?api-version=1.0", + "RequestMethod": "GET", + "RequestHeaders": { + "Request-Id": "00-0763ddb813691f4392fc61a454fe3e1c-b57ad3f86e624245-00", + "traceparent": "00-0763ddb813691f4392fc61a454fe3e1c-b57ad3f86e624245-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "a6f0ba89f608dba8a1208003edc07d6b", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "Content-Disposition": "inline; filename=userrealm.json", + "Content-Length": "187", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:36:02 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUCkbPf3AQAAAHGZe9UOAAAA; expires=Sun, 05-Jan-2020 00:36:02 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "36bb85fb-f615-422d-8561-49fb3301c501" + }, + "ResponseBody": { + "ver": "1.0", + "account_type": "Managed", + "domain_name": "azuresdkplayground.onmicrosoft.com", + "cloud_instance_name": "microsoftonline.com", + "cloud_audience_urn": "urn:federation:MicrosoftOnline" + } + }, + { + "RequestUri": "https://login.microsoftonline.com/c54fac88-3dd3-461f-a7c4-8a368e0340b3/oauth2/v2.0/token", + "RequestMethod": "POST", + "RequestHeaders": { + "client-request-id": "8782d97a-0eaa-4a83-81b1-cc6a4af871d5", + "Content-Length": "9", + "Request-Id": "00-0763ddb813691f4392fc61a454fe3e1c-78b238704f681140-00", + "return-client-request-id": "true", + "traceparent": "00-0763ddb813691f4392fc61a454fe3e1c-78b238704f681140-00", + "User-Agent": [ + "azsdk-net-Identity/1.2.0-dev.20191205.1\u002B6199776c55f1aca14060674321e2899a61ce01fd", + "(.NET Core 4.6.28008.01; Microsoft Windows 10.0.18363 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.18363 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.1.0.0", + "x-ms-client-request-id": "ffe420794f0581b0ba5b7c2af67b3447", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": "U2FuaXRpemVk", + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "client-request-id": "8782d97a-0eaa-4a83-81b1-cc6a4af871d5", + "Content-Length": "3691", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 06 Dec 2019 00:36:02 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=ApAAL5a_CJxJtxLt4BNMmUCkbPf3AgAAAHGZe9UOAAAA; expires=Sun, 05-Jan-2020 00:36:02 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; SameSite=None; secure; HttpOnly", + "stsservicecookie=ests; path=/; secure; HttpOnly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-clitelem": "1,0,0,,", + "x-ms-ests-server": "2.1.9707.16 - WST ProdSlices", + "x-ms-request-id": "00d1b219-0e1c-4f5a-9959-7322f59ecc01" + }, + "ResponseBody": { + "token_type": "Bearer", + "scope": "https://vault.azure.net/user_impersonation https://vault.azure.net/.default", + "expires_in": 3599, + "ext_expires_in": 3599, + "access_token": "Sanitized", + "refresh_token": "Sanitized", + "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzL3YyLjAiLCJpYXQiOjE1NzU1OTIyNjIsIm5iZiI6MTU3NTU5MjI2MiwiZXhwIjoxNTc1NTk2MTYyLCJhaW8iOiJBVFFBeS84TkFBQUFlVTRSRVNjQkdLTE1uNmg2OEhYSEFFUmdEQnJFNUpzMGlnMlo5YnVvZUVaNURnTlNoUDQ2VCtvN3podW5qdTVzIiwibmFtZSI6IlRlbXAgVXNlciAyIiwib2lkIjoiYzQ4Nzk1NDItODQxZi00MDc0LTk0MTMtZDk4MTM1MWU3MzI4IiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVtcHVzZXIyQGF6dXJlc2RrcGxheWdyb3VuZC5vbm1pY3Jvc29mdC5jb20iLCJzdWIiOiJlQzZ2b2RIMjk0VUhnTThvTTN0MlRqVHZrU0NOMThiTzAyZ2dLZ09mVDljIiwidGlkIjoiYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzIiwidXRpIjoiR2JMUkFCd09Xay1aV1hNaTlaN01BUSIsInZlciI6IjIuMCJ9.FjTnpv0bVqEu7SCh_dFyINyjOyG4xGYrUxZ3905SJH1628IjxcUqa6ZFBgS9K1I5jpb9I2OVuZcN3n0IgLjNf31eATqQwZwK2C7eBC6aBI6TG5xoCOId136JzHt4jeXSFUR1DEkb2jZOmYHhDNWjbW-aip0jdWVY4HMJPaSm-fCvlOwt51kPFQpNEXBuXycw1xaT9ia0wqPHxaLMBGV2CRIaMlPDLQqPmwqZ-pu1OPt-bd7w1C3ca-Wr-TTlKPLjvS0UUowMU5Fe28eGvqTcR6UvYvJtEMGhfdIRRkAGDl1AqUJjOICESGkqtP4p6TvC0waOMQlKUtCD97BmUcbwEQ", + "client_info": "eyJ1aWQiOiJjNDg3OTU0Mi04NDFmLTQwNzQtOTQxMy1kOTgxMzUxZTczMjgiLCJ1dGlkIjoiYzU0ZmFjODgtM2RkMy00NjFmLWE3YzQtOGEzNjhlMDM0MGIzIn0" + } + } + ], + "Variables": { + "IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_TENANTID": "c54fac88-3dd3-461f-a7c4-8a368e0340b3", + "IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_USERNAME": "tempuser2@azuresdkplayground.onmicrosoft.com", + "RandomSeed": "2008179389" + } +} \ No newline at end of file diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialLiveTests.cs new file mode 100644 index 000000000000..0135faecacc8 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialLiveTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Testing; +using Microsoft.Identity.Client; +using NUnit.Framework; + +namespace Azure.Identity.Tests +{ + // !!!!!! WARNING !!!!! + // Recordings the tests in this class WILL NOT be sanitized, and hence will + // contain sensitive information which when pushed will FULLY COMPROMISE the + // account used to record the test. For this reason these tests should only + // be recorded with a temporary account which must be deleted IMMEDIATELY + // after recording, and BEFORE any recordings are pushed to tho public repo + // or fork. To re-record these tests the following steps MUST be COMPLETELY + // followed before pushing any updates. + // + // - Create a temporary account in a tenant with MFA not enabled. Azure SDK + // team members can use the azuresdkplayground.onmicrosoft.com tenant + // (c54fac88-3dd3-461f-a7c4-8a368e0340b3) + // - Set the environment variables + // IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_USERNAME + // IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_PASSWORD + // IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_TENANTID + // to the corresponding values of the newly created temp account. + // - Run the tests in "Record" mode and copy the updated recordings into the + // .\SessionRecords folder. + // - Run the tests in "Playback" mode to ensure the recordings work properly. + // - Delete the temporary account. + // - Run the tests in live mode. Ensure the tests fail to authenticate now + // that the account is deleted with the following error message: + // "AADSTS50034: The user account {EmailHidden} does not exist in the + // directory." + // + public class UsernamePasswordCredentialLiveTests : RecordedTestBase + { + private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; + + public UsernamePasswordCredentialLiveTests(bool isAsync) : base(isAsync) + { + Matcher.ExcludeHeaders.Add("Content-Length"); + Matcher.ExcludeHeaders.Add("client-request-id"); + Matcher.ExcludeHeaders.Add("x-client-OS"); + Matcher.ExcludeHeaders.Add("x-client-SKU"); + Matcher.ExcludeHeaders.Add("x-client-CPU"); + + Sanitizer = new IdentityRecordedTestSanitizer(); + } + + [SetUp] + public void ClearDiscoveryCache() + { + Type staticMetadataProviderType = typeof(PublicClientApplication).Assembly.GetType("Microsoft.Identity.Client.Instance.Discovery.StaticMetadataProvider", true); + + var staticMetadataProvider = Activator.CreateInstance(staticMetadataProviderType); + + staticMetadataProvider.GetType().GetMethod("Clear", BindingFlags.Public | BindingFlags.Instance).Invoke(staticMetadataProvider, null); + } + + // !!!!!! WARNING !!!!! + // Recordings the tests in this class WILL NOT be sanitized, and hence will + // contain sensitive information which when pushed will FULLY COMPROMISE the + // account used to record the test. For this reason these tests should only + // be recorded with a temporary account which must be deleted IMMEDIATELY + // after recording, and BEFORE any recordings are pushed to tho public repo + // or fork. To re-record these tests the following steps MUST be COMPLETELY + // followed before pushing any updates. See class comment for instructions + [Test] + public async Task AuthenticateUsernamePasswordLive() + { + var username = Recording.GetVariableFromEnvironment("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_USERNAME"); + + var password = Environment.GetEnvironmentVariable("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_PASSWORD") ?? "SANITIZED"; + + var tenantId = Recording.GetVariableFromEnvironment("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_TENANTID"); + + var options = Recording.InstrumentClientOptions(new TokenCredentialOptions()); + + var cred = InstrumentClient(new UsernamePasswordCredential(username, password, tenantId, ClientId, options)); + + AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); + + Assert.IsNotNull(token.Token); + } + } +} diff --git a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs index da5299fced78..c1a4e77c204a 100644 --- a/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs @@ -12,28 +12,10 @@ namespace Azure.Identity.Tests { public class UsernamePasswordCredentialTests : ClientTestBase { - private const string ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; - public UsernamePasswordCredentialTests(bool isAsync) : base(isAsync) { } - [Test] - [Ignore("This test requires a user account which doesn't have MFA enabled.")] - public async Task AuthenticateUsernamePasswordLiveAsync() - { - var username = Environment.GetEnvironmentVariable("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_USERNAME"); - - var password = Environment.GetEnvironmentVariable("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_PASSWORD"); - - var tenantId = Environment.GetEnvironmentVariable("IDENTITYTEST_USERNAMEPASSWORDCREDENTIAL_TENANTID"); - - var cred = new UsernamePasswordCredential(username, password, tenantId, ClientId); - - AccessToken token = await cred.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); - - Assert.IsNotNull(token.Token); - } [Test] public async Task VerifyMsalClientExceptionAsync() @@ -57,5 +39,6 @@ public async Task VerifyMsalClientExceptionAsync() await Task.CompletedTask; } + } } diff --git a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageRecordMatcher.cs b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageRecordMatcher.cs index 20cddaa64014..83e93ce4690e 100644 --- a/sdk/storage/Azure.Storage.Common/tests/Shared/StorageRecordMatcher.cs +++ b/sdk/storage/Azure.Storage.Common/tests/Shared/StorageRecordMatcher.cs @@ -206,9 +206,9 @@ private static Dictionary ParseUri(string value) private static object ParseBody(RecordEntry entry) { // Switch on the Content-Type to check for XML or JSON - var body = entry.ResponseBody ?? Array.Empty(); + var body = entry.Response.Body ?? Array.Empty(); if (body.Length > 0 && - entry.ResponseHeaders.TryGetValue("Content-Type", out var types) && + entry.Response.Headers.TryGetValue("Content-Type", out var types) && types?.Length > 0) { if (types.Any(t => t.Contains("xml")))