Merge release/2.2 -> master

Fixes integrity validation in Identity UI V3
This commit is contained in:
Javier Calvarro Nelson 2018-11-02 14:57:25 -07:00 committed by GitHub
commit 7421ba8087
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 11 deletions

View File

@ -67,17 +67,17 @@
asp-fallback-src="~/Identity/lib/jquery/dist/jquery.min.js" asp-fallback-src="~/Identity/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery" asp-fallback-test="window.jQuery"
crossorigin="anonymous" crossorigin="anonymous"
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"> integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"
asp-fallback-src="~/Identity/lib/bootstrap/dist/js/bootstrap.min.js" asp-fallback-src="~/Identity/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal" asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous" crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"> integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=">
</script> </script>
<script src="~/Identity/js/site.js" asp-append-version="true"></script> <script src="~/Identity/js/site.js" asp-append-version="true"></script>
</environment> </environment>
@RenderSection("Scripts", required: false) @RenderSection("Scripts", required: false)
</body> </body>
</html> </html>

View File

@ -9,6 +9,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -23,7 +24,7 @@ namespace Microsoft.AspNetCore.Identity.Test
public IdentityUIScriptsTest(ITestOutputHelper output) public IdentityUIScriptsTest(ITestOutputHelper output)
{ {
_output = output; _output = output;
_httpClient = new HttpClient(); _httpClient = new HttpClient(new RetryHandler(new HttpClientHandler() { }));
} }
public static IEnumerable<object[]> ScriptWithIntegrityData public static IEnumerable<object[]> ScriptWithIntegrityData
@ -40,15 +41,18 @@ namespace Microsoft.AspNetCore.Identity.Test
[MemberData(nameof(ScriptWithIntegrityData))] [MemberData(nameof(ScriptWithIntegrityData))]
public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck(ScriptTag scriptTag) public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck(ScriptTag scriptTag)
{ {
string expectedIntegrity; var sha256Integrity = await GetShaIntegrity(scriptTag, SHA256.Create(), "sha256");
Assert.Equal(scriptTag.Integrity, sha256Integrity);
}
private async Task<string> GetShaIntegrity(ScriptTag scriptTag, HashAlgorithm algorithm, string prefix)
{
using (var respStream = await _httpClient.GetStreamAsync(scriptTag.Src)) using (var respStream = await _httpClient.GetStreamAsync(scriptTag.Src))
using (var alg = SHA256.Create()) using (var alg = SHA256.Create())
{ {
var hash = alg.ComputeHash(respStream); var hash = alg.ComputeHash(respStream);
expectedIntegrity = "sha256-" + Convert.ToBase64String(hash); return $"{prefix}-" + Convert.ToBase64String(hash);
} }
Assert.Equal(expectedIntegrity, scriptTag.Integrity);
} }
public static IEnumerable<object[]> ScriptWithFallbackSrcData public static IEnumerable<object[]> ScriptWithFallbackSrcData
@ -66,7 +70,7 @@ namespace Microsoft.AspNetCore.Identity.Test
public async Task IdentityUI_ScriptTags_FallbackSourceContent_Matches_CDNContent(ScriptTag scriptTag) public async Task IdentityUI_ScriptTags_FallbackSourceContent_Matches_CDNContent(ScriptTag scriptTag)
{ {
var slnDir = GetSolutionDir(); var slnDir = GetSolutionDir();
var wwwrootDir = Path.Combine(slnDir, "src", "UI", "wwwroot", "V4"); var wwwrootDir = Path.Combine(slnDir, "src", "UI", "wwwroot", scriptTag.Version);
var cdnContent = await _httpClient.GetStringAsync(scriptTag.Src); var cdnContent = await _httpClient.GetStringAsync(scriptTag.Src);
var fallbackSrcContent = File.ReadAllText( var fallbackSrcContent = File.ReadAllText(
@ -77,6 +81,7 @@ namespace Microsoft.AspNetCore.Identity.Test
public struct ScriptTag public struct ScriptTag
{ {
public string Version;
public string Src; public string Src;
public string Integrity; public string Integrity;
public string FallbackSrc; public string FallbackSrc;
@ -91,8 +96,9 @@ namespace Microsoft.AspNetCore.Identity.Test
private static List<ScriptTag> GetScriptTags() private static List<ScriptTag> GetScriptTags()
{ {
var slnDir = GetSolutionDir(); var slnDir = GetSolutionDir();
var uiDir = Path.Combine(slnDir, "src", "UI", "Areas", "Identity", "Pages", "V4"); var uiDirV3 = Path.Combine(slnDir, "src", "UI", "Areas", "Identity", "Pages", "V3");
var cshtmlFiles = Directory.GetFiles(uiDir, "*.cshtml", SearchOption.AllDirectories); var uiDirV4 = Path.Combine(slnDir, "src", "UI", "Areas", "Identity", "Pages", "V4");
var cshtmlFiles = GetRazorFiles(uiDirV3).Concat(GetRazorFiles(uiDirV4));
var scriptTags = new List<ScriptTag>(); var scriptTags = new List<ScriptTag>();
foreach (var cshtmlFile in cshtmlFiles) foreach (var cshtmlFile in cshtmlFiles)
@ -104,6 +110,8 @@ namespace Microsoft.AspNetCore.Identity.Test
Assert.NotEmpty(scriptTags); Assert.NotEmpty(scriptTags);
return scriptTags; return scriptTags;
IEnumerable<string> GetRazorFiles(string dir) => Directory.GetFiles(dir, "*.cshtml", SearchOption.AllDirectories);
} }
private static List<ScriptTag> GetScriptTags(string cshtmlFile) private static List<ScriptTag> GetScriptTags(string cshtmlFile)
@ -123,6 +131,7 @@ namespace Microsoft.AspNetCore.Identity.Test
scriptTags.Add(new ScriptTag scriptTags.Add(new ScriptTag
{ {
Version = cshtmlFile.Contains("V3") ? "V3" : "V4",
Src = scriptElement.Source, Src = scriptElement.Source,
Integrity = scriptElement.Integrity, Integrity = scriptElement.Integrity,
FallbackSrc = fallbackSrcAttribute?.Value, FallbackSrc = fallbackSrcAttribute?.Value,
@ -155,5 +164,25 @@ namespace Microsoft.AspNetCore.Identity.Test
{ {
_httpClient.Dispose(); _httpClient.Dispose();
} }
class RetryHandler : DelegatingHandler
{
public RetryHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage result = null;
for (var i = 0; i < 10; i++)
{
result = await base.SendAsync(request, cancellationToken);
if (result.IsSuccessStatusCode)
{
return result;
}
await Task.Delay(1000);
}
return result;
}
}
} }
} }