Added test for verifying fallbacksrc data
This commit is contained in:
parent
880ae9aee9
commit
ae6933af2e
|
|
@ -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<ScriptTag>();
|
||||
foreach (var cshtmlFile in cshtmlFiles)
|
||||
{
|
||||
scriptTags.AddRange(GetScriptTags(cshtmlFile));
|
||||
}
|
||||
|
||||
Assert.NotEmpty(scriptTags);
|
||||
|
||||
var shasum = new Dictionary<string, string>(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(@"<script[^>]*src=""(?'src'http[^""]+)""[^>]*integrity=""(?'integrity'[^""]+)""([^>]*)>", RegexOptions.Multiline);
|
||||
|
||||
private IEnumerable<ScriptTag> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<object[]> 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<object[]> 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<ScriptTag> GetScriptTags()
|
||||
{
|
||||
var slnDir = GetSolutionDir();
|
||||
var uiDir = Path.Combine(slnDir, "src", "UI");
|
||||
var cshtmlFiles = Directory.GetFiles(uiDir, "*.cshtml", SearchOption.AllDirectories);
|
||||
|
||||
var scriptTags = new List<ScriptTag>();
|
||||
foreach (var cshtmlFile in cshtmlFiles)
|
||||
{
|
||||
var tags = GetScriptTags(cshtmlFile);
|
||||
scriptTags.AddRange(tags);
|
||||
}
|
||||
|
||||
Assert.NotEmpty(scriptTags);
|
||||
|
||||
return scriptTags;
|
||||
}
|
||||
|
||||
private static List<ScriptTag> GetScriptTags(string cshtmlFile)
|
||||
{
|
||||
IHtmlDocument htmlDocument;
|
||||
var htmlParser = new HtmlParser();
|
||||
using (var stream = File.OpenRead(cshtmlFile))
|
||||
{
|
||||
htmlDocument = htmlParser.Parse(stream);
|
||||
}
|
||||
|
||||
var scriptTags = new List<ScriptTag>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="$(AngleSharpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPackageVersion)" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue