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);
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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));
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        [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 }));
        }