diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index 00ff7c8afa..b93e990ef5 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Globalization; @@ -631,6 +632,14 @@ await PackageDeleteService.HardDeletePackagesAsync( } } + // Perform all the validations we can before adding the package to the entity context. + var beforeValidationResult = await PackageUploadService.ValidateBeforeGeneratePackageAsync(packageToPush); + var beforeValidationActionResult = GetActionResultOrNull(beforeValidationResult); + if (beforeValidationActionResult != null) + { + return beforeValidationActionResult; + } + var packageStreamMetadata = new PackageStreamMetadata { HashAlgorithm = CoreConstants.Sha512HashAlgorithmId, @@ -657,21 +666,16 @@ await PackageDeleteService.HardDeletePackagesAsync( return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, packagePolicyResult.ErrorMessage); } - var validationResult = await PackageUploadService.ValidatePackageAsync( + // Perform validations that require the package already being in the entity context. + var afterValidationResult = await PackageUploadService.ValidateAfterGeneratePackageAsync( package, packageToPush, owner, currentUser); - switch (validationResult.Type) + var afterValidationActionResult = GetActionResultOrNull(afterValidationResult); + if (afterValidationActionResult != null) { - case PackageValidationResultType.Accepted: - break; - case PackageValidationResultType.Invalid: - case PackageValidationResultType.PackageShouldNotBeSigned: - case PackageValidationResultType.PackageShouldNotBeSignedButCanManageCertificates: - return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, validationResult.Message); - default: - throw new NotImplementedException($"The package validation result type {validationResult.Type} is not supported."); + return afterValidationActionResult; } await AutoCuratePackage.ExecuteAsync(package, packageToPush, commitChanges: false); @@ -724,12 +728,15 @@ await AuditingService.SaveAuditRecordAsync( TelemetryService.TrackPackagePushEvent(package, currentUser, User.Identity); + var warnings = new List(); + warnings.AddRange(beforeValidationResult.Warnings); + warnings.AddRange(afterValidationResult.Warnings); if (package.SemVerLevelKey == SemVerLevelKey.SemVer2) { - return new HttpStatusCodeWithServerWarningResult(HttpStatusCode.Created, Strings.WarningSemVer2PackagePushed); + warnings.Add(Strings.WarningSemVer2PackagePushed); } - return new HttpStatusCodeResult(HttpStatusCode.Created); + return new HttpStatusCodeWithServerWarningResult(HttpStatusCode.Created, warnings); } } catch (InvalidPackageException ex) @@ -764,6 +771,21 @@ await AuditingService.SaveAuditRecordAsync( } } + private static ActionResult GetActionResultOrNull(PackageValidationResult validationResult) + { + switch (validationResult.Type) + { + case PackageValidationResultType.Accepted: + return null; + case PackageValidationResultType.Invalid: + case PackageValidationResultType.PackageShouldNotBeSigned: + case PackageValidationResultType.PackageShouldNotBeSignedButCanManageCertificates: + return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, validationResult.Message); + default: + throw new NotImplementedException($"The package validation result type {validationResult.Type} is not supported."); + } + } + private static ActionResult BadRequestForExceptionMessage(Exception ex) { return new HttpStatusCodeWithBodyResult( diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs index a22b9859f7..694c01804e 100644 --- a/src/NuGetGallery/Controllers/PackagesController.cs +++ b/src/NuGetGallery/Controllers/PackagesController.cs @@ -160,13 +160,20 @@ public virtual async Task UploadPackage() { if (uploadedFile != null) { - var package = await SafeCreatePackage(currentUser, uploadedFile); if (package == null) { return View(model); } + var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(package); + var validationErrorMessage = GetErrorMessageOrNull(validationResult); + if (validationErrorMessage != null) + { + TempData["Message"] = validationErrorMessage; + return View(model); + } + try { packageMetadata = PackageMetadata.FromNuspecReader( @@ -181,8 +188,6 @@ public virtual async Task UploadPackage() return View(model); } - model.IsUploadInProgress = true; - var existingPackageRegistration = _packageService.FindPackageRegistrationById(packageMetadata.Id); bool isAllowed; IEnumerable accountsAllowedOnBehalfOf = Enumerable.Empty(); @@ -204,6 +209,7 @@ public virtual async Task UploadPackage() } var verifyRequest = new VerifyPackageRequest(packageMetadata, accountsAllowedOnBehalfOf, existingPackageRegistration); + verifyRequest.Warnings.AddRange(validationResult.Warnings); model.InProgressUpload = verifyRequest; } @@ -230,13 +236,11 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil if (uploadFile == null) { - ModelState.AddModelError(String.Empty, Strings.UploadFileIsRequired); return Json(HttpStatusCode.BadRequest, new[] { Strings.UploadFileIsRequired }); } if (!Path.GetExtension(uploadFile.FileName).Equals(CoreConstants.NuGetPackageFileExtension, StringComparison.OrdinalIgnoreCase)) { - ModelState.AddModelError(String.Empty, Strings.UploadFileMustBeNuGetPackage); return Json(HttpStatusCode.BadRequest, new[] { Strings.UploadFileMustBeNuGetPackage }); } @@ -257,11 +261,6 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil if (entryInTheFuture != null) { - ModelState.AddModelError(String.Empty, string.Format( - CultureInfo.CurrentCulture, - Strings.PackageEntryFromTheFuture, - entryInTheFuture.Name)); - return Json(HttpStatusCode.BadRequest, new[] { string.Format(CultureInfo.CurrentCulture, Strings.PackageEntryFromTheFuture, entryInTheFuture.Name) }); } @@ -284,8 +283,6 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil message = ex.Message; } - ModelState.AddModelError(String.Empty, message); - return Json(HttpStatusCode.BadRequest, new[] { message }); } finally @@ -301,22 +298,14 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil foreach (var error in errors) { errorStrings.Add(error.ErrorMessage); - ModelState.AddModelError(String.Empty, error.ErrorMessage); } - return Json(HttpStatusCode.BadRequest, errorStrings); + return Json(HttpStatusCode.BadRequest, errorStrings.ToArray()); } // Check min client version if (nuspec.GetMinClientVersion() > Constants.MaxSupportedMinClientVersion) { - ModelState.AddModelError( - string.Empty, - string.Format( - CultureInfo.CurrentCulture, - Strings.UploadPackage_MinClientVersionOutOfRange, - nuspec.GetMinClientVersion())); - return Json(HttpStatusCode.BadRequest, new[] { string.Format(CultureInfo.CurrentCulture, Strings.UploadPackage_MinClientVersionOutOfRange, nuspec.GetMinClientVersion()) }); } @@ -328,9 +317,6 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil ActionsRequiringPermissions.UploadNewPackageId.CheckPermissionsOnBehalfOfAnyAccount( currentUser, new ActionOnNewPackageContext(id, _reservedNamespaceService), out accountsAllowedOnBehalfOf) != PermissionsCheckResult.Allowed) { - ModelState.AddModelError( - string.Empty, string.Format(CultureInfo.CurrentCulture, Strings.UploadPackage_IdNamespaceConflict)); - var version = nuspec.GetVersion().ToNormalizedString(); _telemetryService.TrackPackagePushNamespaceConflictEvent(id, version, currentUser, User.Identity); @@ -343,9 +329,6 @@ public virtual async Task UploadPackage(HttpPostedFileBase uploadFil if (ActionsRequiringPermissions.UploadNewPackageVersion.CheckPermissionsOnBehalfOfAnyAccount( currentUser, existingPackageRegistration, out accountsAllowedOnBehalfOf) != PermissionsCheckResult.Allowed) { - ModelState.AddModelError( - string.Empty, string.Format(CultureInfo.CurrentCulture, Strings.PackageIdNotAvailable, existingPackageRegistration.Id)); - return Json(HttpStatusCode.Conflict, new[] { string.Format(CultureInfo.CurrentCulture, Strings.PackageIdNotAvailable, existingPackageRegistration.Id) }); } @@ -395,10 +378,6 @@ await _packageDeleteService.HardDeletePackagesAsync( existingPackage.Version); } - ModelState.AddModelError( - string.Empty, - message); - return Json(HttpStatusCode.Conflict, new[] { message }); } } @@ -407,11 +386,11 @@ await _packageDeleteService.HardDeletePackagesAsync( } PackageMetadata packageMetadata; + IReadOnlyList warnings; using (Stream uploadedFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { if (uploadedFile == null) { - ModelState.AddModelError(String.Empty, Strings.UploadFileIsRequired); return Json(HttpStatusCode.BadRequest, new[] { Strings.UploadFileIsRequired }); } @@ -433,9 +412,19 @@ await _packageDeleteService.HardDeletePackagesAsync( return Json(HttpStatusCode.BadRequest, new[] { ex.GetUserSafeMessage() }); } + + var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(package); + var validationJsonResult = GetJsonResultOrNull(validationResult); + if (validationJsonResult != null) + { + return validationJsonResult; + } + + warnings = validationResult.Warnings; } var model = new VerifyPackageRequest(packageMetadata, accountsAllowedOnBehalfOf, existingPackageRegistration); + model.Warnings.AddRange(warnings); return Json(model); } @@ -1602,6 +1591,14 @@ public virtual async Task VerifyPackage(VerifyPackageRequest formDat } } + // Perform all the validations we can before adding the package to the entity context. + var beforeValidationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(nugetPackage); + var beforeValidationJsonResult = GetJsonResultOrNull(beforeValidationResult); + if (beforeValidationJsonResult != null) + { + return beforeValidationJsonResult; + } + // update relevant database tables try { @@ -1621,28 +1618,16 @@ public virtual async Task VerifyPackage(VerifyPackageRequest formDat return Json(HttpStatusCode.BadRequest, new[] { ex.Message }); } - var validationResult = await _packageUploadService.ValidatePackageAsync( + // Perform validations that require the package already being in the entity context. + var afterValidationResult = await _packageUploadService.ValidateAfterGeneratePackageAsync( package, nugetPackage, owner, currentUser); - switch (validationResult.Type) + var afterValidationJsonResult = GetJsonResultOrNull(afterValidationResult); + if (afterValidationJsonResult != null) { - case PackageValidationResultType.Accepted: - break; - case PackageValidationResultType.Invalid: - case PackageValidationResultType.PackageShouldNotBeSigned: - return Json(HttpStatusCode.BadRequest, new[] { validationResult.Message }); - case PackageValidationResultType.PackageShouldNotBeSignedButCanManageCertificates: - return Json( - HttpStatusCode.BadRequest, - new[] - { - validationResult.Message + " " + - Strings.UploadPackage_PackageIsSignedButMissingCertificate_ManageCertificate - }); - default: - throw new NotImplementedException($"The package validation result type {validationResult.Type} is not supported."); + return afterValidationJsonResult; } if (formData.Edit != null) @@ -1753,6 +1738,34 @@ await _auditingService.SaveAuditRecordAsync( } } + private JsonResult GetJsonResultOrNull(PackageValidationResult validationResult) + { + var errorMessage = GetErrorMessageOrNull(validationResult); + if (errorMessage == null) + { + return null; + } + + return Json(HttpStatusCode.BadRequest, new[] { errorMessage }); + } + + private static string GetErrorMessageOrNull(PackageValidationResult validationResult) + { + switch (validationResult.Type) + { + case PackageValidationResultType.Accepted: + return null; + case PackageValidationResultType.Invalid: + case PackageValidationResultType.PackageShouldNotBeSigned: + return validationResult.Message; + case PackageValidationResultType.PackageShouldNotBeSignedButCanManageCertificates: + return validationResult.Message + " " + + Strings.UploadPackage_PackageIsSignedButMissingCertificate_ManageCertificate; + default: + throw new NotImplementedException($"The package validation result type {validationResult.Type} is not supported."); + } + } + private async Task SafeCreatePackage(User currentUser, Stream uploadFile) { Exception caught = null; diff --git a/src/NuGetGallery/Infrastructure/HttpStatusCodeWithServerWarningResult.cs b/src/NuGetGallery/Infrastructure/HttpStatusCodeWithServerWarningResult.cs index 7a1ddb60c5..40c4209a1f 100644 --- a/src/NuGetGallery/Infrastructure/HttpStatusCodeWithServerWarningResult.cs +++ b/src/NuGetGallery/Infrastructure/HttpStatusCodeWithServerWarningResult.cs @@ -1,29 +1,36 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; +using System.Linq; using System.Net; using System.Web.Mvc; -using NuGet.Protocol; namespace NuGetGallery { public class HttpStatusCodeWithServerWarningResult : HttpStatusCodeResult { - private readonly string _warningMessage; + public IReadOnlyList Warnings { get; } - public HttpStatusCodeWithServerWarningResult(HttpStatusCode statusCode, string warningMessage) + public HttpStatusCodeWithServerWarningResult(HttpStatusCode statusCode, IReadOnlyList warnings) : base((int)statusCode) { - _warningMessage = warningMessage; + Warnings = warnings ?? new string[0]; } public override void ExecuteResult(ControllerContext context) { var response = context.RequestContext.HttpContext.Response; - if (!string.IsNullOrEmpty(_warningMessage) && !response.HeadersWritten) + if (Warnings.Any() && !response.HeadersWritten) { - response.AppendHeader(ProtocolConstants.ServerWarningHeader, _warningMessage); + foreach (var warning in Warnings) + { + if (!string.IsNullOrWhiteSpace(warning)) + { + response.AppendHeader(Constants.WarningHeaderName, warning); + } + } } base.ExecuteResult(context); diff --git a/src/NuGetGallery/RequestModels/SubmitPackageRequest.cs b/src/NuGetGallery/RequestModels/SubmitPackageRequest.cs index 783241b92c..b2af5e3927 100644 --- a/src/NuGetGallery/RequestModels/SubmitPackageRequest.cs +++ b/src/NuGetGallery/RequestModels/SubmitPackageRequest.cs @@ -1,17 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.ComponentModel.DataAnnotations; -using System.Web; namespace NuGetGallery { public class SubmitPackageRequest { - [Required] - [Hint("Your package file will be uploaded and hosted on the gallery server.")] - public HttpPostedFile PackageFile { get; set; } - - public bool IsUploadInProgress { get; set; } + public bool IsUploadInProgress => InProgressUpload != null; public VerifyPackageRequest InProgressUpload { get; set; } } diff --git a/src/NuGetGallery/RequestModels/VerifyPackageRequest.cs b/src/NuGetGallery/RequestModels/VerifyPackageRequest.cs index da4f88760d..9037974b1f 100644 --- a/src/NuGetGallery/RequestModels/VerifyPackageRequest.cs +++ b/src/NuGetGallery/RequestModels/VerifyPackageRequest.cs @@ -10,7 +10,9 @@ namespace NuGetGallery { public class VerifyPackageRequest { - public VerifyPackageRequest() { } + public VerifyPackageRequest() + { + } public VerifyPackageRequest(PackageMetadata packageMetadata, IEnumerable possibleOwners, PackageRegistration existingPackageRegistration) { @@ -111,6 +113,8 @@ public VerifyPackageRequest(PackageMetadata packageMetadata, IEnumerable p public string Tags { get; set; } public string Title { get; set; } + public List Warnings { get; set; } = new List(); + private static IReadOnlyCollection ParseUserList(IEnumerable users) { return users.Select(u => u.Username).ToList(); diff --git a/src/NuGetGallery/Services/IPackageUploadService.cs b/src/NuGetGallery/Services/IPackageUploadService.cs index b0e964c345..c0c4784342 100644 --- a/src/NuGetGallery/Services/IPackageUploadService.cs +++ b/src/NuGetGallery/Services/IPackageUploadService.cs @@ -10,6 +10,16 @@ namespace NuGetGallery { public interface IPackageUploadService { + /// + /// Validate the provided package archive reader before + /// is + /// called. This is useful for finding errors or warnings that should be caught before the user verifies their + /// UI package upload. + /// + /// The package archive reader. + /// The package validation result. + Task ValidateBeforeGeneratePackageAsync(PackageArchiveReader nuGetPackage); + Task GeneratePackageAsync( string id, PackageArchiveReader nugetPackage, @@ -28,7 +38,7 @@ Task GeneratePackageAsync( /// The owner of the package. /// The current user. /// The package validation result. - Task ValidatePackageAsync( + Task ValidateAfterGeneratePackageAsync( Package package, PackageArchiveReader nuGetPackage, User owner, diff --git a/src/NuGetGallery/Services/PackageUploadService.cs b/src/NuGetGallery/Services/PackageUploadService.cs index a87564565b..28318f5982 100644 --- a/src/NuGetGallery/Services/PackageUploadService.cs +++ b/src/NuGetGallery/Services/PackageUploadService.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using NuGet.Packaging; +using NuGet.Versioning; using NuGetGallery.Configuration; using NuGetGallery.Extensions; using NuGetGallery.Packaging; @@ -38,7 +40,72 @@ public PackageUploadService( _config = config ?? throw new ArgumentNullException(nameof(config)); } - public async Task ValidatePackageAsync( + public async Task ValidateBeforeGeneratePackageAsync(PackageArchiveReader nuGetPackage) + { + var warnings = new List(); + + var result = await CheckForUnsignedPushAfterAuthorSignedAsync( + nuGetPackage, + warnings); + if (result != null) + { + return result; + } + + return PackageValidationResult.AcceptedWithWarnings(warnings); + } + + /// + /// If a package author pushes version X that is author signed then pushes version Y that is unsigned, where Y + /// is immediately after X when the version list is sorted used SemVer 2.0.0 rules, warn the package author. + /// If the user pushes another unsigned version after Y, no warning is produced. This means the warning will + /// not present on every subsequent push, which would be a bit too noisy. + /// + /// The package archive reader. + /// The working list of warnings. + /// The package validation result or null. + private async Task CheckForUnsignedPushAfterAuthorSignedAsync( + PackageArchiveReader nuGetPackage, + List warnings) + { + // If the package is signed, there's no problem. + if (await nuGetPackage.IsSignedAsync(CancellationToken.None)) + { + return null; + } + + var newIdentity = nuGetPackage.GetIdentity(); + var packageRegistration = _packageService.FindPackageRegistrationById(newIdentity.Id); + + // If the package registration does not exist yet, there's no problem. + if (packageRegistration == null) + { + return null; + } + + // Find the highest package version less than the new package that is Available. Deleted packages should + // be ignored and Validating or FailedValidation packages will not necessarily have certificate information. + var previousPackage = packageRegistration + .Packages + .Where(x => x.PackageStatusKey == PackageStatus.Available) + .Select(x => new { x.NormalizedVersion, x.CertificateKey }) + .ToList() // Materialize the lazy collection. + .Select(x => new { Version = NuGetVersion.Parse(x.NormalizedVersion), x.CertificateKey }) + .Where(x => x.Version < newIdentity.Version) + .OrderByDescending(x => x.Version) + .FirstOrDefault(); + + if (previousPackage != null && previousPackage.CertificateKey.HasValue) + { + warnings.Add(string.Format( + Strings.UploadPackage_SignedToUnsignedTransition, + previousPackage.Version.ToNormalizedString())); + } + + return null; + } + + public async Task ValidateAfterGeneratePackageAsync( Package package, PackageArchiveReader nuGetPackage, User owner, diff --git a/src/NuGetGallery/Services/PackageValidationResult.cs b/src/NuGetGallery/Services/PackageValidationResult.cs index 85a159ea03..b132a8dd05 100644 --- a/src/NuGetGallery/Services/PackageValidationResult.cs +++ b/src/NuGetGallery/Services/PackageValidationResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; namespace NuGetGallery { @@ -11,7 +12,14 @@ namespace NuGetGallery /// public class PackageValidationResult { + private static readonly IReadOnlyList EmptyList = new string[0]; + public PackageValidationResult(PackageValidationResultType type, string message) + : this(type, message, warnings: null) + { + } + + public PackageValidationResult(PackageValidationResultType type, string message, IReadOnlyList warnings) { if (type != PackageValidationResultType.Accepted && message == null) { @@ -20,14 +28,27 @@ public PackageValidationResult(PackageValidationResultType type, string message) Type = type; Message = message; + Warnings = warnings ?? EmptyList; } public PackageValidationResultType Type { get; } public string Message { get; } + public IReadOnlyList Warnings { get; } public static PackageValidationResult Accepted() { - return new PackageValidationResult(PackageValidationResultType.Accepted, message: null); + return new PackageValidationResult( + PackageValidationResultType.Accepted, + message: null, + warnings: null); + } + + public static PackageValidationResult AcceptedWithWarnings(IReadOnlyList warnings) + { + return new PackageValidationResult( + PackageValidationResultType.Accepted, + message: null, + warnings: warnings); } public static PackageValidationResult Invalid(string message) @@ -37,7 +58,10 @@ public static PackageValidationResult Invalid(string message) throw new ArgumentNullException(nameof(message)); } - return new PackageValidationResult(PackageValidationResultType.Invalid, message); + return new PackageValidationResult( + PackageValidationResultType.Invalid, + message, + warnings: null); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Strings.Designer.cs b/src/NuGetGallery/Strings.Designer.cs index d9f4b5fe01..c57383e3ee 100644 --- a/src/NuGetGallery/Strings.Designer.cs +++ b/src/NuGetGallery/Strings.Designer.cs @@ -2220,6 +2220,15 @@ public static string UploadPackage_PackageIsSignedButMissingCertificate_Required } } + /// + /// Looks up a localized string similar to The previous package version '{0}' is author signed but the uploaded package is unsigned. To avoid this warning, sign the package before uploading.. + /// + public static string UploadPackage_SignedToUnsignedTransition { + get { + return ResourceManager.GetString("UploadPackage_SignedToUnsignedTransition", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot upload file because an upload is already in progress.. /// diff --git a/src/NuGetGallery/Strings.resx b/src/NuGetGallery/Strings.resx index c9516839b1..0c60119617 100644 --- a/src/NuGetGallery/Strings.resx +++ b/src/NuGetGallery/Strings.resx @@ -964,4 +964,8 @@ Policy violations: {0} It looks like there is another copy of this symbols package pending validation(s). Please wait for the validation(s) to finish before trying to replace the symbols package. + + The previous package version '{0}' is author signed but the uploaded package is unsigned. To avoid this warning, sign the package before uploading. + {0} is the previous package's normalized version. + \ No newline at end of file diff --git a/src/NuGetGallery/Views/Packages/UploadPackage.cshtml b/src/NuGetGallery/Views/Packages/UploadPackage.cshtml index 6e48130755..5df65fa0b7 100644 --- a/src/NuGetGallery/Views/Packages/UploadPackage.cshtml +++ b/src/NuGetGallery/Views/Packages/UploadPackage.cshtml @@ -27,8 +27,6 @@
@Html.AntiForgeryToken() - @Html.ValidationSummary(true) -
- @if (Model != null && Model.IsUploadInProgress) {
- @ViewHelpers.AlertWarning(@You had an upload in progress. You can continue it here or cancel to restart.) - @ViewHelpers.AlertPackageVerifyRecommendation() -
- } - else - { - } + + @Html.Partial("_VerifyForm") diff --git a/src/NuGetGallery/Views/Packages/_VerifyMetadata.cshtml b/src/NuGetGallery/Views/Packages/_VerifyMetadata.cshtml index 1853bac86f..bc23a9bd17 100644 --- a/src/NuGetGallery/Views/Packages/_VerifyMetadata.cshtml +++ b/src/NuGetGallery/Views/Packages/_VerifyMetadata.cshtml @@ -44,6 +44,11 @@