From ece9861b5fa2331fddb4dc5438deb3ea2d833714 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Wed, 26 Apr 2023 23:43:21 -0700 Subject: [PATCH 1/9] Add string and uri extensions --- .../Extensions/StringExtensionsTests.cs | 24 +++++++ .../Extensions/UriExtensionsTests.cs | 16 +++++ .../Extensions/StringExtensions.cs | 67 +++++++++++++++++++ .../Extensions/UriExtensions.cs | 33 +++++++++ README.md | 2 + 5 files changed, 142 insertions(+) create mode 100644 IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs create mode 100644 IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs create mode 100644 IntelliTect.Multitool/Extensions/StringExtensions.cs create mode 100644 IntelliTect.Multitool/Extensions/UriExtensions.cs diff --git a/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs new file mode 100644 index 0000000..ebe3c80 --- /dev/null +++ b/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace IntelliTect.Multitool.Extensions.Tests +{ + public class StringExtensionsTests + { + [Theory] + [InlineData(" ExtraSpacing ", "extraspacing")] + [InlineData("Hello World", "hello-world")] + [InlineData("Coding the Publish–Subscribe Pattern with Multicast Delegates", "coding-the-publish-subscribe-pattern-with-multicast-delegates")] + [InlineData("C#", "c")] + [InlineData("C# Syntax Fundamentals", "c-syntax-fundamentals")] + [InlineData("C#_Syntax_Fundamentals", "c-syntax-fundamentals")] + [InlineData("C# Syntax_Fundamentals-for-me", "c-syntax-fundamentals-for-me")] + [InlineData("Bitwise Operators (<<, >>, |, &, ^, ~)", "bitwise-operators")] + [InlineData(".NET Standard", "net-standard")] + [InlineData("Working with System.Threading", "working-with-system-threading")] + [InlineData("á, Working í,withú, System.Thróeading", "working-with-system-threading")] + public void SanitizeStringToOnlyHaveDashesAndLowerCase(string actual, string sanitized) + { + Assert.Equal(sanitized, actual.Slugify()); + } + } +} diff --git a/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs new file mode 100644 index 0000000..e39024f --- /dev/null +++ b/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace IntelliTect.Multitool.Extensions.Tests +{ + public class UriExtensionsTests + { + [Theory] + [InlineData("https://thisisaverybadurlthatwillnotworkhopefullyever.com/", false)] + [InlineData("https://www.google.com/", true)] + [InlineData("https://api.github.com/meta", true)] + public async void ValidateUri_CheckUrl_SuccessIsAsExpected(string urlUnderTest, bool expected) + { + Assert.Equal(expected, await urlUnderTest.ValidateUrlString(false)); + } + } +} diff --git a/IntelliTect.Multitool/Extensions/StringExtensions.cs b/IntelliTect.Multitool/Extensions/StringExtensions.cs new file mode 100644 index 0000000..d7664d3 --- /dev/null +++ b/IntelliTect.Multitool/Extensions/StringExtensions.cs @@ -0,0 +1,67 @@ +using System.Text; + +namespace IntelliTect.Multitool.Extensions; + +/// +/// Various string extensions +/// +public static class StringExtensions +{ + /// + /// Slugify's text so that it is URL compatible. IE: "Hi My Name is" -> "hi-my-name-is". + /// Removes special characters, sets to lowercase, replaces ' ' and '_' with '-', and trims the string + /// + public static string Slugify(this string str) + { + str = str.ToLowerInvariant().Trim(); + StringBuilder sb = new(); + const char separatorCharacter = '-'; + bool allowSeparator = false; + foreach (char character in str) + { + switch (character) + { + // this second '-' here is different than a normal - in terms of key code + // so we replace it with a normal - + case char c when c == '_' || c == ' ' || c == '–' || c == '-' || c == '.' || c == ',': + if (allowSeparator) + { + sb.Append(separatorCharacter); + allowSeparator = false; + } + break; + // Only allow letters and numbers as valid characters (removing things like diacritics (accents)) + case char c when (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'): + sb.Append(character); + allowSeparator = true; + break; + default: + break; + } + } + return sb.ToString().TrimEnd(separatorCharacter); + } + + /// + /// Validates a URL string by checking to make sure the string is formatted correctly + /// and by attempting to make a GET request to it. + /// + /// + /// + /// + /// + public static async Task ValidateUrlString(this string hyperlink, bool logUpdates = true) + { + if (Uri.IsWellFormedUriString(hyperlink, UriKind.Absolute) && + Uri.TryCreate(hyperlink, UriKind.Absolute, out Uri? uri)) + { + if (!await uri.ValidateUri()) + { + if (logUpdates) throw new InvalidOperationException($"HTTP Request Check Failed for HyperLink URL=({hyperlink})"); + return false; + } + return true; + } + else { return false; } + } +} diff --git a/IntelliTect.Multitool/Extensions/UriExtensions.cs b/IntelliTect.Multitool/Extensions/UriExtensions.cs new file mode 100644 index 0000000..fe2df80 --- /dev/null +++ b/IntelliTect.Multitool/Extensions/UriExtensions.cs @@ -0,0 +1,33 @@ +namespace IntelliTect.Multitool.Extensions; + +/// +/// Various Uri Extensions +/// +public static class UriExtensions +{ + private static HttpClient HttpClient { get; } = new(); + + /// + /// Validates a Uri by attempting to make a GET request to it. + /// + /// The Uri you want to validate + /// The result of the call + public static async Task ValidateUri(this Uri uri) + { + try + { + // Clear then add a user-agent header in case the API requires one (such as github.com or stackoverflow.com) + // https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required + HttpClient.DefaultRequestHeaders.Clear(); + HttpClient.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("IntelliTect.Multitool"))); + + HttpResponseMessage result = await HttpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + result.EnsureSuccessStatusCode(); + } + catch (Exception) + { + return false; + } + return true; + } +} diff --git a/README.md b/README.md index 5da8d29..898c816 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ ## Security - ClaimsPrincipalExtensions: Extension methods to get a user ID and roles. +- StringExtensions: Extension methods to slugify a string and to validate a string is a valid url. +- UriExtensions: Extension methods to validate a Uri by attempting to make a GET request to it. ## Contributing From 4c0e3f078c64816cd8302e30bd2bbdb278857ee6 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 27 Apr 2023 12:35:17 -0700 Subject: [PATCH 2/9] Refactor and CR feedback --- .../ClaimsPrincipalGetRolesTests.cs | 33 ------------ .../Extensions/StringExtensionsTests.cs | 35 +++++++------ .../Extensions/UriExtensionsTests.cs | 26 ++++++---- .../Extensions/StringExtensions.cs | 50 +++++++++---------- .../Extensions/UriExtensions.cs | 13 +++-- .../Security/ClaimsPrincipalExtensions.cs | 3 +- 6 files changed, 63 insertions(+), 97 deletions(-) delete mode 100644 IntelliTect.Multitool.Tests/ClaimsPrincipalGetRolesTests.cs diff --git a/IntelliTect.Multitool.Tests/ClaimsPrincipalGetRolesTests.cs b/IntelliTect.Multitool.Tests/ClaimsPrincipalGetRolesTests.cs deleted file mode 100644 index 73d7942..0000000 --- a/IntelliTect.Multitool.Tests/ClaimsPrincipalGetRolesTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using IntelliTect.Multitool.Security; -using System.Security.Claims; -using System.Security.Principal; -using Xunit; - -namespace IntelliTect.Multitool.Tests; - -public class ClaimsPrincipalGetRolesTests -{ - [Fact] - public void WhenClaimsPrincipalNull_Should_Throw() - { - ClaimsPrincipal? sut = null; - - Assert.Throws(() => sut!.GetRoles()); - } - - [Fact] - public void WhenClaimsPrincipalHasNoRoles_Should_ReturnEmpty() - { - ClaimsPrincipal sut = new(); - - Assert.Empty(sut.GetRoles()); - } - - [Fact] - public void WhenClaimsPrincipalHasRoles_Should_ReturnNotEmpty() - { - ClaimsPrincipal sut = new GenericPrincipal(new GenericIdentity("Uncle Festus"), new[] { "Foo", "Bar" }); - - Assert.Collection(sut.GetRoles(), s => Assert.Equal("Foo", s), t => Assert.Equal("Bar", t)); - } -} \ No newline at end of file diff --git a/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs index ebe3c80..389efba 100644 --- a/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs +++ b/IntelliTect.Multitool.Tests/Extensions/StringExtensionsTests.cs @@ -1,24 +1,23 @@ using Xunit; -namespace IntelliTect.Multitool.Extensions.Tests +namespace IntelliTect.Multitool.Extensions.Tests; + +public class StringExtensionsTests { - public class StringExtensionsTests + [Theory] + [InlineData(" ExtraSpacing ", "extraspacing")] + [InlineData("Hello World", "hello-world")] + [InlineData("Coding the Publish–Subscribe Pattern with Multicast Delegates", "coding-the-publish-subscribe-pattern-with-multicast-delegates")] + [InlineData("C#", "c")] + [InlineData("C# Syntax Fundamentals ", "c-syntax-fundamentals")] + [InlineData("C#_Syntax_Fundamentals", "c-syntax-fundamentals")] + [InlineData("C# Syntax_Fundamentals-for-me", "c-syntax-fundamentals-for-me")] + [InlineData("Bitwise Operators (<<, >>, |, &, ^, ~)", "bitwise-operators")] + [InlineData(".NET Standard", "net-standard")] + [InlineData("Working with System.Threading", "working-with-system-threading")] + [InlineData("á, Working í,withú, System.Thróeading", "working-with-system-threading")] + public void SanitizeStringToOnlyHaveDashesAndLowerCase(string actual, string sanitized) { - [Theory] - [InlineData(" ExtraSpacing ", "extraspacing")] - [InlineData("Hello World", "hello-world")] - [InlineData("Coding the Publish–Subscribe Pattern with Multicast Delegates", "coding-the-publish-subscribe-pattern-with-multicast-delegates")] - [InlineData("C#", "c")] - [InlineData("C# Syntax Fundamentals", "c-syntax-fundamentals")] - [InlineData("C#_Syntax_Fundamentals", "c-syntax-fundamentals")] - [InlineData("C# Syntax_Fundamentals-for-me", "c-syntax-fundamentals-for-me")] - [InlineData("Bitwise Operators (<<, >>, |, &, ^, ~)", "bitwise-operators")] - [InlineData(".NET Standard", "net-standard")] - [InlineData("Working with System.Threading", "working-with-system-threading")] - [InlineData("á, Working í,withú, System.Thróeading", "working-with-system-threading")] - public void SanitizeStringToOnlyHaveDashesAndLowerCase(string actual, string sanitized) - { - Assert.Equal(sanitized, actual.Slugify()); - } + Assert.Equal(sanitized, actual.CreateUrlSlug()); } } diff --git a/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs index e39024f..1bdd3b1 100644 --- a/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs +++ b/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs @@ -1,16 +1,22 @@ using Xunit; -namespace IntelliTect.Multitool.Extensions.Tests +namespace IntelliTect.Multitool.Extensions.Tests; + +public class UriExtensionsTests { - public class UriExtensionsTests + static UriExtensionsTests() + { + HttpClient = new HttpClient(); + } + + private static HttpClient HttpClient { get; } + + [Theory] + [InlineData("https://thisisaverybadurlthatwillnotworkhopefullyever.com/", false)] + [InlineData("https://www.google.com/", true)] + [InlineData("https://api.github.com/meta", true)] + public async void ValidateUri_CheckUrl_SuccessIsAsExpected(string urlUnderTest, bool expected) { - [Theory] - [InlineData("https://thisisaverybadurlthatwillnotworkhopefullyever.com/", false)] - [InlineData("https://www.google.com/", true)] - [InlineData("https://api.github.com/meta", true)] - public async void ValidateUri_CheckUrl_SuccessIsAsExpected(string urlUnderTest, bool expected) - { - Assert.Equal(expected, await urlUnderTest.ValidateUrlString(false)); - } + Assert.Equal(expected, await HttpClient.ValidateUri(new Uri(urlUnderTest))); } } diff --git a/IntelliTect.Multitool/Extensions/StringExtensions.cs b/IntelliTect.Multitool/Extensions/StringExtensions.cs index d7664d3..63fd5e6 100644 --- a/IntelliTect.Multitool/Extensions/StringExtensions.cs +++ b/IntelliTect.Multitool/Extensions/StringExtensions.cs @@ -8,17 +8,20 @@ namespace IntelliTect.Multitool.Extensions; public static class StringExtensions { /// - /// Slugify's text so that it is URL compatible. IE: "Hi My Name is" -> "hi-my-name-is". + /// Modify text so that it is URL compatible. IE: "Hi My Name is" -> "hi-my-name-is". /// Removes special characters, sets to lowercase, replaces ' ' and '_' with '-', and trims the string /// - public static string Slugify(this string str) + /// The character to use as a separator. Defaults to '-'. + /// The string to modify + /// A string that is url compatible + public static string CreateUrlSlug(this string str, char separatorCharacter = '-') { - str = str.ToLowerInvariant().Trim(); StringBuilder sb = new(); - const char separatorCharacter = '-'; bool allowSeparator = false; - foreach (char character in str) + char? nextSeparator = null; + for (int i = 0; i < str.Length; i++) { + char character = str[i]; switch (character) { // this second '-' here is different than a normal - in terms of key code @@ -26,42 +29,35 @@ public static string Slugify(this string str) case char c when c == '_' || c == ' ' || c == '–' || c == '-' || c == '.' || c == ',': if (allowSeparator) { - sb.Append(separatorCharacter); + nextSeparator = separatorCharacter; allowSeparator = false; } break; // Only allow letters and numbers as valid characters (removing things like diacritics (accents)) - case char c when (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'): - sb.Append(character); + case char c when (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'): + if (nextSeparator != null) + { + sb.Append(nextSeparator); + nextSeparator = null; + } + sb.Append(char.ToLowerInvariant(character)); allowSeparator = true; break; default: break; } } - return sb.ToString().TrimEnd(separatorCharacter); + return sb.ToString(); } /// - /// Validates a URL string by checking to make sure the string is formatted correctly - /// and by attempting to make a GET request to it. + /// Validates a URL string by checking to make sure the string is formatted correctly. /// - /// - /// - /// - /// - public static async Task ValidateUrlString(this string hyperlink, bool logUpdates = true) + /// The url string to check. + /// Result of validation. + public static bool ValidateUrlString(this string hyperlink) { - if (Uri.IsWellFormedUriString(hyperlink, UriKind.Absolute) && - Uri.TryCreate(hyperlink, UriKind.Absolute, out Uri? uri)) - { - if (!await uri.ValidateUri()) - { - if (logUpdates) throw new InvalidOperationException($"HTTP Request Check Failed for HyperLink URL=({hyperlink})"); - return false; - } - return true; - } - else { return false; } + return Uri.IsWellFormedUriString(hyperlink, UriKind.Absolute) && + Uri.TryCreate(hyperlink, UriKind.Absolute, out _); } } diff --git a/IntelliTect.Multitool/Extensions/UriExtensions.cs b/IntelliTect.Multitool/Extensions/UriExtensions.cs index fe2df80..fcee39e 100644 --- a/IntelliTect.Multitool/Extensions/UriExtensions.cs +++ b/IntelliTect.Multitool/Extensions/UriExtensions.cs @@ -5,23 +5,22 @@ /// public static class UriExtensions { - private static HttpClient HttpClient { get; } = new(); - /// /// Validates a Uri by attempting to make a GET request to it. /// - /// The Uri you want to validate + /// The HttpClient to make the request + /// The Uri to validate /// The result of the call - public static async Task ValidateUri(this Uri uri) + public static async Task ValidateUri(this HttpClient client, Uri uri) { try { // Clear then add a user-agent header in case the API requires one (such as github.com or stackoverflow.com) // https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required - HttpClient.DefaultRequestHeaders.Clear(); - HttpClient.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("IntelliTect.Multitool"))); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("IntelliTect.Multitool"))); - HttpResponseMessage result = await HttpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + HttpResponseMessage result = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); result.EnsureSuccessStatusCode(); } catch (Exception) diff --git a/IntelliTect.Multitool/Security/ClaimsPrincipalExtensions.cs b/IntelliTect.Multitool/Security/ClaimsPrincipalExtensions.cs index 0dd53e3..f9bb265 100644 --- a/IntelliTect.Multitool/Security/ClaimsPrincipalExtensions.cs +++ b/IntelliTect.Multitool/Security/ClaimsPrincipalExtensions.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; +using System.Security.Claims; namespace IntelliTect.Multitool.Security; @@ -27,7 +27,6 @@ public static class ClaimsPrincipalExtensions return claim.Value; } - /// /// Gets all the roles a belongs to. /// From be9957662086d8a64dfd934e8d7d3b098b92d514 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 27 Apr 2023 12:37:46 -0700 Subject: [PATCH 3/9] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 898c816..aac476f 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ ## Security - ClaimsPrincipalExtensions: Extension methods to get a user ID and roles. + +## Extensions + - StringExtensions: Extension methods to slugify a string and to validate a string is a valid url. - UriExtensions: Extension methods to validate a Uri by attempting to make a GET request to it. From ad4f2e321132c752abfcd362ba07661a0a8dd45f Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 27 Apr 2023 12:38:42 -0700 Subject: [PATCH 4/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aac476f..1d817de 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ ## Extensions - StringExtensions: Extension methods to slugify a string and to validate a string is a valid url. -- UriExtensions: Extension methods to validate a Uri by attempting to make a GET request to it. +- HttpExtensions: Extension methods to validate a Uri by attempting to make a GET request to it. ## Contributing From 8153dcb47a1d7575caf03d3cbbce872fd3afd538 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 27 Apr 2023 12:38:49 -0700 Subject: [PATCH 5/9] Rename --- .../{UriExtensionsTests.cs => HttpExtensionsTests.cs} | 4 ++-- .../Extensions/{UriExtensions.cs => HttpExtensions.cs} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename IntelliTect.Multitool.Tests/Extensions/{UriExtensionsTests.cs => HttpExtensionsTests.cs} (89%) rename IntelliTect.Multitool/Extensions/{UriExtensions.cs => HttpExtensions.cs} (97%) diff --git a/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs similarity index 89% rename from IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs rename to IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs index 1bdd3b1..2e7c73f 100644 --- a/IntelliTect.Multitool.Tests/Extensions/UriExtensionsTests.cs +++ b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs @@ -2,9 +2,9 @@ namespace IntelliTect.Multitool.Extensions.Tests; -public class UriExtensionsTests +public class HttpExtensionsTests { - static UriExtensionsTests() + static HttpExtensionsTests() { HttpClient = new HttpClient(); } diff --git a/IntelliTect.Multitool/Extensions/UriExtensions.cs b/IntelliTect.Multitool/Extensions/HttpExtensions.cs similarity index 97% rename from IntelliTect.Multitool/Extensions/UriExtensions.cs rename to IntelliTect.Multitool/Extensions/HttpExtensions.cs index fcee39e..971137b 100644 --- a/IntelliTect.Multitool/Extensions/UriExtensions.cs +++ b/IntelliTect.Multitool/Extensions/HttpExtensions.cs @@ -3,7 +3,7 @@ /// /// Various Uri Extensions /// -public static class UriExtensions +public static class HttpExtensions { /// /// Validates a Uri by attempting to make a GET request to it. From 76c67ea3ea90c5ff5ffd91769c184bbb5232b4e6 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 27 Apr 2023 12:44:26 -0700 Subject: [PATCH 6/9] Update header value --- .../Extensions/HttpExtensionsTests.cs | 2 +- IntelliTect.Multitool/Extensions/HttpExtensions.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs index 2e7c73f..e33fdcd 100644 --- a/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs +++ b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs @@ -17,6 +17,6 @@ static HttpExtensionsTests() [InlineData("https://api.github.com/meta", true)] public async void ValidateUri_CheckUrl_SuccessIsAsExpected(string urlUnderTest, bool expected) { - Assert.Equal(expected, await HttpClient.ValidateUri(new Uri(urlUnderTest))); + Assert.Equal(expected, await HttpClient.ValidateUri(new Uri(urlUnderTest), "IntelliTect.Multitool.Testing")); } } diff --git a/IntelliTect.Multitool/Extensions/HttpExtensions.cs b/IntelliTect.Multitool/Extensions/HttpExtensions.cs index 971137b..66edd9a 100644 --- a/IntelliTect.Multitool/Extensions/HttpExtensions.cs +++ b/IntelliTect.Multitool/Extensions/HttpExtensions.cs @@ -10,15 +10,16 @@ public static class HttpExtensions /// /// The HttpClient to make the request /// The Uri to validate + /// A string for what will be passed in the header of the validation. /// The result of the call - public static async Task ValidateUri(this HttpClient client, Uri uri) + public static async Task ValidateUri(this HttpClient client, Uri uri, string headerValue = "IntelliTect.Multitool") { try { // Clear then add a user-agent header in case the API requires one (such as github.com or stackoverflow.com) // https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required client.DefaultRequestHeaders.Clear(); - client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("IntelliTect.Multitool"))); + client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue(headerValue))); HttpResponseMessage result = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); result.EnsureSuccessStatusCode(); From d04b0cf7804f9445507ec8518105e8e09dcde904 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Wed, 31 May 2023 16:24:25 -0700 Subject: [PATCH 7/9] Minor updates from CR --- .../Extensions/HttpExtensionsTests.cs | 2 +- .../Extensions/HttpExtensions.cs | 32 ++++++++++++++++--- .../Extensions/StringExtensions.cs | 8 ++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs index e33fdcd..01ad2e5 100644 --- a/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs +++ b/IntelliTect.Multitool.Tests/Extensions/HttpExtensionsTests.cs @@ -17,6 +17,6 @@ static HttpExtensionsTests() [InlineData("https://api.github.com/meta", true)] public async void ValidateUri_CheckUrl_SuccessIsAsExpected(string urlUnderTest, bool expected) { - Assert.Equal(expected, await HttpClient.ValidateUri(new Uri(urlUnderTest), "IntelliTect.Multitool.Testing")); + Assert.Equal(expected, await HttpClient.ValidateUri(new Uri(urlUnderTest), new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("IntelliTect.Multitool.Testing")))); } } diff --git a/IntelliTect.Multitool/Extensions/HttpExtensions.cs b/IntelliTect.Multitool/Extensions/HttpExtensions.cs index 66edd9a..c90c250 100644 --- a/IntelliTect.Multitool/Extensions/HttpExtensions.cs +++ b/IntelliTect.Multitool/Extensions/HttpExtensions.cs @@ -1,4 +1,6 @@ -namespace IntelliTect.Multitool.Extensions; +using System.Net.Http.Headers; + +namespace IntelliTect.Multitool.Extensions; /// /// Various Uri Extensions @@ -10,18 +12,25 @@ public static class HttpExtensions /// /// The HttpClient to make the request /// The Uri to validate - /// A string for what will be passed in the header of the validation. + /// Header values to add to the clients user agent request headers. + /// A completion option if the default completion option is not desired /// The result of the call - public static async Task ValidateUri(this HttpClient client, Uri uri, string headerValue = "IntelliTect.Multitool") + public static async Task ValidateUri(this HttpClient client, Uri uri, ProductInfoHeaderValue[]? headerValues = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead) { try { // Clear then add a user-agent header in case the API requires one (such as github.com or stackoverflow.com) // https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required client.DefaultRequestHeaders.Clear(); - client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue(headerValue))); + if (headerValues is not null) + { + foreach (ProductInfoHeaderValue value in headerValues) + { + client.DefaultRequestHeaders.UserAgent.Add(value); + } + } - HttpResponseMessage result = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + HttpResponseMessage result = await client.GetAsync(uri, completionOption); result.EnsureSuccessStatusCode(); } catch (Exception) @@ -30,4 +39,17 @@ public static async Task ValidateUri(this HttpClient client, Uri uri, stri } return true; } + + /// + /// Validates a Uri by attempting to make a GET request to it. + /// + /// The HttpClient to make the request + /// The Uri to validate + /// Header value to add to the clients user agent request header. + /// A completion option if the default completion option is not desired + /// The result of the call + public static async Task ValidateUri(this HttpClient client, Uri uri, ProductInfoHeaderValue? headerValue, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead) + { + return await ValidateUri(client, uri, headerValue is null ? null : new ProductInfoHeaderValue[] { headerValue }, completionOption); + } } diff --git a/IntelliTect.Multitool/Extensions/StringExtensions.cs b/IntelliTect.Multitool/Extensions/StringExtensions.cs index 63fd5e6..8202f5c 100644 --- a/IntelliTect.Multitool/Extensions/StringExtensions.cs +++ b/IntelliTect.Multitool/Extensions/StringExtensions.cs @@ -53,11 +53,11 @@ public static string CreateUrlSlug(this string str, char separatorCharacter = '- /// /// Validates a URL string by checking to make sure the string is formatted correctly. /// - /// The url string to check. + /// The url string to check. /// Result of validation. - public static bool ValidateUrlString(this string hyperlink) + public static bool ValidateUrlString(this string url) { - return Uri.IsWellFormedUriString(hyperlink, UriKind.Absolute) && - Uri.TryCreate(hyperlink, UriKind.Absolute, out _); + return Uri.IsWellFormedUriString(url, UriKind.Absolute) && + Uri.TryCreate(url, UriKind.Absolute, out _); } } From 912149cc2318a8ca8dd472d5ea341eaa24e7002c Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Wed, 31 May 2023 16:27:06 -0700 Subject: [PATCH 8/9] Docs update --- IntelliTect.Multitool/Extensions/HttpExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IntelliTect.Multitool/Extensions/HttpExtensions.cs b/IntelliTect.Multitool/Extensions/HttpExtensions.cs index c90c250..ab26a0c 100644 --- a/IntelliTect.Multitool/Extensions/HttpExtensions.cs +++ b/IntelliTect.Multitool/Extensions/HttpExtensions.cs @@ -12,7 +12,7 @@ public static class HttpExtensions /// /// The HttpClient to make the request /// The Uri to validate - /// Header values to add to the clients user agent request headers. + /// Header values to add to the clients user agent request headers. Note: Some sites require a request header. /// A completion option if the default completion option is not desired /// The result of the call public static async Task ValidateUri(this HttpClient client, Uri uri, ProductInfoHeaderValue[]? headerValues = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead) @@ -45,7 +45,7 @@ public static async Task ValidateUri(this HttpClient client, Uri uri, Prod /// /// The HttpClient to make the request /// The Uri to validate - /// Header value to add to the clients user agent request header. + /// Header value to add to the clients user agent request header. Note: Some sites require a request header. /// A completion option if the default completion option is not desired /// The result of the call public static async Task ValidateUri(this HttpClient client, Uri uri, ProductInfoHeaderValue? headerValue, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead) From 0da4686cafde07c30956bedd15072ddc590b5b96 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Thu, 1 Jun 2023 08:49:40 -0700 Subject: [PATCH 9/9] Simplify tests --- .../ClaimsPrincipalExtensionsTests.cs | 56 +++++++++++++++++++ .../ClaimsPrincipalGetUserIdTests.cs | 33 ----------- 2 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 IntelliTect.Multitool.Tests/ClaimsPrincipalExtensionsTests.cs delete mode 100644 IntelliTect.Multitool.Tests/ClaimsPrincipalGetUserIdTests.cs diff --git a/IntelliTect.Multitool.Tests/ClaimsPrincipalExtensionsTests.cs b/IntelliTect.Multitool.Tests/ClaimsPrincipalExtensionsTests.cs new file mode 100644 index 0000000..f015ee8 --- /dev/null +++ b/IntelliTect.Multitool.Tests/ClaimsPrincipalExtensionsTests.cs @@ -0,0 +1,56 @@ +using System.Security.Claims; +using System.Security.Principal; +using Xunit; + +namespace IntelliTect.Multitool.Security.Tests; + +public class ClaimsPrincipalExtensionsTests +{ + [Fact] + public void GetRoles_WhenClaimsPrincipalNull_Should_Throw() + { + ClaimsPrincipal? sut = null; + + Assert.Throws(() => sut!.GetRoles()); + } + + [Fact] + public void GetRoles_WhenClaimsPrincipalHasNoRoles_Should_ReturnEmpty() + { + ClaimsPrincipal sut = new(); + + Assert.Empty(sut.GetRoles()); + } + + [Fact] + public void GetRoles_WhenClaimsPrincipalHasRoles_Should_ReturnNotEmpty() + { + ClaimsPrincipal sut = new GenericPrincipal(new GenericIdentity("Uncle Festus"), new[] { "Foo", "Bar" }); + + Assert.Collection(sut.GetRoles(), s => Assert.Equal("Foo", s), t => Assert.Equal("Bar", t)); + } + + [Fact] + public void GetUserId_WhenClaimsPrincipalNull_Should_Throw() + { + ClaimsPrincipal? sut = null; + + Assert.Throws(() => sut!.GetUserId()); + } + + [Fact] + public void GetUserId_WhenClaimsPrincipalHasNoProperty_Should_ReturnNull() + { + ClaimsPrincipal sut = new(); + + Assert.Null(sut.GetUserId()); + } + + [Fact] + public void GetUserId_WhenClaimsPrincipalHasId_Should_ReturnString() + { + ClaimsPrincipal sut = new GenericPrincipal(new GenericIdentity("Taki The Frog"), new[] { "Bar" }); + + Assert.Equal("Taki The Frog", sut.GetUserId()); + } +} \ No newline at end of file diff --git a/IntelliTect.Multitool.Tests/ClaimsPrincipalGetUserIdTests.cs b/IntelliTect.Multitool.Tests/ClaimsPrincipalGetUserIdTests.cs deleted file mode 100644 index 2030746..0000000 --- a/IntelliTect.Multitool.Tests/ClaimsPrincipalGetUserIdTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using IntelliTect.Multitool.Security; -using System.Security.Claims; -using System.Security.Principal; -using Xunit; - -namespace IntelliTect.Multitool.Tests; - -public class ClaimsPrincipalGetUserIdTests -{ - [Fact] - public void WhenClaimsPrincipalNull_Should_Throw() - { - ClaimsPrincipal? sut = null; - - Assert.Throws(() => sut!.GetUserId()); - } - - [Fact] - public void WhenClaimsPrincipalHasNoProperty_Should_ReturnNull() - { - ClaimsPrincipal sut = new(); - - Assert.Null(sut.GetUserId()); - } - - [Fact] - public void WhenClaimsPrincipalHasId_Should_ReturnString() - { - ClaimsPrincipal sut = new GenericPrincipal(new GenericIdentity("Taki The Frog"), new[] { "Bar" }); - - Assert.Equal("Taki The Frog", sut.GetUserId()); - } -} \ No newline at end of file