public virtual async Task <ActionResult> VerifyPackage(bool?listed) { var currentUser = _userService.FindByUsername(GetIdentity().Name); Package package; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { if (uploadFile == null) { return(HttpNotFound()); } INupkg nugetPackage = CreatePackage(uploadFile); // update relevant database tables package = _packageService.CreatePackage(nugetPackage, currentUser, commitChanges: false); Debug.Assert(package.PackageRegistration != null); _packageService.PublishPackage(package, commitChanges: false); if (listed == false) { _packageService.MarkPackageUnlisted(package, commitChanges: false); } _autoCuratedPackageCmd.Execute(package, nugetPackage, commitChanges: false); // save package to blob storage uploadFile.Position = 0; await _packageFileService.SavePackageFileAsync(package, uploadFile); // commit all changes to database as an atomic transaction _entitiesContext.SaveChanges(); // tell Lucene to update index for the new package _indexingService.UpdateIndex(); // If we're pushing a new stable version of NuGet.CommandLine, update the extracted executable. if (package.PackageRegistration.Id.Equals(Constants.NuGetCommandLinePackageId, StringComparison.OrdinalIgnoreCase) && package.IsLatestStable) { await _nugetExeDownloaderService.UpdateExecutableAsync(nugetPackage); } } // delete the uploaded binary in the Uploads container await _uploadFileService.DeleteUploadFileAsync(currentUser.Key); TempData["Message"] = String.Format( CultureInfo.CurrentCulture, Strings.SuccessfullyUploadedPackage, package.PackageRegistration.Id, package.Version); return(RedirectToRoute(RouteName.DisplayPackage, new { package.PackageRegistration.Id, package.Version })); }
public async Task <PackageCommitResult> CommitPackageAsync(Package package, Stream packageFile) { if (package.PackageStatusKey != PackageStatus.Available && package.PackageStatusKey != PackageStatus.Validating) { throw new ArgumentException( $"The package to commit must have either the {PackageStatus.Available} or {PackageStatus.Validating} package status.", nameof(package)); } try { if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.SaveValidationPackageFileAsync(package, packageFile); } else { await _packageFileService.SavePackageFileAsync(package, packageFile); } } catch (InvalidOperationException ex) { ex.Log(); return(PackageCommitResult.Conflict); } try { // commit all changes to database as an atomic transaction await _entitiesContext.SaveChangesAsync(); } catch { // If saving to the DB fails for any reason we need to delete the package we just saved. if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.DeleteValidationPackageFileAsync( package.PackageRegistration.Id, package.Version); } else { await _packageFileService.DeletePackageFileAsync( package.PackageRegistration.Id, package.Version); } throw; } return(PackageCommitResult.Success); }
public async Task <PackageCommitResult> CommitPackageAsync(Package package, Stream packageFile) { if (package == null) { throw new ArgumentNullException(nameof(package)); } if (packageFile == null) { throw new ArgumentNullException(nameof(packageFile)); } if (!packageFile.CanSeek) { throw new ArgumentException($"{nameof(packageFile)} argument must be seekable stream", nameof(packageFile)); } await _validationService.UpdatePackageAsync(package); if (package.PackageStatusKey != PackageStatus.Available && package.PackageStatusKey != PackageStatus.Validating) { throw new ArgumentException( $"The package to commit must have either the {PackageStatus.Available} or {PackageStatus.Validating} package status.", nameof(package)); } try { if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.SaveValidationPackageFileAsync(package, packageFile); /* Suppose two package upload requests come in at the same time with the same package (same ID and * version). It's possible that one request has committed and validated the package AFTER the other * request has checked that this package does not exist in the database. Observe the following * sequence of events to understand why the packages container check is necessary. * * Request | Step | Component | Success | Notes * ------- | ---------------------------------------------- | ---------------- | ------- | ----- * 1 | version should not exist in DB | gallery | TRUE | 1st duplicate check (catches most cases over time) * 2 | version should not exist in DB | gallery | TRUE | * 1 | upload to validation container | gallery | TRUE | 2nd duplicate check (relevant with high concurrency) * 1 | version should not exist in packages container | gallery | TRUE | 3rd duplicate check (relevant with fast validations) * 1 | commit to DB | gallery | TRUE | * 1 | upload to packages container | async validation | TRUE | * 1 | move package to Available status in DB | async validation | TRUE | * 1 | delete from validation container | async validation | TRUE | * 2 | upload to validation container | gallery | TRUE | * 2 | version should not exist in packages container | gallery | FALSE | * 2 | delete from validation (rollback) | gallery | TRUE | Only occurs in the failure case, as a clean-up. * * Alternatively, we could handle the DB conflict exception that would occur in request 2, but this * would result in an exception that can be avoided and require some ugly code that teases the * unique constraint failure out of a SqlException. * * Another alternative is always leaving the package in the validation container. This is not great * since it doubles the amount of space we need to store packages. Also, it complicates the soft or * hard package delete flow. * * We can safely delete the validation package because we know it's ours. We know this because * saving the validation package succeeded, meaning async validation already successfully moved the * previous package (request 1's package) from the validation container to the package container * and transitioned the package to Available status. * * See the following issue in GitHub for how this case was found: * https://github.com/NuGet/NuGetGallery/issues/5039 */ if (await _packageFileService.DoesPackageFileExistAsync(package)) { await _packageFileService.DeleteValidationPackageFileAsync( package.PackageRegistration.Id, package.Version); return(PackageCommitResult.Conflict); } } else { if (package.EmbeddedLicenseType != EmbeddedLicenseFileType.Absent) { // if the package is immediately made available, it means there is a high chance we don't have // validation pipeline that would normally store the license file, so we'll do it ourselves here. await _coreLicenseFileService.ExtractAndSaveLicenseFileAsync(package, packageFile); } var isReadmeFileExtractedAndSaved = false; if (package.HasReadMe && package.EmbeddedReadmeType != EmbeddedReadmeFileType.Absent) { await _packageFileService.ExtractAndSaveReadmeFileAsync(package, packageFile); isReadmeFileExtractedAndSaved = true; } try { packageFile.Seek(0, SeekOrigin.Begin); await _packageFileService.SavePackageFileAsync(package, packageFile); } catch when(package.EmbeddedLicenseType != EmbeddedLicenseFileType.Absent || isReadmeFileExtractedAndSaved) { if (package.EmbeddedLicenseType != EmbeddedLicenseFileType.Absent) { await _coreLicenseFileService.DeleteLicenseFileAsync( package.PackageRegistration.Id, package.NormalizedVersion); } if (isReadmeFileExtractedAndSaved) { await _packageFileService.DeleteReadMeMdFileAsync(package); } throw; } } } catch (FileAlreadyExistsException ex) { ex.Log(); return(PackageCommitResult.Conflict); } try { // Sending the validation request after copying to prevent multiple validation requests // sent when several pushes for the same package happen concurrently. Copying the file // resolves the race and only one request will "win" and reach this code. await _validationService.StartValidationAsync(package); // commit all changes to database as an atomic transaction await _entitiesContext.SaveChangesAsync(); } catch (Exception ex) { // If sending the validation request or saving to the DB fails for any reason // we need to delete the package we just saved. if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.DeleteValidationPackageFileAsync( package.PackageRegistration.Id, package.Version); } else { await _packageFileService.DeletePackageFileAsync( package.PackageRegistration.Id, package.Version); await _coreLicenseFileService.DeleteLicenseFileAsync( package.PackageRegistration.Id, package.NormalizedVersion); await _packageFileService.DeleteReadMeMdFileAsync(package); } return(ReturnConflictOrThrow(ex)); } return(PackageCommitResult.Success); }
private async Task <ActionResult> CreatePackageInternal(string apiKey) { Guid parsedApiKey; if (!Guid.TryParse(apiKey, out parsedApiKey)) { return(new HttpStatusCodeWithBodyResult( HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKey))); } var user = _userService.FindByApiKey(parsedApiKey); if (user == null) { return(new HttpStatusCodeWithBodyResult( HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "push"))); } using (var packageToPush = ReadPackageFromRequest()) { // Ensure that the user can push packages for this partialId. var packageRegistration = _packageService.FindPackageRegistrationById(packageToPush.Metadata.Id); if (packageRegistration != null) { if (!packageRegistration.IsOwner(user)) { return(new HttpStatusCodeWithBodyResult( HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "push"))); } // Check if a particular Id-Version combination already exists. We eventually need to remove this check. bool packageExists = packageRegistration.Packages.Any( p => p.Version.Equals(packageToPush.Metadata.Version.ToString(), StringComparison.OrdinalIgnoreCase)); if (packageExists) { return(new HttpStatusCodeWithBodyResult( HttpStatusCode.Conflict, String.Format(CultureInfo.CurrentCulture, Strings.PackageExistsAndCannotBeModified, packageToPush.Metadata.Id, packageToPush.Metadata.Version))); } } var package = _packageService.CreatePackage(packageToPush, user, commitChanges: true); using (Stream uploadStream = packageToPush.GetStream()) { await _packageFileService.SavePackageFileAsync(package, uploadStream); } if ( packageToPush.Metadata.Id.Equals(Constants.NuGetCommandLinePackageId, StringComparison.OrdinalIgnoreCase) && package.IsLatestStable) { // If we're pushing a new stable version of NuGet.CommandLine, update the extracted executable. await _nugetExeDownloaderService.UpdateExecutableAsync(packageToPush); } } return(new HttpStatusCodeResult(201)); }
public async Task <PackageCommitResult> CommitPackageAsync(Package package, Stream packageFile) { await _validationService.StartValidationAsync(package); if (package.PackageStatusKey != PackageStatus.Available && package.PackageStatusKey != PackageStatus.Validating) { throw new ArgumentException( $"The package to commit must have either the {PackageStatus.Available} or {PackageStatus.Validating} package status.", nameof(package)); } try { if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.SaveValidationPackageFileAsync(package, packageFile); /* Suppose two package upload requests come in at the same time with the same package (same ID and * version). It's possible that one request has committed and validated the package AFTER the other * request has checked that this package does not exist in the database. Observe the following * sequence of events to understand why the packages container check is necessary. * * Request | Step | Component | Success | Notes * ------- | ---------------------------------------------- | ---------------- | ------- | ----- * 1 | version should not exist in DB | gallery | TRUE | 1st duplicate check (catches most cases over time) * 2 | version should not exist in DB | gallery | TRUE | * 1 | upload to validation container | gallery | TRUE | 2nd duplicate check (relevant with high concurrency) * 1 | version should not exist in packages container | gallery | TRUE | 3rd duplicate check (relevant with fast validations) * 1 | commit to DB | gallery | TRUE | * 1 | upload to packages container | async validation | TRUE | * 1 | move package to Available status in DB | async validation | TRUE | * 1 | delete from validation container | async validation | TRUE | * 2 | upload to validation container | gallery | TRUE | * 2 | version should not exist in packages container | gallery | FALSE | * 2 | delete from validation (rollback) | gallery | TRUE | Only occurs in the failure case, as a clean-up. * * Alternatively, we could handle the DB conflict exception that would occur in request 2, but this * would result in an exception that can be avoided and require some ugly code that teases the * unique constraint failure out of a SqlException. * * Another alternative is always leaving the package in the validation container. This is not great * since it doubles the amount of space we need to store packages. Also, it complicates the soft or * hard package delete flow. * * We can safely delete the validation package because we know it's ours. We know this because * saving the validation package succeeded, meaning async validation already successfully moved the * previous package (request 1's package) from the validation container to the package container * and transitioned the package to Available status. * * See the following issue in GitHub for how this case was found: * https://github.com/NuGet/NuGetGallery/issues/5039 */ if (await _packageFileService.DoesPackageFileExistAsync(package)) { await _packageFileService.DeleteValidationPackageFileAsync( package.PackageRegistration.Id, package.Version); return(PackageCommitResult.Conflict); } } else { await _packageFileService.SavePackageFileAsync(package, packageFile); } } catch (FileAlreadyExistsException ex) { ex.Log(); return(PackageCommitResult.Conflict); } try { // commit all changes to database as an atomic transaction await _entitiesContext.SaveChangesAsync(); } catch { // If saving to the DB fails for any reason we need to delete the package we just saved. if (package.PackageStatusKey == PackageStatus.Validating) { await _packageFileService.DeleteValidationPackageFileAsync( package.PackageRegistration.Id, package.Version); } else { await _packageFileService.DeletePackageFileAsync( package.PackageRegistration.Id, package.Version); } throw; } return(PackageCommitResult.Success); }
[ValidateInput(false)] // Security note: Disabling ASP.Net input validation which does things like disallow angle brackets in submissions. See http://go.microsoft.com/fwlink/?LinkID=212874 public virtual async Task <ActionResult> VerifyPackage(VerifyPackageRequest formData) { var currentUser = GetCurrentUser(); Package package; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { if (uploadFile == null) { TempData["Message"] = "Your attempt to verify the package submission failed, because we could not find the uploaded package file. Please try again."; return(new RedirectResult(Url.UploadPackage())); } INupkg nugetPackage = await SafeCreatePackage(currentUser, uploadFile); if (nugetPackage == null) { // Send the user back return(new RedirectResult(Url.UploadPackage())); } Debug.Assert(nugetPackage != null); // Rule out problem scenario with multiple tabs - verification request (possibly with edits) was submitted by user // viewing a different package to what was actually most recently uploaded if (!(String.IsNullOrEmpty(formData.Id) || String.IsNullOrEmpty(formData.Version))) { if (!(String.Equals(nugetPackage.Metadata.Id, formData.Id, StringComparison.OrdinalIgnoreCase) && String.Equals(nugetPackage.Metadata.Version.ToNormalizedString(), formData.Version, StringComparison.OrdinalIgnoreCase))) { TempData["Message"] = "Your attempt to verify the package submission failed, because the package file appears to have changed. Please try again."; return(new RedirectResult(Url.VerifyPackage())); } } bool pendEdit = false; if (formData.Edit != null) { pendEdit = pendEdit || formData.Edit.RequiresLicenseAcceptance != nugetPackage.Metadata.RequireLicenseAcceptance; pendEdit = pendEdit || IsDifferent(formData.Edit.IconUrl, nugetPackage.Metadata.IconUrl.ToEncodedUrlStringOrNull()); pendEdit = pendEdit || IsDifferent(formData.Edit.ProjectUrl, nugetPackage.Metadata.ProjectUrl.ToEncodedUrlStringOrNull()); pendEdit = pendEdit || IsDifferent(formData.Edit.Authors, nugetPackage.Metadata.Authors.Flatten()); pendEdit = pendEdit || IsDifferent(formData.Edit.Copyright, nugetPackage.Metadata.Copyright); pendEdit = pendEdit || IsDifferent(formData.Edit.Description, nugetPackage.Metadata.Description); pendEdit = pendEdit || IsDifferent(formData.Edit.ReleaseNotes, nugetPackage.Metadata.ReleaseNotes); pendEdit = pendEdit || IsDifferent(formData.Edit.Summary, nugetPackage.Metadata.Summary); pendEdit = pendEdit || IsDifferent(formData.Edit.Tags, nugetPackage.Metadata.Tags); pendEdit = pendEdit || IsDifferent(formData.Edit.VersionTitle, nugetPackage.Metadata.Title); } // update relevant database tables package = _packageService.CreatePackage(nugetPackage, currentUser, commitChanges: false); Debug.Assert(package.PackageRegistration != null); _packageService.PublishPackage(package, commitChanges: false); if (pendEdit) { // Add the edit request to a queue where it will be processed in the background. _editPackageService.StartEditPackageRequest(package, formData.Edit, currentUser); } if (!formData.Listed) { _packageService.MarkPackageUnlisted(package, commitChanges: false); } _autoCuratedPackageCmd.Execute(package, nugetPackage, commitChanges: false); // save package to blob storage uploadFile.Position = 0; await _packageFileService.SavePackageFileAsync(package, uploadFile); // commit all changes to database as an atomic transaction _entitiesContext.SaveChanges(); // tell Lucene to update index for the new package _indexingService.UpdateIndex(); } // delete the uploaded binary in the Uploads container await _uploadFileService.DeleteUploadFileAsync(currentUser.Key); TempData["Message"] = String.Format( CultureInfo.CurrentCulture, Strings.SuccessfullyUploadedPackage, package.PackageRegistration.Id, package.Version); return(RedirectToRoute(RouteName.DisplayPackage, new { package.PackageRegistration.Id, package.Version })); }