Merge branch 'release/2.1' into dev

This commit is contained in:
Nate McMaster 2018-05-16 11:53:22 -07:00
Родитель 540006316a 6420150fe1
Коммит 621d5af1a1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A778D9601BD78810
9 изменённых файлов: 281 добавлений и 29 удалений

Просмотреть файл

@ -220,12 +220,25 @@ namespace Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Identity
return false;
}
const string sharedDirName = "Shared/";
const string sharedDirName = "Shared";
string outputDirectory;
if (templateModel.SupportFileLocation.EndsWith(sharedDirName))
string checkSupportFileLocation = templateModel.SupportFileLocation;
if (checkSupportFileLocation.EndsWith("\\") || checkSupportFileLocation.EndsWith("/"))
{
int directoryLengthWithoutShared = templateModel.SupportFileLocation.Length - sharedDirName.Length;
outputDirectory = templateModel.SupportFileLocation.Substring(0, directoryLengthWithoutShared);
checkSupportFileLocation = checkSupportFileLocation.Substring(0, checkSupportFileLocation.Length - 1);
}
//if (templateModel.SupportFileLocation.EndsWith(sharedDirName))
//{
// int directoryLengthWithoutShared = templateModel.SupportFileLocation.Length - sharedDirName.Length;
// outputDirectory = templateModel.SupportFileLocation.Substring(0, directoryLengthWithoutShared);
//}
if (checkSupportFileLocation.EndsWith(sharedDirName))
{
int directoryLengthWithoutShared = checkSupportFileLocation.Length - sharedDirName.Length;
outputDirectory = checkSupportFileLocation.Substring(0, directoryLengthWithoutShared);
}
else
{

Просмотреть файл

@ -247,23 +247,50 @@ namespace Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Identity
{ "Views/", "Views/Shared/" }
};
private static readonly string _DefaultSupportLocation = "Pages/Shared/";
internal static readonly string _DefaultSupportLocation = "Pages/Shared/";
private static readonly string _LayoutFileName = "_Layout.cshtml";
internal static readonly string _LayoutFileName = "_Layout.cshtml";
// Checks if there is an existing layout page, and based on its location or lack of existence, determines where to put support pages.
// Returns true if there is an existing layout page.
// Note: layoutFile & supportFileLocation will always have a value when this exits.
// supportFileLocation is rooted
private bool DetermineSupportFileLocation(out string supportFileLocation, out string layoutFile)
internal bool DetermineSupportFileLocation(out string supportFileLocation, out string layoutFile)
{
string projectDir = Path.GetDirectoryName(_projectContext.ProjectFullPath);
if (!string.IsNullOrEmpty(_commandlineModel.Layout))
{
supportFileLocation = Path.GetDirectoryName(_commandlineModel.Layout);
layoutFile = _commandlineModel.Layout;
return true;
if (_commandlineModel.Layout.StartsWith("~"))
{
layoutFile = _commandlineModel.Layout.Substring(1);
}
else
{
layoutFile = _commandlineModel.Layout;
}
while (!string.IsNullOrEmpty(layoutFile) &&
(layoutFile[0] == Path.DirectorySeparatorChar ||
layoutFile[0] == Path.AltDirectorySeparatorChar))
{
layoutFile = layoutFile.Substring(1);
}
// if the input layout file path consists of only slashes (and possibly a lead ~), it'll be empty at this point.
// So we'll treat it as if no layout file was specified (handled below).
if (!string.IsNullOrEmpty(layoutFile))
{
// normalize the path characters sp GetDirectoryName() works.
layoutFile = layoutFile.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
supportFileLocation = Path.GetDirectoryName(layoutFile);
// always use forward slashes for the layout file path.
layoutFile = layoutFile.Replace("\\", "/");
return true;
}
}
bool hasExistingLayoutFile = false;

Просмотреть файл

@ -1,4 +1,4 @@
@inherits Microsoft.VisualStudio.Web.CodeGeneration.Templating.RazorTemplateBase
@inherits Microsoft.VisualStudio.Web.CodeGeneration.Templating.RazorTemplateBase
@{
var currentYear = System.DateTime.Now.Year;
}
@ -67,11 +67,11 @@
<script src="~/Identity/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/Identity/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/Identity/lib/bootstrap/dist/js/bootstrap.min.js"

Просмотреть файл

@ -3,16 +3,16 @@
<script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k">
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH">
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
</script>
</environment>

Просмотреть файл

@ -3,12 +3,16 @@
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
</script>
</environment>

Просмотреть файл

@ -3,12 +3,16 @@
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
</script>
</environment>

Просмотреть файл

@ -3,12 +3,16 @@
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
</script>
</environment>

Просмотреть файл

@ -0,0 +1,113 @@
// 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.VisualStudio.Web.CodeGenerators.Mvc
{
public class CdnScriptTagTests
{
private readonly ITestOutputHelper _output;
public CdnScriptTagTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public async Task ScriptTags_SubresourceIntegrityCheck()
{
var slnDir = GetSolutionDir();
var sourceDir = Path.Combine(slnDir, "src", "VS.Web.CG.Mvc");
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.Path}");
});
}
private struct ScriptTag
{
public string Src;
public string Integrity;
public string FileName;
internal string Path;
}
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),
Path = 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, "Scaffolding.sln")))
{
break;
}
dir = dir.Parent;
}
return dir.FullName;
}
}
}

Просмотреть файл

@ -9,6 +9,7 @@ using Microsoft.Extensions.ProjectModel;
using Microsoft.VisualStudio.Web.CodeGeneration;
using Microsoft.VisualStudio.Web.CodeGeneration.Contracts.ProjectModel;
using Microsoft.VisualStudio.Web.CodeGeneration.DotNet;
using Microsoft.VisualStudio.Web.CodeGeneration.Test.Sources;
using Microsoft.VisualStudio.Web.CodeGeneration.Utils;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Identity;
using Moq;
@ -71,6 +72,92 @@ namespace Microsoft.VisualStudio.Web.CodeGenerators.Mvc
}
}
private static readonly string LayoutFileLocationTestProjectBasePath = "c:\\users\\test\\source\\repos\\Test1\\";
// Tests for determining the support file location when an existing layout path is specified.
// The input layout file path is relative to the project root.
//
// tests for layout file in a standard location
[Theory]
[InlineData(false, false, new[] { "Views", "Shared", "Layout.cshtml" }, new[] { "Views", "Shared" }, new[] { "Views", "Shared", "Layout.cshtml" })]
[InlineData(true, false, new[] { "Views", "Shared", "Layout.cshtml" }, new[] { "Views", "Shared" }, new[] { "Views", "Shared", "Layout.cshtml" })]
[InlineData(false, true, new[] { "Views", "Shared", "Layout.cshtml" }, new[] { "Views", "Shared" }, new[] { "Views", "Shared", "Layout.cshtml" })]
[InlineData(true, true, new[] { "Views", "Shared", "Layout.cshtml" }, new[] { "Views", "Shared" }, new[] { "Views", "Shared", "Layout.cshtml" })]
// test for no layout file / improper specification
[InlineData(false, false, new string[0], new string[0], new string[0])]
[InlineData(true, false, new string[0], new string[0], new string[0])]
[InlineData(false, true, new string[0], new string[0], new string[0])]
[InlineData(true, true, new string[0], new string[0], new string[0])]
// tests for layout file in custom locations.
[InlineData(false, false, new[] { "Custom", "Location", "Layout.cshtml" }, new[] { "Custom", "Location" }, new[] { "Custom", "Location", "Layout.cshtml" })]
[InlineData(true, true, new[] { "My", "Files", "Layout.cshtml" }, new[] { "My", "Files" }, new[] { "My", "Files", "Layout.cshtml" })]
[InlineData(false, true, new[] { "My", "Files", "Layout.cshtml" }, new[] { "My", "Files" }, new[] { "My", "Files", "Layout.cshtml" })]
[InlineData(true, true, new[] { "Some", "Location", "Layout.cshtml" }, new[] { "Some", "Location" }, new[] { "Some", "Location", "Layout.cshtml" })]
public void SupportFileLocationForExistingLayoutFileTest(bool leadTilde, bool leadSeparator, string[] existingLayoutFileParts, string[] expectedSupportFileLocationParts, string[] expectedLayoutFileParts)
{
string expectedSupportFileLocation;
string expectedLayoutFile;
if (expectedSupportFileLocationParts.Length > 0)
{
expectedSupportFileLocation = Path.Combine(expectedSupportFileLocationParts);
}
else
{
expectedSupportFileLocation = IdentityGeneratorTemplateModelBuilder._DefaultSupportLocation;
}
if (expectedLayoutFileParts.Length > 0)
{
expectedLayoutFile = Path.Combine(expectedLayoutFileParts);
}
else
{
expectedLayoutFile = Path.Combine(IdentityGeneratorTemplateModelBuilder._DefaultSupportLocation, IdentityGeneratorTemplateModelBuilder._LayoutFileName);
}
expectedLayoutFile = expectedLayoutFile.Replace("\\", "/");
string existingLayoutFile = string.Empty;
if (leadTilde)
{
existingLayoutFile += "~";
}
if (leadSeparator)
{
existingLayoutFile += Path.DirectorySeparatorChar;
}
if (existingLayoutFileParts.Length > 0)
{
existingLayoutFile = existingLayoutFile + Path.Combine(existingLayoutFileParts);
}
IdentityGeneratorCommandLineModel commandLineModel = new IdentityGeneratorCommandLineModel();
commandLineModel.Layout = existingLayoutFile;
IApplicationInfo applicationInfo = new ApplicationInfo("test", LayoutFileLocationTestProjectBasePath);
CommonProjectContext context = new CommonProjectContext();
context.ProjectFullPath = LayoutFileLocationTestProjectBasePath;
context.ProjectName = "TestProject";
context.AssemblyName = "TestAssembly";
context.CompilationItems = new List<string>();
context.CompilationAssemblies = new List<ResolvedReference>();
Workspace workspace = new RoslynWorkspace(context);
ICodeGenAssemblyLoadContext assemblyLoadContext = new DefaultAssemblyLoadContext();
IFileSystem mockFileSystem = new MockFileSystem();
ILogger logger = new ConsoleLogger();
IdentityGeneratorTemplateModelBuilder modelBuilder = new IdentityGeneratorTemplateModelBuilder(commandLineModel, applicationInfo, context, workspace, assemblyLoadContext, mockFileSystem, logger);
modelBuilder.DetermineSupportFileLocation(out string supportFileLocation, out string layoutFile);
Assert.Equal(expectedSupportFileLocation, supportFileLocation);
Assert.Equal(expectedLayoutFile, layoutFile);
}
private Workspace GetWorkspace(string path)
{
_projectContext = new MsBuildProjectContextBuilder(path, "Dummy")