diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.bowerrc b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.bowerrc new file mode 100755 index 000000000..6406626ab --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "wwwroot/lib" +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.gitignore b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.gitignore new file mode 100755 index 000000000..0ca27f04e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/.gitignore @@ -0,0 +1,234 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/AccountController.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/AccountController.cs new file mode 100755 index 000000000..035638af9 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/AccountController.cs @@ -0,0 +1,468 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Logging; +using WebApplication.Models; +using WebApplication.Models.AccountViewModels; +using WebApplication.Services; + +namespace WebApplication.Controllers +{ + [Authorize] + public class AccountController : Controller + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + private readonly ISmsSender _smsSender; + private readonly ILogger _logger; + + public AccountController( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender, + ISmsSender smsSender, + ILoggerFactory loggerFactory) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + _smsSender = smsSender; + _logger = loggerFactory.CreateLogger(); + } + + // + // GET: /Account/Login + [HttpGet] + [AllowAnonymous] + public IActionResult Login(string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } + + // + // POST: /Account/Login + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model, string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + if (ModelState.IsValid) + { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + _logger.LogInformation(1, "User logged in."); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + } + if (result.IsLockedOut) + { + _logger.LogWarning(2, "User account locked out."); + return View("Lockout"); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid login attempt."); + return View(model); + } + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/Register + [HttpGet] + [AllowAnonymous] + public IActionResult Register(string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } + + // + // POST: /Account/Register + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Register(RegisterViewModel model, string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + if (ModelState.IsValid) + { + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await _userManager.CreateAsync(user, model.Password); + if (result.Succeeded) + { + // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713 + // Send an email with this link + //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); + //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", + // $"Please confirm your account by clicking this link: link"); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(3, "User created a new account with password."); + return RedirectToLocal(returnUrl); + } + AddErrors(result); + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // POST: /Account/LogOff + [HttpPost] + [ValidateAntiForgeryToken] + public async Task LogOff() + { + await _signInManager.SignOutAsync(); + _logger.LogInformation(4, "User logged out."); + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + + // + // POST: /Account/ExternalLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public IActionResult ExternalLogin(string provider, string returnUrl = null) + { + // Request a redirect to the external login provider. + var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return Challenge(properties, provider); + } + + // + // GET: /Account/ExternalLoginCallback + [HttpGet] + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) + { + if (remoteError != null) + { + ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); + return View(nameof(Login)); + } + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + return RedirectToAction(nameof(Login)); + } + + // Sign in the user with this external login provider if the user already has a login. + var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); + if (result.Succeeded) + { + _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + // If the user does not have an account, then ask the user to create an account. + ViewData["ReturnUrl"] = returnUrl; + ViewData["LoginProvider"] = info.LoginProvider; + var email = info.Principal.FindFirstValue(ClaimTypes.Email); + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + } + } + + // + // POST: /Account/ExternalLoginConfirmation + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) + { + if (ModelState.IsValid) + { + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + return View("ExternalLoginFailure"); + } + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await _userManager.CreateAsync(user); + if (result.Succeeded) + { + result = await _userManager.AddLoginAsync(user, info); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider); + return RedirectToLocal(returnUrl); + } + } + AddErrors(result); + } + + ViewData["ReturnUrl"] = returnUrl; + return View(model); + } + + // GET: /Account/ConfirmEmail + [HttpGet] + [AllowAnonymous] + public async Task ConfirmEmail(string userId, string code) + { + if (userId == null || code == null) + { + return View("Error"); + } + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return View("Error"); + } + var result = await _userManager.ConfirmEmailAsync(user, code); + return View(result.Succeeded ? "ConfirmEmail" : "Error"); + } + + // + // GET: /Account/ForgotPassword + [HttpGet] + [AllowAnonymous] + public IActionResult ForgotPassword() + { + return View(); + } + + // + // POST: /Account/ForgotPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(ForgotPasswordViewModel model) + { + if (ModelState.IsValid) + { + var user = await _userManager.FindByNameAsync(model.Email); + if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) + { + // Don't reveal that the user does not exist or is not confirmed + return View("ForgotPasswordConfirmation"); + } + + // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713 + // Send an email with this link + //var code = await _userManager.GeneratePasswordResetTokenAsync(user); + //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); + //await _emailSender.SendEmailAsync(model.Email, "Reset Password", + // $"Please reset your password by clicking here: link"); + //return View("ForgotPasswordConfirmation"); + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ForgotPasswordConfirmation + [HttpGet] + [AllowAnonymous] + public IActionResult ForgotPasswordConfirmation() + { + return View(); + } + + // + // GET: /Account/ResetPassword + [HttpGet] + [AllowAnonymous] + public IActionResult ResetPassword(string code = null) + { + return code == null ? View("Error") : View(); + } + + // + // POST: /Account/ResetPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await _userManager.FindByNameAsync(model.Email); + if (user == null) + { + // Don't reveal that the user does not exist + return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); + } + var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); + if (result.Succeeded) + { + return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); + } + AddErrors(result); + return View(); + } + + // + // GET: /Account/ResetPasswordConfirmation + [HttpGet] + [AllowAnonymous] + public IActionResult ResetPasswordConfirmation() + { + return View(); + } + + // + // GET: /Account/SendCode + [HttpGet] + [AllowAnonymous] + public async Task SendCode(string returnUrl = null, bool rememberMe = false) + { + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); + var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } + + // + // POST: /Account/SendCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task SendCode(SendCodeViewModel model) + { + if (!ModelState.IsValid) + { + return View(); + } + + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + + // Generate the token and send it + var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider); + if (string.IsNullOrWhiteSpace(code)) + { + return View("Error"); + } + + var message = "Your security code is: " + code; + if (model.SelectedProvider == "Email") + { + await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message); + } + else if (model.SelectedProvider == "Phone") + { + await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message); + } + + return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + } + + // + // GET: /Account/VerifyCode + [HttpGet] + [AllowAnonymous] + public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + { + // Require that the user has already logged in via username/password or external login + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } + + // + // POST: /Account/VerifyCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task VerifyCode(VerifyCodeViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + // The following code protects for brute force attacks against the two factor codes. + // If a user enters incorrect codes for a specified amount of time then the user account + // will be locked out for a specified amount of time. + var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); + if (result.Succeeded) + { + return RedirectToLocal(model.ReturnUrl); + } + if (result.IsLockedOut) + { + _logger.LogWarning(7, "User account locked out."); + return View("Lockout"); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid code."); + return View(model); + } + } + + #region Helpers + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + private Task GetCurrentUserAsync() + { + return _userManager.GetUserAsync(HttpContext.User); + } + + private IActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + else + { + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + } + + #endregion + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/HomeController.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/HomeController.cs new file mode 100755 index 000000000..4b21d0cce --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace WebApplication.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + + public IActionResult About() + { + ViewData["Message"] = "Your application description page."; + + return View(); + } + + public IActionResult Contact() + { + ViewData["Message"] = "Your contact page."; + + return View(); + } + + public IActionResult Error() + { + return View(); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/ManageController.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/ManageController.cs new file mode 100755 index 000000000..8d04fe58b --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Controllers/ManageController.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using WebApplication.Models; +using WebApplication.Models.ManageViewModels; +using WebApplication.Services; + +namespace WebApplication.Controllers +{ + [Authorize] + public class ManageController : Controller + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + private readonly ISmsSender _smsSender; + private readonly ILogger _logger; + + public ManageController( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender, + ISmsSender smsSender, + ILoggerFactory loggerFactory) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + _smsSender = smsSender; + _logger = loggerFactory.CreateLogger(); + } + + // + // GET: /Manage/Index + [HttpGet] + public async Task Index(ManageMessageId? message = null) + { + ViewData["StatusMessage"] = + message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." + : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." + : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." + : message == ManageMessageId.Error ? "An error has occurred." + : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." + : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." + : ""; + + var user = await GetCurrentUserAsync(); + var model = new IndexViewModel + { + HasPassword = await _userManager.HasPasswordAsync(user), + PhoneNumber = await _userManager.GetPhoneNumberAsync(user), + TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user), + Logins = await _userManager.GetLoginsAsync(user), + BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user) + }; + return View(model); + } + + // + // POST: /Manage/RemoveLogin + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemoveLogin(RemoveLoginViewModel account) + { + ManageMessageId? message = ManageMessageId.Error; + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + message = ManageMessageId.RemoveLoginSuccess; + } + } + return RedirectToAction(nameof(ManageLogins), new { Message = message }); + } + + // + // GET: /Manage/AddPhoneNumber + public IActionResult AddPhoneNumber() + { + return View(); + } + + // + // POST: /Manage/AddPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + // Generate the token and send it + var user = await GetCurrentUserAsync(); + var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber); + await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code); + return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber }); + } + + // + // POST: /Manage/EnableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EnableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + await _userManager.SetTwoFactorEnabledAsync(user, true); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(1, "User enabled two-factor authentication."); + } + return RedirectToAction(nameof(Index), "Manage"); + } + + // + // POST: /Manage/DisableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + await _userManager.SetTwoFactorEnabledAsync(user, false); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(2, "User disabled two-factor authentication."); + } + return RedirectToAction(nameof(Index), "Manage"); + } + + // + // GET: /Manage/VerifyPhoneNumber + [HttpGet] + public async Task VerifyPhoneNumber(string phoneNumber) + { + var code = await _userManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), phoneNumber); + // Send an SMS to verify the phone number + return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + } + + // + // POST: /Manage/VerifyPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess }); + } + } + // If we got this far, something failed, redisplay the form + ModelState.AddModelError(string.Empty, "Failed to verify phone number"); + return View(model); + } + + // + // POST: /Manage/RemovePhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemovePhoneNumber() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await _userManager.SetPhoneNumberAsync(user, null); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess }); + } + } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } + + // + // GET: /Manage/ChangePassword + [HttpGet] + public IActionResult ChangePassword() + { + return View(); + } + + // + // POST: /Manage/ChangePassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ChangePassword(ChangePasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(3, "User changed their password successfully."); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess }); + } + AddErrors(result); + return View(model); + } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } + + // + // GET: /Manage/SetPassword + [HttpGet] + public IActionResult SetPassword() + { + return View(); + } + + // + // POST: /Manage/SetPassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task SetPassword(SetPasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await _userManager.AddPasswordAsync(user, model.NewPassword); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess }); + } + AddErrors(result); + return View(model); + } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } + + //GET: /Manage/ManageLogins + [HttpGet] + public async Task ManageLogins(ManageMessageId? message = null) + { + ViewData["StatusMessage"] = + message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." + : message == ManageMessageId.AddLoginSuccess ? "The external login was added." + : message == ManageMessageId.Error ? "An error has occurred." + : ""; + var user = await GetCurrentUserAsync(); + if (user == null) + { + return View("Error"); + } + var userLogins = await _userManager.GetLoginsAsync(user); + var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList(); + ViewData["ShowRemoveButton"] = user.PasswordHash != null || userLogins.Count > 1; + return View(new ManageLoginsViewModel + { + CurrentLogins = userLogins, + OtherLogins = otherLogins + }); + } + + // + // POST: /Manage/LinkLogin + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult LinkLogin(string provider) + { + // Request a redirect to the external login provider to link a login for the current user + var redirectUrl = Url.Action("LinkLoginCallback", "Manage"); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); + return Challenge(properties, provider); + } + + // + // GET: /Manage/LinkLoginCallback + [HttpGet] + public async Task LinkLoginCallback() + { + var user = await GetCurrentUserAsync(); + if (user == null) + { + return View("Error"); + } + var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); + if (info == null) + { + return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error }); + } + var result = await _userManager.AddLoginAsync(user, info); + var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error; + return RedirectToAction(nameof(ManageLogins), new { Message = message }); + } + + #region Helpers + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + public enum ManageMessageId + { + AddPhoneSuccess, + AddLoginSuccess, + ChangePasswordSuccess, + SetTwoFactorSuccess, + SetPasswordSuccess, + RemoveLoginSuccess, + RemovePhoneSuccess, + Error + } + + private Task GetCurrentUserAsync() + { + return _userManager.GetUserAsync(HttpContext.User); + } + + #endregion + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/ApplicationDbContext.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/ApplicationDbContext.cs new file mode 100755 index 000000000..336e6d466 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/ApplicationDbContext.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using WebApplication.Models; + +namespace WebApplication.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100755 index 000000000..bb12d2bbb --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,212 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using WebApplication.Data; + +namespace WebApplication.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rc2-20901"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => + { + b.Property("Id"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasAnnotation("MaxLength", 256); + + b.Property("NormalizedName") + .HasAnnotation("MaxLength", 256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("WebApplication.Models.ApplicationUser", b => + { + b.Property("Id"); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasAnnotation("MaxLength", 256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasAnnotation("MaxLength", 256); + + b.Property("NormalizedUserName") + .HasAnnotation("MaxLength", 256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasAnnotation("MaxLength", 256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.cs new file mode 100755 index 000000000..e6f038f8e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace WebApplication.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + ConcurrencyStamp = table.Column(nullable: true), + Name = table.Column(nullable: true), + NormalizedName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(nullable: false), + Name = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false), + ConcurrencyStamp = table.Column(nullable: true), + Email = table.Column(nullable: true), + EmailConfirmed = table.Column(nullable: false), + LockoutEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + NormalizedEmail = table.Column(nullable: true), + NormalizedUserName = table.Column(nullable: true), + PasswordHash = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + SecurityStamp = table.Column(nullable: true), + TwoFactorEnabled = table.Column(nullable: false), + UserName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(nullable: false), + ProviderKey = table.Column(nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_UserId", + table: "AspNetUserRoles", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100755 index 000000000..cb459319d --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,211 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using WebApplication.Data; + +namespace WebApplication.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rc2-20901"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => + { + b.Property("Id"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasAnnotation("MaxLength", 256); + + b.Property("NormalizedName") + .HasAnnotation("MaxLength", 256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("WebApplication.Models.ApplicationUser", b => + { + b.Property("Id"); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasAnnotation("MaxLength", 256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasAnnotation("MaxLength", 256); + + b.Property("NormalizedUserName") + .HasAnnotation("MaxLength", 256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasAnnotation("MaxLength", 256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("WebApplication.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs new file mode 100755 index 000000000..a60894ce8 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class ExternalLoginConfirmationViewModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ForgotPasswordViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ForgotPasswordViewModel.cs new file mode 100755 index 000000000..70fab0c9f --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ForgotPasswordViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class ForgotPasswordViewModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/LoginViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/LoginViewModel.cs new file mode 100755 index 000000000..7dc974b6b --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/LoginViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class LoginViewModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/RegisterViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/RegisterViewModel.cs new file mode 100755 index 000000000..bc86f2aed --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/RegisterViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class RegisterViewModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ResetPasswordViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ResetPasswordViewModel.cs new file mode 100755 index 000000000..43198b7e5 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/ResetPasswordViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class ResetPasswordViewModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + + public string Code { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/SendCodeViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/SendCodeViewModel.cs new file mode 100755 index 000000000..b8ed8f1d4 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/SendCodeViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace WebApplication.Models.AccountViewModels +{ + public class SendCodeViewModel + { + public string SelectedProvider { get; set; } + + public ICollection Providers { get; set; } + + public string ReturnUrl { get; set; } + + public bool RememberMe { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/VerifyCodeViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/VerifyCodeViewModel.cs new file mode 100755 index 000000000..394db8c59 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/AccountViewModels/VerifyCodeViewModel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.AccountViewModels +{ + public class VerifyCodeViewModel + { + [Required] + public string Provider { get; set; } + + [Required] + public string Code { get; set; } + + public string ReturnUrl { get; set; } + + [Display(Name = "Remember this browser?")] + public bool RememberBrowser { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ApplicationUser.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ApplicationUser.cs new file mode 100755 index 000000000..4642ef2c3 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ApplicationUser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; + +namespace WebApplication.Models +{ + // Add profile data for application users by adding properties to the ApplicationUser class + public class ApplicationUser : IdentityUser + { + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/AddPhoneNumberViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/AddPhoneNumberViewModel.cs new file mode 100755 index 000000000..d2baaf7e0 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/AddPhoneNumberViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class AddPhoneNumberViewModel + { + [Required] + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ChangePasswordViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ChangePasswordViewModel.cs new file mode 100755 index 000000000..421b91a03 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ChangePasswordViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class ChangePasswordViewModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ConfigureTwoFactorViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ConfigureTwoFactorViewModel.cs new file mode 100755 index 000000000..beb1fd1a4 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ConfigureTwoFactorViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace WebApplication.Models.ManageViewModels +{ + public class ConfigureTwoFactorViewModel + { + public string SelectedProvider { get; set; } + + public ICollection Providers { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/FactorViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/FactorViewModel.cs new file mode 100755 index 000000000..b2d4f9e2f --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/FactorViewModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class FactorViewModel + { + public string Purpose { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/IndexViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/IndexViewModel.cs new file mode 100755 index 000000000..e0b69f2df --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/IndexViewModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; + +namespace WebApplication.Models.ManageViewModels +{ + public class IndexViewModel + { + public bool HasPassword { get; set; } + + public IList Logins { get; set; } + + public string PhoneNumber { get; set; } + + public bool TwoFactor { get; set; } + + public bool BrowserRemembered { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ManageLoginsViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ManageLoginsViewModel.cs new file mode 100755 index 000000000..fc03a0c27 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/ManageLoginsViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Identity; + +namespace WebApplication.Models.ManageViewModels +{ + public class ManageLoginsViewModel + { + public IList CurrentLogins { get; set; } + + public IList OtherLogins { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/RemoveLoginViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/RemoveLoginViewModel.cs new file mode 100755 index 000000000..394df34a0 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/RemoveLoginViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class RemoveLoginViewModel + { + public string LoginProvider { get; set; } + public string ProviderKey { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/SetPasswordViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/SetPasswordViewModel.cs new file mode 100755 index 000000000..76c1b4bb4 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/SetPasswordViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class SetPasswordViewModel + { + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/VerifyPhoneNumberViewModel.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/VerifyPhoneNumberViewModel.cs new file mode 100755 index 000000000..3c8c08c87 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Models/ManageViewModels/VerifyPhoneNumberViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Models.ManageViewModels +{ + public class VerifyPhoneNumberViewModel + { + [Required] + public string Code { get; set; } + + [Required] + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Program.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Program.cs new file mode 100755 index 000000000..74e9753fb --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace WebApplication +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/README.md b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/README.md new file mode 100755 index 000000000..d8ba0b382 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/README.md @@ -0,0 +1,39 @@ +# Welcome to ASP.NET Core + +We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. + +You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) + +## This application consists of: + +* Sample pages using ASP.NET Core MVC +* [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries +* Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) + +## How to + +* [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) +* [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) +* [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) +* [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) +* [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) +* [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) +* [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) + +## Overview + +* [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) +* [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) +* [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) +* [Security](https://go.microsoft.com/fwlink/?LinkId=398603) +* [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) +* [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) +* [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) + +## Run & Deploy + +* [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) +* [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) +* [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) + +We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/IEmailSender.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/IEmailSender.cs new file mode 100755 index 000000000..08fb35bad --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/IEmailSender.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Services +{ + public interface IEmailSender + { + Task SendEmailAsync(string email, string subject, string message); + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/ISmsSender.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/ISmsSender.cs new file mode 100755 index 000000000..8e57a2343 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/ISmsSender.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Services +{ + public interface ISmsSender + { + Task SendSmsAsync(string number, string message); + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/MessageServices.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/MessageServices.cs new file mode 100755 index 000000000..de54bfc79 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Services/MessageServices.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication.Services +{ + // This class is used by the application to send Email and SMS + // when you turn on two-factor authentication in ASP.NET Identity. + // For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713 + public class AuthMessageSender : IEmailSender, ISmsSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // Plug in your email service here to send an email. + return Task.FromResult(0); + } + + public Task SendSmsAsync(string number, string message) + { + // Plug in your SMS service here to send a text message. + return Task.FromResult(0); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Startup.cs b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Startup.cs new file mode 100755 index 000000000..2fa7ae51b --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Startup.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using WebApplication.Data; +using WebApplication.Models; +using WebApplication.Services; + +namespace WebApplication +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); + + if (env.IsDevelopment()) + { + // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709 + builder.AddUserSecrets(); + } + + builder.AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddDbContext(options => + options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"))); + + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddMvc(); + + // Add application services. + services.AddTransient(); + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseDatabaseErrorPage(); + app.UseBrowserLink(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseStaticFiles(); + + app.UseIdentity(); + + // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715 + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ConfirmEmail.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ConfirmEmail.cshtml new file mode 100755 index 000000000..8e8088d44 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ConfirmEmail.cshtml @@ -0,0 +1,10 @@ +@{ + ViewData["Title"] = "Confirm Email"; +} + +

@ViewData["Title"].

+
+

+ Thank you for confirming your email. Please Click here to Log in. +

+
diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginConfirmation.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginConfirmation.cshtml new file mode 100755 index 000000000..eb3612b55 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginConfirmation.cshtml @@ -0,0 +1,35 @@ +@model ExternalLoginConfirmationViewModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"].

+

Associate your @ViewData["LoginProvider"] account.

+ +
+

Association Form

+
+
+ +

+ You've successfully authenticated with @ViewData["LoginProvider"]. + Please enter an email address for this site below and click the Register button to finish + logging in. +

+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginFailure.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginFailure.cshtml new file mode 100755 index 000000000..2509746be --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ExternalLoginFailure.cshtml @@ -0,0 +1,8 @@ +@{ + ViewData["Title"] = "Login Failure"; +} + +
+

@ViewData["Title"].

+

Unsuccessful login with service.

+
diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPassword.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPassword.cshtml new file mode 100755 index 000000000..9d748023f --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPassword.cshtml @@ -0,0 +1,31 @@ +@model ForgotPasswordViewModel +@{ + ViewData["Title"] = "Forgot your password?"; +} + +

@ViewData["Title"]

+

+ For more information on how to enable reset password please see this article. +

+ +@*
+

Enter your email.

+
+
+
+ +
+ + +
+
+
+
+ +
+
+
*@ + +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPasswordConfirmation.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPasswordConfirmation.cshtml new file mode 100755 index 000000000..4877fc83a --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ForgotPasswordConfirmation.cshtml @@ -0,0 +1,8 @@ +@{ + ViewData["Title"] = "Forgot Password Confirmation"; +} + +

@ViewData["Title"].

+

+ Please check your email to reset your password. +

diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Lockout.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Lockout.cshtml new file mode 100755 index 000000000..34ac56ff3 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Lockout.cshtml @@ -0,0 +1,8 @@ +@{ + ViewData["Title"] = "Locked out"; +} + +
+

Locked out.

+

This account has been locked out, please try again later.

+
diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Login.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Login.cshtml new file mode 100755 index 000000000..95430a12e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Login.cshtml @@ -0,0 +1,92 @@ +@using System.Collections.Generic +@using Microsoft.AspNetCore.Http +@using Microsoft.AspNetCore.Http.Authentication +@model LoginViewModel +@inject SignInManager SignInManager + +@{ + ViewData["Title"] = "Log in"; +} + +

@ViewData["Title"].

+
+
+
+
+

Use a local account to log in.

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+

+ Register as a new user? +

+

+ Forgot your password? +

+
+
+
+
+
+

Use another service to log in.

+
+ @{ + var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList(); + if (loginProviders.Count == 0) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in loginProviders) + { + + } +

+
+
+ } + } +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Register.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Register.cshtml new file mode 100755 index 000000000..2090f900e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/Register.cshtml @@ -0,0 +1,42 @@ +@model RegisterViewModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"].

+ +
+

Create a new account.

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPassword.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPassword.cshtml new file mode 100755 index 000000000..dd716d735 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPassword.cshtml @@ -0,0 +1,43 @@ +@model ResetPasswordViewModel +@{ + ViewData["Title"] = "Reset password"; +} + +

@ViewData["Title"].

+ +
+

Reset your password.

+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPasswordConfirmation.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPasswordConfirmation.cshtml new file mode 100755 index 000000000..6321d858e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/ResetPasswordConfirmation.cshtml @@ -0,0 +1,8 @@ +@{ + ViewData["Title"] = "Reset password confirmation"; +} + +

@ViewData["Title"].

+

+ Your password has been reset. Please Click here to log in. +

diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/SendCode.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/SendCode.cshtml new file mode 100755 index 000000000..e85ca3c2b --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/SendCode.cshtml @@ -0,0 +1,21 @@ +@model SendCodeViewModel +@{ + ViewData["Title"] = "Send Verification Code"; +} + +

@ViewData["Title"].

+ +
+ +
+
+ Select Two-Factor Authentication Provider: + + +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/VerifyCode.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/VerifyCode.cshtml new file mode 100755 index 000000000..60afb361d --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Account/VerifyCode.cshtml @@ -0,0 +1,38 @@ +@model VerifyCodeViewModel +@{ + ViewData["Title"] = "Verify"; +} + +

@ViewData["Title"].

+ +
+
+ + +

@ViewData["Status"]

+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/About.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/About.cshtml new file mode 100755 index 000000000..b653a26f1 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/About.cshtml @@ -0,0 +1,7 @@ +@{ + ViewData["Title"] = "About"; +} +

@ViewData["Title"].

+

@ViewData["Message"]

+ +

Use this area to provide additional information.

diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Contact.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Contact.cshtml new file mode 100755 index 000000000..f953aa63d --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Contact.cshtml @@ -0,0 +1,17 @@ +@{ + ViewData["Title"] = "Contact"; +} +

@ViewData["Title"].

+

@ViewData["Message"]

+ +
+ One Microsoft Way
+ Redmond, WA 98052-6399
+ P: + 425.555.0100 +
+ +
+ Support: Support@example.com
+ Marketing: Marketing@example.com +
diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Index.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Index.cshtml new file mode 100755 index 000000000..957b8c1da --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Home/Index.cshtml @@ -0,0 +1,109 @@ +@{ + ViewData["Title"] = "Home Page"; +} + + + + diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/AddPhoneNumber.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/AddPhoneNumber.cshtml new file mode 100755 index 000000000..2feb93b22 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/AddPhoneNumber.cshtml @@ -0,0 +1,27 @@ +@model AddPhoneNumberViewModel +@{ + ViewData["Title"] = "Add Phone Number"; +} + +

@ViewData["Title"].

+
+

Add a phone number.

+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ChangePassword.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ChangePassword.cshtml new file mode 100755 index 000000000..41c7960c8 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ChangePassword.cshtml @@ -0,0 +1,42 @@ +@model ChangePasswordViewModel +@{ + ViewData["Title"] = "Change Password"; +} + +

@ViewData["Title"].

+ +
+

Change Password Form

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/Index.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/Index.cshtml new file mode 100755 index 000000000..8419b2429 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/Index.cshtml @@ -0,0 +1,71 @@ +@model IndexViewModel +@{ + ViewData["Title"] = "Manage your account"; +} + +

@ViewData["Title"].

+

@ViewData["StatusMessage"]

+ +
+

Change your account settings

+
+
+
Password:
+
+ @if (Model.HasPassword) + { + Change + } + else + { + Create + } +
+
External Logins:
+
+ + @Model.Logins.Count Manage +
+
Phone Number:
+
+

+ Phone Numbers can used as a second factor of verification in two-factor authentication. + See this article + for details on setting up this ASP.NET application to support two-factor authentication using SMS. +

+ @*@(Model.PhoneNumber ?? "None") + @if (Model.PhoneNumber != null) + { +
+ Change +
+ [] +
+ } + else + { + Add + }*@ +
+ +
Two-Factor Authentication:
+
+

+ There are no two-factor authentication providers configured. See this article + for setting up this application to support two-factor authentication. +

+ @*@if (Model.TwoFactor) + { +
+ Enabled +
+ } + else + { +
+ Disabled +
+ }*@ +
+
+
diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ManageLogins.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ManageLogins.cshtml new file mode 100755 index 000000000..35e12da68 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/ManageLogins.cshtml @@ -0,0 +1,54 @@ +@model ManageLoginsViewModel +@using Microsoft.AspNetCore.Http.Authentication +@{ + ViewData["Title"] = "Manage your external logins"; +} + +

@ViewData["Title"].

+ +

@ViewData["StatusMessage"]

+@if (Model.CurrentLogins.Count > 0) +{ +

Registered Logins

+ + + @for (var index = 0; index < Model.CurrentLogins.Count; index++) + { + + + + + } + +
@Model.CurrentLogins[index].LoginProvider + @if ((bool)ViewData["ShowRemoveButton"]) + { +
+
+ + + +
+
+ } + else + { + @:   + } +
+} +@if (Model.OtherLogins.Count > 0) +{ +

Add another service to log in.

+
+
+
+

+ @foreach (var provider in Model.OtherLogins) + { + + } +

+
+
+} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/SetPassword.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/SetPassword.cshtml new file mode 100755 index 000000000..cfa779160 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/SetPassword.cshtml @@ -0,0 +1,38 @@ +@model SetPasswordViewModel +@{ + ViewData["Title"] = "Set Password"; +} + +

+ You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +

+ +
+

Set your password

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/VerifyPhoneNumber.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/VerifyPhoneNumber.cshtml new file mode 100755 index 000000000..af7cd0b1f --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Manage/VerifyPhoneNumber.cshtml @@ -0,0 +1,30 @@ +@model VerifyPhoneNumberViewModel +@{ + ViewData["Title"] = "Verify Phone Number"; +} + +

@ViewData["Title"].

+ +
+ +

Add a phone number.

+
@ViewData["Status"]
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/Error.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/Error.cshtml new file mode 100755 index 000000000..229c2dead --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/Error.cshtml @@ -0,0 +1,14 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_Layout.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_Layout.cshtml new file mode 100755 index 000000000..56827ca52 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_Layout.cshtml @@ -0,0 +1,68 @@ + + + + + + @ViewData["Title"] - WebApplication + + + + + + + + + + + + +
+ @RenderBody() +
+
+

© 2016 - WebApplication

+
+
+ + + + + + + + + + + + + @RenderSection("scripts", required: false) + + diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_LoginPartial.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_LoginPartial.cshtml new file mode 100755 index 000000000..f50d5e89e --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_LoginPartial.cshtml @@ -0,0 +1,26 @@ +@using Microsoft.AspNetCore.Identity +@using WebApplication.Models + +@inject SignInManager SignInManager +@inject UserManager UserManager + +@if (SignInManager.IsSignedIn(User)) +{ + +} +else +{ + +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_ValidationScriptsPartial.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100755 index 000000000..289b22064 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewImports.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewImports.cshtml new file mode 100755 index 000000000..dcca16cb0 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewImports.cshtml @@ -0,0 +1,6 @@ +@using WebApplication +@using WebApplication.Models +@using WebApplication.Models.AccountViewModels +@using WebApplication.Models.ManageViewModels +@using Microsoft.AspNetCore.Identity +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewStart.cshtml b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewStart.cshtml new file mode 100755 index 000000000..820a2f6e0 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/appsettings.json b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/appsettings.json new file mode 100755 index 000000000..53b17ae04 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=WebApplication.db" + }, + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/bower.json b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/bower.json new file mode 100755 index 000000000..3891fce13 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/bower.json @@ -0,0 +1,10 @@ +{ + "name": "webapplication", + "private": true, + "dependencies": { + "bootstrap": "3.3.6", + "jquery": "2.2.3", + "jquery-validation": "1.15.0", + "jquery-validation-unobtrusive": "3.2.6" + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/gulpfile.js b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/gulpfile.js new file mode 100755 index 000000000..faf295540 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/gulpfile.js @@ -0,0 +1,45 @@ +/// +"use strict"; + +var gulp = require("gulp"), + rimraf = require("rimraf"), + concat = require("gulp-concat"), + cssmin = require("gulp-cssmin"), + uglify = require("gulp-uglify"); + +var webroot = "./wwwroot/"; + +var paths = { + js: webroot + "js/**/*.js", + minJs: webroot + "js/**/*.min.js", + css: webroot + "css/**/*.css", + minCss: webroot + "css/**/*.min.css", + concatJsDest: webroot + "js/site.min.js", + concatCssDest: webroot + "css/site.min.css" +}; + +gulp.task("clean:js", function (cb) { + rimraf(paths.concatJsDest, cb); +}); + +gulp.task("clean:css", function (cb) { + rimraf(paths.concatCssDest, cb); +}); + +gulp.task("clean", ["clean:js", "clean:css"]); + +gulp.task("min:js", function () { + return gulp.src([paths.js, "!" + paths.minJs], { base: "." }) + .pipe(concat(paths.concatJsDest)) + .pipe(uglify()) + .pipe(gulp.dest(".")); +}); + +gulp.task("min:css", function () { + return gulp.src([paths.css, "!" + paths.minCss]) + .pipe(concat(paths.concatCssDest)) + .pipe(cssmin()) + .pipe(gulp.dest(".")); +}); + +gulp.task("min", ["min:js", "min:css"]); diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/package.json b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/package.json new file mode 100755 index 000000000..4bb6c884c --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/package.json @@ -0,0 +1,12 @@ +{ + "name": "webapplication", + "version": "0.0.0", + "private": true, + "devDependencies": { + "gulp": "3.9.1", + "gulp-concat": "2.6.0", + "gulp-cssmin": "0.1.7", + "gulp-uglify": "1.5.3", + "rimraf": "2.5.2" + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/project.json b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/project.json new file mode 100755 index 000000000..f43360c74 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/project.json @@ -0,0 +1,106 @@ +{ + "userSecretsId": "aspnet-WebApplication-0799fe3e-6eaf-4c5f-b40e-7c6bfd5dfa9a", + + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + }, + "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", + "Microsoft.AspNetCore.Diagnostics": "1.0.0", + "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0", + "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0", + "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Razor.Tools": { + "version": "1.0.0-preview2-final", + "type": "build" + }, + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.StaticFiles": "1.0.0", + "Microsoft.EntityFrameworkCore.Sqlite": "1.0.0", + "Microsoft.EntityFrameworkCore.Tools": { + "version": "1.0.0-preview2-final", + "type": "build" + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", + "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", + "Microsoft.Extensions.Logging.Debug": "1.0.0", + "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", + "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { + "version": "1.0.0-preview2-final", + "type": "build" + }, + "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { + "version": "1.0.0-preview2-final", + "type": "build" + } + }, + + "tools": { + "Microsoft.AspNetCore.Razor.Tools": { + "version": "1.0.0-preview2-final", + "imports": "portable-net45+win8+dnxcore50" + }, + "Microsoft.AspNetCore.Server.IISIntegration.Tools": { + "version": "1.0.0-preview2-final", + "imports": "portable-net45+win8+dnxcore50" + }, + "Microsoft.EntityFrameworkCore.Tools": { + "version": "1.0.0-preview2-final", + "imports": [ + "portable-net45+win8+dnxcore50", + "portable-net45+win8" + ] + }, + "Microsoft.Extensions.SecretManager.Tools": { + "version": "1.0.0-preview2-final", + "imports": "portable-net45+win8+dnxcore50" + }, + "Microsoft.VisualStudio.Web.CodeGeneration.Tools": { + "version": "1.0.0-preview2-final", + "imports": [ + "portable-net45+win8+dnxcore50", + "portable-net45+win8" + ] + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dotnet5.6", + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "buildOptions": { + "debugType": "portable", + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + + "runtimeOptions": { + "configProperties": { + "System.GC.Server": true + } + }, + + "publishOptions": { + "include": [ + "wwwroot", + "Views", + "appsettings.json", + "web.config" + ] + }, + + "tooling": { + "defaultNamespace": "WebApplication" + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/web.config b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/web.config new file mode 100755 index 000000000..a8d667275 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/web.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.css b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.css new file mode 100755 index 000000000..6baa84da1 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.css @@ -0,0 +1,44 @@ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Wrapping element */ +/* Set some basic padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Set widths on the form inputs since otherwise they're 100% wide */ +input, +select, +textarea { + max-width: 280px; +} + +/* Carousel */ +.carousel-caption p { + font-size: 20px; + line-height: 1.4; +} + +/* buttons and links extension to use brackets: [ click me ] */ +.btn-bracketed::before { + display:inline-block; + content: "["; + padding-right: 0.5em; +} +.btn-bracketed::after { + display:inline-block; + content: "]"; + padding-left: 0.5em; +} + +/* Hide/rearrange for smaller screens */ +@media screen and (max-width: 767px) { + /* Hide captions */ + .carousel-caption { + display: none + } +} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.min.css b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.min.css new file mode 100755 index 000000000..c8f600ac5 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/css/site.min.css @@ -0,0 +1 @@ +body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.btn-bracketed::before{display:inline-block;content:"[";padding-right:.5em}.btn-bracketed::after{display:inline-block;content:"]";padding-left:.5em}@media screen and (max-width:767px){.carousel-caption{display:none}} diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/favicon.ico b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/favicon.ico new file mode 100755 index 000000000..a3a799985 Binary files /dev/null and b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/favicon.ico differ diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner1.svg b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner1.svg new file mode 100755 index 000000000..1ab32b60b --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner2.svg b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner2.svg new file mode 100755 index 000000000..9679c604d --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner3.svg b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner3.svg new file mode 100755 index 000000000..9be2c2503 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner3.svg @@ -0,0 +1 @@ +banner3b \ No newline at end of file diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner4.svg b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner4.svg new file mode 100755 index 000000000..38b3d7cd1 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/images/banner4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/js/site.js b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/js/site.js new file mode 100755 index 000000000..e069226a1 --- /dev/null +++ b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/js/site.js @@ -0,0 +1 @@ +// Write your Javascript code. diff --git a/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/js/site.min.js b/TestAssets/TestProjects/WebAppWithMissingFileInPublishOptions/wwwroot/js/site.min.js new file mode 100755 index 000000000..e69de29bb diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs index 1cd3440a5..bdee534d4 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs @@ -208,9 +208,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration if (existingMetadata == default(ProjectMetadataElement)) { #if !DISABLE_TRACE - MigrationTrace.Instance.WriteLine($"{nameof(AddMetadata)}: Adding metadata to {item.ItemType} item: {{ {metadata.Name}, {metadata.Value} }}"); + MigrationTrace.Instance.WriteLine($"{nameof(AddMetadata)}: Adding metadata to {item.ItemType} item: {{ {metadata.Name}, {metadata.Value}, {metadata.Condition} }}"); #endif - item.AddMetadata(metadata.Name, metadata.Value); + var metametadata = item.AddMetadata(metadata.Name, metadata.Value); + metametadata.Condition = metadata.Condition; } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs index 1fd6f77bb..548dea277 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/ItemMetadataValue.cs @@ -8,22 +8,41 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Models internal class ItemMetadataValue { public string MetadataName { get; } + public string Condition { get; } - private readonly string _metadataValue; private readonly Func _metadataValueFunc; private readonly Func _writeMetadataConditionFunc; - public ItemMetadataValue(string metadataName, string metadataValue) + public ItemMetadataValue( + string metadataName, + string metadataValue, + string condition = null) : + this(metadataName, + _ => metadataValue, + condition: condition) { - MetadataName = metadataName; - _metadataValue = metadataValue; } - public ItemMetadataValue(string metadataName, Func metadataValueFunc, Func writeMetadataConditionFunc = null) + public ItemMetadataValue( + string metadataName, + Func metadataValueFunc, + Func writeMetadataConditionFunc = null, + string condition = null) { + if (metadataName == null) + { + throw new ArgumentNullException(nameof(metadataName)); + } + + if (metadataValueFunc == null) + { + throw new ArgumentNullException(nameof(metadataValueFunc)); + } + MetadataName = metadataName; _metadataValueFunc = metadataValueFunc; _writeMetadataConditionFunc = writeMetadataConditionFunc; + Condition = condition; } public bool ShouldWriteMetadata(T source) @@ -33,7 +52,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Models public string GetMetadataValue(T source) { - return _metadataValue ?? _metadataValueFunc(source); + return _metadataValueFunc(source); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs index edb9582b6..6f174e6b9 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePublishOptionsRule.cs @@ -34,6 +34,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules private IncludeContextTransform CopyToOutputFilesTransform => new IncludeContextTransform("Content", transformMappings: true) - .WithMetadata("CopyToPublishDirectory", "PreserveNewest"); + .WithMetadata("CopyToPublishDirectory", "PreserveNewest", "Exists(%(Identity))"); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs index 59505d3c0..5a68752d8 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/AddItemTransform.cs @@ -109,7 +109,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { if (metadata.ShouldWriteMetadata(source)) { - item.AddMetadata(metadata.MetadataName, metadata.GetMetadataValue(source)); + var metametadata = item.AddMetadata(metadata.MetadataName, metadata.GetMetadataValue(source)); + metametadata.Condition = metadata.Condition; } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs index 3a6183d03..f53fd6500 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/IncludeContextTransform.cs @@ -81,15 +81,24 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms }; } - public IncludeContextTransform WithMetadata(string metadataName, string metadataValue) + public IncludeContextTransform WithMetadata( + string metadataName, + string metadataValue, + string condition = null) { - _metadata.Add(new ItemMetadataValue(metadataName, metadataValue)); + _metadata.Add(new ItemMetadataValue(metadataName, metadataValue, condition)); return this; } - public IncludeContextTransform WithMetadata(string metadataName, Func metadataValueFunc) + public IncludeContextTransform WithMetadata( + string metadataName, + Func metadataValueFunc, + string condition = null) { - _metadata.Add(new ItemMetadataValue(metadataName, metadataValueFunc)); + _metadata.Add(new ItemMetadataValue( + metadataName, + metadataValueFunc, + condition: condition)); return this; } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs index 0f97f9c45..a32f62d2f 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigratePublishOptions.cs @@ -16,7 +16,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests public class GivenThatIWantToMigratePublishOptions : TestBase { [Fact] - private void Migrating_publishOptions_include_exclude_populates_Content_item() + private void MigratingPublishOptionsIncludeExcludePopulatesContentItem() { var testDirectory = Temp.CreateDirectory().Path; WriteFilesInProjectDirectory(testDirectory); @@ -55,7 +55,36 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests } [Fact] - private void Migrating_publishOptions_and_buildOptions_CopyToOutput_merges_Content_items() + public void MigratingPublishOptionsIncludeEmitsConditionalAttribute() + { + + var mockProj = RunPublishAndBuildOptionsRuleOnPj(@" + { + ""publishOptions"": { + ""include"": [ + ""appsettings.json"", + ""appsettings.Production.json"", + ""dist"", + ""Dockerfile"", + ""hosting.json"", + ""web.config"" + ] + } + }"); + + mockProj.Items + .Should() + .ContainSingle(i => i.ItemType == "Content") + .Which + .Metadata + .Should() + .ContainSingle(m => m.Name == "CopyToPublishDirectory" && + m.Condition == "Exists(%(Identity))"); + + } + + [Fact] + private void MigratingPublishOptionsAndBuildOptionsCopyToOutputMergesContentItems() { var testDirectory = Temp.CreateDirectory().Path; WriteFilesInProjectDirectory(testDirectory); diff --git a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs index 750fe1f96..673a1c000 100644 --- a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs +++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs @@ -123,6 +123,23 @@ namespace Microsoft.DotNet.Migration.Tests outputsIdentical.Should().BeTrue(); } + [Fact] + public void ItMigratesAndPublishesWebAppWithFilesThatDoNotExistInPublishOptions() + { + const string projectName = "WebAppWithMissingFileInPublishOptions"; + var testInstance = TestAssets.Get(projectName) + .CreateInstance() + .WithSourceFiles(); + + var projectDirectory = testInstance.Root.FullName; + + MigrateProject(new [] { projectDirectory }); + + Restore(projectDirectory); + PublishMSBuild(projectDirectory, projectName); + } + + [Fact] public void ItAddsMicrosoftNetWebSdkToTheSdkAttributeOfAWebApp() { var testInstance = TestAssetsManager @@ -722,7 +739,7 @@ namespace Microsoft.DotNet.Migration.Tests private string PublishMSBuild( string projectDirectory, string projectName, - string runtime, + string runtime = null, string configuration = "Debug") { if (projectName != null)