From ae6933af2e15f5dddf2fcebb1150be2f2104509d Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 16 May 2018 13:32:08 -0700 Subject: [PATCH] Added test for verifying fallbacksrc data --- test/Identity.Test/CdnScriptTaghelperTests.cs | 111 ------------ test/Identity.Test/IdentityUIScriptsTest.cs | 159 ++++++++++++++++++ .../Microsoft.AspNetCore.Identity.Test.csproj | 1 + 3 files changed, 160 insertions(+), 111 deletions(-) delete mode 100644 test/Identity.Test/CdnScriptTaghelperTests.cs create mode 100644 test/Identity.Test/IdentityUIScriptsTest.cs diff --git a/test/Identity.Test/CdnScriptTaghelperTests.cs b/test/Identity.Test/CdnScriptTaghelperTests.cs deleted file mode 100644 index b9a65a320c..0000000000 --- a/test/Identity.Test/CdnScriptTaghelperTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Identity.Test -{ - public class CdnScriptTagTests - { - private readonly ITestOutputHelper _output; - - public CdnScriptTagTests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck() - { - var slnDir = GetSolutionDir(); - var sourceDir = Path.Combine(slnDir, "src", "UI"); - var cshtmlFiles = Directory.GetFiles(sourceDir, "*.cshtml", SearchOption.AllDirectories); - - var scriptTags = new List(); - foreach (var cshtmlFile in cshtmlFiles) - { - scriptTags.AddRange(GetScriptTags(cshtmlFile)); - } - - Assert.NotEmpty(scriptTags); - - var shasum = new Dictionary(StringComparer.OrdinalIgnoreCase); - using (var client = new HttpClient()) - { - foreach (var script in scriptTags) - { - if (shasum.ContainsKey(script.Src)) - { - continue; - } - - using (var resp = await client.GetStreamAsync(script.Src)) - using (var alg = SHA384.Create()) - { - var hash = alg.ComputeHash(resp); - shasum.Add(script.Src, "sha384-" + Convert.ToBase64String(hash)); - } - } - } - - Assert.All(scriptTags, t => - { - Assert.True(shasum[t.Src] == t.Integrity, userMessage: $"Expected integrity on script tag to be {shasum[t.Src]} but it was {t.Integrity}. {t.FileName}"); - }); - } - - private struct ScriptTag - { - public string Src; - public string Integrity; - public string FileName; - } - - private static readonly Regex _scriptRegex = new Regex(@"]*src=""(?'src'http[^""]+)""[^>]*integrity=""(?'integrity'[^""]+)""([^>]*)>", RegexOptions.Multiline); - - private IEnumerable GetScriptTags(string cshtmlFile) - { - string contents; - using (var reader = new StreamReader(File.OpenRead(cshtmlFile))) - { - contents = reader.ReadToEnd(); - } - - var match = _scriptRegex.Match(contents); - while (match != null && match != Match.Empty) - { - var tag = new ScriptTag - { - Src = match.Groups["src"].Value, - Integrity = match.Groups["integrity"].Value, - FileName = Path.GetFileName(cshtmlFile) - }; - yield return tag; - _output.WriteLine($"Found script tag in '{tag.FileName}', src='{tag.Src}' integrity='{tag.Integrity}'"); - match = match.NextMatch(); - } - } - - private static string GetSolutionDir() - { - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir != null) - { - if (File.Exists(Path.Combine(dir.FullName, "Identity.sln"))) - { - break; - } - dir = dir.Parent; - } - return dir.FullName; - } - } -} \ No newline at end of file diff --git a/test/Identity.Test/IdentityUIScriptsTest.cs b/test/Identity.Test/IdentityUIScriptsTest.cs new file mode 100644 index 0000000000..21f9bb98d4 --- /dev/null +++ b/test/Identity.Test/IdentityUIScriptsTest.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using AngleSharp.Dom.Html; +using AngleSharp.Parser.Html; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Identity.Test +{ + public class IdentityUIScriptsTest : IDisposable + { + private readonly ITestOutputHelper _output; + private readonly HttpClient _httpClient; + + public IdentityUIScriptsTest(ITestOutputHelper output) + { + _output = output; + _httpClient = new HttpClient(); + } + + public static IEnumerable ScriptWithIntegrityData + { + get + { + return GetScriptTags() + .Where(st => st.Integrity != null) + .Select(st => new object[] { st }); + } + } + + [Theory] + [MemberData(nameof(ScriptWithIntegrityData))] + public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck(ScriptTag scriptTag) + { + string expectedIntegrity; + using (var respStream = await _httpClient.GetStreamAsync(scriptTag.Src)) + using (var alg = SHA384.Create()) + { + var hash = alg.ComputeHash(respStream); + expectedIntegrity = "sha384-" + Convert.ToBase64String(hash); + } + + Assert.Equal(expectedIntegrity, scriptTag.Integrity); + } + + public static IEnumerable ScriptWithFallbackSrcData + { + get + { + return GetScriptTags() + .Where(st => st.FallbackSrc != null) + .Select(st => new object[] { st }); + } + } + + [Theory] + [MemberData(nameof(ScriptWithFallbackSrcData))] + public async Task IdentityUI_ScriptTags_FallbackSourceContent_Matches_CDNContent(ScriptTag scriptTag) + { + var slnDir = GetSolutionDir(); + var wwwrootDir = Path.Combine(slnDir, "src", "UI", "wwwroot"); + + var cdnContent = await _httpClient.GetStringAsync(scriptTag.Src); + var fallbackSrcContent = File.ReadAllText( + Path.Combine(wwwrootDir, scriptTag.FallbackSrc.TrimStart('~').TrimStart('/'))); + + Assert.Equal(RemoveLineEndings(cdnContent), RemoveLineEndings(fallbackSrcContent)); + } + + public struct ScriptTag + { + public string Src; + public string Integrity; + public string FallbackSrc; + public string File; + + public override string ToString() + { + return Src; + } + } + + private static List GetScriptTags() + { + var slnDir = GetSolutionDir(); + var uiDir = Path.Combine(slnDir, "src", "UI"); + var cshtmlFiles = Directory.GetFiles(uiDir, "*.cshtml", SearchOption.AllDirectories); + + var scriptTags = new List(); + foreach (var cshtmlFile in cshtmlFiles) + { + var tags = GetScriptTags(cshtmlFile); + scriptTags.AddRange(tags); + } + + Assert.NotEmpty(scriptTags); + + return scriptTags; + } + + private static List GetScriptTags(string cshtmlFile) + { + IHtmlDocument htmlDocument; + var htmlParser = new HtmlParser(); + using (var stream = File.OpenRead(cshtmlFile)) + { + htmlDocument = htmlParser.Parse(stream); + } + + var scriptTags = new List(); + foreach (var scriptElement in htmlDocument.Scripts) + { + var fallbackSrcAttribute = scriptElement.Attributes + .FirstOrDefault(attr => string.Equals("asp-fallback-src", attr.Name, StringComparison.OrdinalIgnoreCase)); + + scriptTags.Add(new ScriptTag + { + Src = scriptElement.Source, + Integrity = scriptElement.Integrity, + FallbackSrc = fallbackSrcAttribute?.Value, + File = cshtmlFile + }); + } + return scriptTags; + } + + private static string GetSolutionDir() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, "Identity.sln"))) + { + break; + } + dir = dir.Parent; + } + return dir.FullName; + } + + private static string RemoveLineEndings(string originalString) + { + return originalString.Replace("\r\n", "").Replace("\n", ""); + } + + public void Dispose() + { + _httpClient.Dispose(); + } + } +} diff --git a/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj b/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj index 903bcb51d1..e6e124f3fc 100644 --- a/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj +++ b/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj @@ -14,6 +14,7 @@ +