public async Task AddPackageOwnerAsync(PackageRegistration packageRegistration, User user, bool commitChanges = true)
        {
            if (packageRegistration == null)
            {
                throw new ArgumentNullException(nameof(packageRegistration));
            }

            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (commitChanges)
            {
                using (var strategy = new SuspendDbExecutionStrategy())
                    using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                    {
                        await AddPackageOwnerTask(packageRegistration, user, commitChanges);

                        transaction.Commit();
                    }
            }
            else
            {
                await AddPackageOwnerTask(packageRegistration, user, commitChanges);
            }

            await _auditingService.SaveAuditRecordAsync(
                new PackageRegistrationAuditRecord(packageRegistration, AuditedPackageRegistrationAction.AddOwner, user.Username));
        }
        public async Task AddPackageOwnerAsync(PackageRegistration packageRegistration, User user)
        {
            if (packageRegistration == null)
            {
                throw new ArgumentNullException(nameof(packageRegistration));
            }

            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            using (var strategy = new SuspendDbExecutionStrategy())
                using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                {
                    Func <ReservedNamespace, bool> predicate =
                        reservedNamespace => reservedNamespace.IsPrefix
                        ? packageRegistration.Id.StartsWith(reservedNamespace.Value, StringComparison.OrdinalIgnoreCase)
                        : packageRegistration.Id.Equals(reservedNamespace.Value, StringComparison.OrdinalIgnoreCase);

                    var userOwnedMatchingNamespacesForId = user
                                                           .ReservedNamespaces
                                                           .Where(predicate);

                    if (userOwnedMatchingNamespacesForId.Any())
                    {
                        if (!packageRegistration.IsVerified)
                        {
                            await _packageService.UpdatePackageVerifiedStatusAsync(new List <PackageRegistration> {
                                packageRegistration
                            }, isVerified : true);
                        }

                        userOwnedMatchingNamespacesForId
                        .ToList()
                        .ForEach(mn =>
                                 _reservedNamespaceService.AddPackageRegistrationToNamespace(mn.Value, packageRegistration));

                        // The 'AddPackageRegistrationToNamespace' does not commit its changes, so saving changes for consistency.
                        await _entitiesContext.SaveChangesAsync();
                    }

                    await _packageService.AddPackageOwnerAsync(packageRegistration, user);

                    await DeletePackageOwnershipRequestAsync(packageRegistration, user);

                    transaction.Commit();
                }

            await _auditingService.SaveAuditRecordAsync(
                new PackageRegistrationAuditRecord(packageRegistration, AuditedPackageRegistrationAction.AddOwner, user.Username));
        }
        public async Task <Package> ReflowAsync(string id, string version)
        {
            var package = _packageService.FindPackageByIdAndVersionStrict(id, version);

            if (package == null)
            {
                return(null);
            }

            EntitiesConfiguration.SuspendExecutionStrategy = true;
            using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
            {
                // 1) Download package binary to memory
                using (var packageStream = await _packageFileService.DownloadPackageFileAsync(package))
                {
                    using (var packageArchive = new PackageArchiveReader(packageStream, leaveStreamOpen: false))
                    {
                        // 2) Determine package metadata from binary
                        var packageStreamMetadata = new PackageStreamMetadata
                        {
                            HashAlgorithm = Constants.Sha512HashAlgorithmId,
                            Hash          = CryptographyService.GenerateHash(packageStream.AsSeekableStream()),
                            Size          = packageStream.Length,
                        };

                        var packageMetadata = PackageMetadata.FromNuspecReader(packageArchive.GetNuspecReader());

                        // 3) Clear referenced objects that will be reflowed
                        ClearSupportedFrameworks(package);
                        ClearAuthors(package);
                        ClearDependencies(package);

                        // 4) Reflow the package
                        var listed = package.Listed;

                        package = _packageService.EnrichPackageFromNuGetPackage(
                            package,
                            packageArchive,
                            packageMetadata,
                            packageStreamMetadata,
                            package.User);

                        package.LastEdited = DateTime.UtcNow;
                        package.Listed     = listed;

                        // 5) Update IsLatest so that reflow can correct concurrent updates (see Gallery #2514)
                        await _packageService.UpdateIsLatestAsync(package.PackageRegistration, commitChanges : false);

                        // 6) Save and profit
                        await _entitiesContext.SaveChangesAsync();
                    }
                }

                // Commit transaction
                transaction.Commit();
            }
            EntitiesConfiguration.SuspendExecutionStrategy = false;

            return(package);
        }
Пример #4
0
        /// <summary>
        /// Will clean-up the data related with an user account.
        /// The result will be:
        /// 1. The user will be removed as owner from its owned packages.
        /// 2. Any of the packages that become orphaned as its result will be unlisted if the unlistOrphanPackages is set to true.
        /// 3. Any owned namespaces will be released.
        /// 4. The user credentials will be cleaned.
        /// 5. The user data will be cleaned.
        /// </summary>
        /// <param name="userToBeDeleted">The user to be deleted.</param>
        /// <param name="admin">The admin that will perform the delete action.</param>
        /// <param name="signature">The admin signature.</param>
        /// <param name="unlistOrphanPackages">If the orphaned packages will unlisted.</param>
        /// <param name="commitAsTransaction">If the data will be persisted as a transaction.</param>
        /// <returns></returns>
        public async Task <DeleteUserAccountStatus> DeleteGalleryUserAccountAsync(User userToBeDeleted, User admin, string signature, bool unlistOrphanPackages, bool commitAsTransaction)
        {
            if (userToBeDeleted == null)
            {
                throw new ArgumentNullException(nameof(userToBeDeleted));
            }
            if (admin == null)
            {
                throw new ArgumentNullException(nameof(admin));
            }
            if (userToBeDeleted.IsDeleted)
            {
                return(new DeleteUserAccountStatus()
                {
                    Success = false,
                    Description = string.Format(Strings.AccountDelete_AccountAlreadyDeleted, userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }
            try
            {
                if (commitAsTransaction)
                {
                    using (var strategy = new SuspendDbExecutionStrategy())
                        using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                        {
                            await DeleteGalleryUserAccountImplAsync(userToBeDeleted, admin, signature, unlistOrphanPackages);

                            transaction.Commit();
                        }
                }
                else
                {
                    await DeleteGalleryUserAccountImplAsync(userToBeDeleted, admin, signature, unlistOrphanPackages);
                }
                return(new DeleteUserAccountStatus()
                {
                    Success = true,
                    Description = string.Format(Strings.AccountDelete_Success, userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }
            catch (Exception e)
            {
                QuietLog.LogHandledException(e);
                return(new DeleteUserAccountStatus()
                {
                    Success = true,
                    Description = string.Format(Strings.AccountDelete_Fail, userToBeDeleted.Username, e),
                    AccountName = userToBeDeleted.Username
                });
            }
        }
Пример #5
0
        private async Task <DeleteUserAccountStatus> RunAccountDeletionTask(Func <Task> getTask, User userToBeDeleted, User requestingUser, bool commitAsTransaction)
        {
            try
            {
                // The support requests DB and gallery DB are different.
                // TransactionScope can be used for doing transaction actions across db on the same server but not on different servers.
                // The below code will clean the feature flags and suppport requests before the gallery data.
                // The order is important in order to allow the admin the opportunity to execute this step again.
                await _featureFlagService.RemoveUserAsync(userToBeDeleted);
                await RemoveSupportRequests(userToBeDeleted);

                if (commitAsTransaction)
                {
                    using (var strategy = new SuspendDbExecutionStrategy())
                        using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                        {
                            await getTask();

                            transaction.Commit();
                        }
                }
                else
                {
                    await getTask();
                }

                await _auditingService.SaveAuditRecordAsync(new DeleteAccountAuditRecord(username : userToBeDeleted.Username,
                                                                                         status : DeleteAccountAuditRecord.ActionStatus.Success,
                                                                                         action : AuditedDeleteAccountAction.DeleteAccount,
                                                                                         adminUsername : requestingUser.Username));

                return(new DeleteUserAccountStatus()
                {
                    Success = true,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_Success,
                                                userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }
            catch (Exception e)
            {
                QuietLog.LogHandledException(e);
                return(new DeleteUserAccountStatus()
                {
                    Success = false,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_Fail,
                                                userToBeDeleted.Username, e),
                    AccountName = userToBeDeleted.Username
                });
            }
        }
Пример #6
0
        public async Task SoftDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature)
        {
            using (var strategy = new SuspendDbExecutionStrategy())
                using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                {
                    // Increase command timeout
                    _entitiesContext.SetCommandTimeout(seconds: 300);

                    // Keep package registrations
                    var packageRegistrations = packages
                                               .GroupBy(p => p.PackageRegistration)
                                               .Select(g => g.First().PackageRegistration)
                                               .ToList();

                    // Backup the package binaries and remove from main storage
                    // We're doing this early in the process as we need the metadata to still exist in the DB.
                    await BackupPackageBinaries(packages);

                    // Store the soft delete in the database
                    var packageDelete = new PackageDelete
                    {
                        DeletedOn = DateTime.UtcNow,
                        DeletedBy = deletedBy,
                        Reason    = reason,
                        Signature = signature
                    };

                    foreach (var package in packages)
                    {
                        /// We do not call <see cref="IPackageService.MarkPackageUnlistedAsync(Package, bool)"/> here
                        /// because that writes an audit entry. Additionally, the latest bits are already updated by
                        /// the package status change.
                        package.Listed = false;

                        await _packageService.UpdatePackageStatusAsync(
                            package,
                            PackageStatus.Deleted,
                            commitChanges : false);

                        packageDelete.Packages.Add(package);

                        await _auditingService.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.SoftDelete, reason));

                        _telemetryService.TrackPackageDelete(package, isHardDelete: false);
                    }

                    _packageDeletesRepository.InsertOnCommit(packageDelete);

                    // Commit changes
                    await _packageRepository.CommitChangesAsync();

                    await _packageDeletesRepository.CommitChangesAsync();

                    transaction.Commit();
                }

            // Force refresh the index
            UpdateSearchIndex();
        }
Пример #7
0
        public async Task SoftDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature)
        {
            EntitiesConfiguration.SuspendExecutionStrategy = true;
            using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
            {
                // Increase command timeout
                _entitiesContext.SetCommandTimeout(seconds: 300);

                // Keep package registrations
                var packageRegistrations = packages
                                           .GroupBy(p => p.PackageRegistration)
                                           .Select(g => g.First().PackageRegistration)
                                           .ToList();

                // Backup the package binaries and remove from main storage
                // We're doing this early in the process as we need the metadata to still exist in the DB.
                await BackupPackageBinaries(packages);

                // Store the soft delete in the database
                var packageDelete = new PackageDelete
                {
                    DeletedOn = DateTime.UtcNow,
                    DeletedBy = deletedBy,
                    Reason    = reason,
                    Signature = signature
                };

                foreach (var package in packages)
                {
                    package.Listed  = false;
                    package.Deleted = true;
                    packageDelete.Packages.Add(package);

                    await _auditingService.SaveAuditRecord(CreateAuditRecord(package, package.PackageRegistration, PackageAuditAction.SoftDeleted, reason));
                }

                _packageDeletesRepository.InsertOnCommit(packageDelete);

                // Update latest versions
                await UpdateIsLatestAsync(packageRegistrations);

                // Commit changes
                await _packageRepository.CommitChangesAsync();

                await _packageDeletesRepository.CommitChangesAsync();

                transaction.Commit();
            }
            EntitiesConfiguration.SuspendExecutionStrategy = false;


            // Force refresh the index
            UpdateSearchIndex();
        }
Пример #8
0
        public static async Task <bool> TransformUserToOrganization(this IEntitiesContext context, User accountToTransform, User adminUser, string token)
        {
            accountToTransform = accountToTransform ?? throw new ArgumentNullException(nameof(accountToTransform));
            adminUser          = adminUser ?? throw new ArgumentNullException(nameof(adminUser));

            if (string.IsNullOrWhiteSpace(token))
            {
                throw new ArgumentException(nameof(token));
            }

            var database    = context.GetDatabase();
            var recordCount = await database.ExecuteSqlResourceAsync(
                MigrateUserToOrganization.ResourceName,
                new SqlParameter(MigrateUserToOrganization.OrganizationKey, accountToTransform.Key),
                new SqlParameter(MigrateUserToOrganization.AdminKey, adminUser.Key),
                new SqlParameter(MigrateUserToOrganization.ConfirmationToken, token));

            return(recordCount > 0);
        }
Пример #9
0
        /// <remarks>
        /// Normally we would use a large parameterized SQL query for this.
        /// Unfortunately, however, there is a maximum number of parameters for a SQL query (around 2,000-3,000).
        /// By writing a query containing the package keys directly we can remove this restriction.
        /// Furthermore, package keys are not user data, so there is no risk to writing a query in this way.
        /// </remarks>
        private async Task UpdatePackagesInBulkAsync(IReadOnlyList<int> packageKeys)
        {
            var query = string.Format(
                UpdateBulkPackagesQueryFormat,
                string.Join(
                    ", ", 
                    packageKeys
                        .OrderBy(k => k)));

            var result = await _entitiesContext
                .GetDatabase()
                .ExecuteSqlCommandAsync(query);

            // The query updates each row twice--once for the initial commit and a second time due to the trigger on LastEdited.
            var expectedResult = packageKeys.Count() * 2;
            if (result != expectedResult)
            {
                throw new InvalidOperationException(
                    $"Updated an unexpected number of packages when performing a bulk update! " +
                    $"Updated {result} packages instead of {expectedResult}.");
            }
        }
        /// <summary>
        /// Will clean-up the data related with an user account.
        /// The result will be:
        /// 1. The user will be removed as owner from its owned packages.
        /// 2. Any of the packages that become orphaned as its result will be unlisted if the unlistOrphanPackages is set to true.
        /// 3. Any owned namespaces will be released.
        /// 4. The user credentials will be cleaned.
        /// 5. The user data will be cleaned.
        /// </summary>
        /// <param name="userToBeDeleted">The user to be deleted.</param>
        /// <param name="admin">The admin that will perform the delete action.</param>
        /// <param name="signature">The admin signature.</param>
        /// <param name="unlistOrphanPackages">If the orphaned packages will unlisted.</param>
        /// <param name="commitAsTransaction">If the data will be persisted as a transaction.</param>
        /// <returns></returns>
        public async Task <DeleteUserAccountStatus> DeleteGalleryUserAccountAsync(User userToBeDeleted, User admin, string signature, bool unlistOrphanPackages, bool commitAsTransaction)
        {
            if (userToBeDeleted == null)
            {
                throw new ArgumentNullException(nameof(userToBeDeleted));
            }
            if (admin == null)
            {
                throw new ArgumentNullException(nameof(admin));
            }

            if (userToBeDeleted.IsDeleted)
            {
                return(new DeleteUserAccountStatus()
                {
                    Success = false,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_AccountAlreadyDeleted,
                                                userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }

            // The deletion of Organization and Organization member accounts is disabled for now.
            if (userToBeDeleted is Organization)
            {
                return(new DeleteUserAccountStatus()
                {
                    Success = false,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_OrganizationDeleteNotImplemented,
                                                userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }
            else if (userToBeDeleted.Organizations.Any())
            {
                return(new DeleteUserAccountStatus()
                {
                    Success = false,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_OrganizationMemberDeleteNotImplemented,
                                                userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }

            try
            {
                // The support requests db and gallery db are different.
                // TransactionScope can be used for doing transaction actions across db on the same server but not on different servers.
                // The below code will clean first the suppport requests and after the gallery data.
                // The order is important in order to allow the admin the oportunity to execute this step again.
                await RemoveSupportRequests(userToBeDeleted);

                if (commitAsTransaction)
                {
                    using (var strategy = new SuspendDbExecutionStrategy())
                        using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                        {
                            await DeleteGalleryUserAccountImplAsync(userToBeDeleted, admin, signature, unlistOrphanPackages);

                            transaction.Commit();
                        }
                }
                else
                {
                    await DeleteGalleryUserAccountImplAsync(userToBeDeleted, admin, signature, unlistOrphanPackages);
                }
                await _auditingService.SaveAuditRecordAsync(new DeleteAccountAuditRecord(username : userToBeDeleted.Username,
                                                                                         status : DeleteAccountAuditRecord.ActionStatus.Success,
                                                                                         action : AuditedDeleteAccountAction.DeleteAccount,
                                                                                         adminUsername : admin.Username));

                return(new DeleteUserAccountStatus()
                {
                    Success = true,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_Success,
                                                userToBeDeleted.Username),
                    AccountName = userToBeDeleted.Username
                });
            }
            catch (Exception e)
            {
                QuietLog.LogHandledException(e);
                return(new DeleteUserAccountStatus()
                {
                    Success = true,
                    Description = string.Format(CultureInfo.CurrentCulture,
                                                Strings.AccountDelete_Fail,
                                                userToBeDeleted.Username, e),
                    AccountName = userToBeDeleted.Username
                });
            }
        }
Пример #11
0
        protected internal async virtual Task <bool> TryUpdateIsLatestInDatabase(IEntitiesContext context)
        {
            // Use the EF change tracker to identify changes made in TryUpdateIsLatestAsync which
            // need to be applied to the database below.
            // Note that the change tracker is not mocked which make this method hard to unit test.
            var changeTracker    = context.GetChangeTracker();
            var modifiedPackages = changeTracker.Entries <Package>().Where(p => p.State == EntityState.Modified).ToList();

            if (modifiedPackages.Count == 0)
            {
                return(true);
            }

            // Apply changes to the database with an optimistic concurrency check to prevent multiple
            // threads (in the same or different gallery instance) from setting IsLatest/IsLatestStable
            // flag to true on different package versions.
            // To preserve existing behavior, we only want to reject concurrent updates which set the
            // IsLatest/IsLatestStable columns. For this reason, we must avoid the EF ConcurrencyCheck
            // attribute which could reject any package update or delete.
            var query = new StringBuilder("DECLARE @rowCount INT = 0");

            foreach (var packageEntry in modifiedPackages)
            {
                // Set LastUpdated after all IsLatest/IsLatestStable changes are complete to ensure
                // that we don't update rows where IsLatest/IsLatestStable hasn't changed.
                packageEntry.Entity.LastUpdated = DateTime.UtcNow;

                var isLatest               = packageEntry.Entity.IsLatest ? 1 : 0;
                var isLatestStable         = packageEntry.Entity.IsLatestStable ? 1 : 0;
                var key                    = packageEntry.Entity.Key;
                var originalIsLatest       = Boolean.Parse(packageEntry.OriginalValues["IsLatest"].ToString()) ? 1 : 0;
                var originalIsLatestStable = Boolean.Parse(packageEntry.OriginalValues["IsLatestStable"].ToString()) ? 1 : 0;

                query.AppendLine($"UPDATE [dbo].[Packages]");
                query.AppendLine($"SET [IsLatest] = {isLatest}, [IsLatestStable] = {isLatestStable}, [LastUpdated] = GETUTCDATE()");
                query.AppendLine($"WHERE [Key] = {key}");
                // optimistic concurrency check to prevent concurrent sets of latest/latestStable
                query.AppendLine($" AND [IsLatest] = {originalIsLatest} AND [IsLatestStable] = {originalIsLatestStable}");
                // ensure new latest/latestStable was not concurrently unlisted/deleted
                if (packageEntry.Entity.IsLatest || packageEntry.Entity.IsLatestStable)
                {
                    query.AppendLine($" AND [Listed] = 1 AND [Deleted] = 0");
                }
                query.AppendLine($"SET @rowCount = @rowCount + @@ROWCOUNT");
            }
            query.AppendLine("SELECT @rowCount");

            using (var transaction = context.GetDatabase().BeginTransaction(IsolationLevel.ReadCommitted))
            {
                var rowCount = await context.GetDatabase().ExecuteSqlCommandAsync(query.ToString());

                if (rowCount == modifiedPackages.Count)
                {
                    transaction.Commit();
                    return(true);
                }
                else
                {
                    // RowCount will not match if one or more updates failed the concurrency check. This
                    // likely means another thread is trying to clear the current IsLatest/IsLatestStable.
                    transaction.Rollback();
                    return(false);
                }
            }
        }
        public async Task UpdateDeprecation(
            IReadOnlyList <Package> packages,
            PackageDeprecationStatus status,
            PackageRegistration alternatePackageRegistration,
            Package alternatePackage,
            string customMessage,
            User user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (packages == null || !packages.Any())
            {
                throw new ArgumentException(nameof(packages));
            }

            var registration = packages.First().PackageRegistration;

            if (packages.Select(p => p.PackageRegistrationKey).Distinct().Count() > 1)
            {
                throw new ArgumentException("All packages to deprecate must have the same ID.", nameof(packages));
            }

            using (var strategy = new SuspendDbExecutionStrategy())
                using (var transaction = _entitiesContext.GetDatabase().BeginTransaction())
                {
                    var shouldDelete = status == PackageDeprecationStatus.NotDeprecated;
                    var deprecations = new List <PackageDeprecation>();
                    foreach (var package in packages)
                    {
                        var deprecation = package.Deprecations.SingleOrDefault();
                        if (shouldDelete)
                        {
                            if (deprecation != null)
                            {
                                package.Deprecations.Remove(deprecation);
                                deprecations.Add(deprecation);
                            }
                        }
                        else
                        {
                            if (deprecation == null)
                            {
                                deprecation = new PackageDeprecation
                                {
                                    Package = package
                                };

                                package.Deprecations.Add(deprecation);
                                deprecations.Add(deprecation);
                            }

                            deprecation.Status           = status;
                            deprecation.DeprecatedByUser = user;

                            deprecation.AlternatePackageRegistration = alternatePackageRegistration;
                            deprecation.AlternatePackage             = alternatePackage;

                            deprecation.CustomMessage = customMessage;
                        }
                    }

                    if (shouldDelete)
                    {
                        _entitiesContext.Deprecations.RemoveRange(deprecations);
                    }
                    else
                    {
                        _entitiesContext.Deprecations.AddRange(deprecations);
                    }

                    await _entitiesContext.SaveChangesAsync();

                    await _packageUpdateService.UpdatePackagesAsync(packages);

                    transaction.Commit();

                    _telemetryService.TrackPackageDeprecate(
                        packages,
                        status,
                        alternatePackageRegistration,
                        alternatePackage,
                        !string.IsNullOrWhiteSpace(customMessage));

                    foreach (var package in packages)
                    {
                        await _auditingService.SaveAuditRecordAsync(
                            new PackageAuditRecord(
                                package,
                                status == PackageDeprecationStatus.NotDeprecated ? AuditedPackageAction.Undeprecate : AuditedPackageAction.Deprecate,
                                status == PackageDeprecationStatus.NotDeprecated ? PackageUndeprecatedVia.Web : PackageDeprecatedVia.Web));
                    }
                }
        }