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 MarkPackageListedAsync(Package package, bool commitChanges = true) { if (package == null) { throw new ArgumentNullException(nameof(package)); } if (package.Listed) { return; } if (package.PackageStatusKey == PackageStatus.Deleted) { throw new InvalidOperationException("A deleted package should never be listed!"); } if (!package.Listed && (package.IsLatestStable || package.IsLatest)) { throw new InvalidOperationException("An unlisted package should never be latest or latest stable!"); } package.Listed = true; package.LastUpdated = DateTime.UtcNow; // NOTE: LastEdited will be overwritten by a trigger defined in the migration named "AddTriggerForPackagesLastEdited". package.LastEdited = DateTime.UtcNow; await UpdateIsLatestAsync(package.PackageRegistration, commitChanges : false); await _auditingService.SaveAuditRecordAsync(new PackageAuditRecord(package, AuditedPackageAction.List)); if (commitChanges) { await _packageRepository.CommitChangesAsync(); } }
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(); }
public async Task SoftDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature) { List <PackageRegistration> packageRegistrations; // Must suspend the retry execution strategy in order to use transactions. using (EntitiesConfiguration.SuspendRetriableExecutionStrategy()) { using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { // Increase command timeout _entitiesContext.SetCommandTimeout(seconds: 300); // Keep package registrations 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.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.SoftDelete, reason)); } _packageDeletesRepository.InsertOnCommit(packageDelete); // Commit changes await _packageRepository.CommitChangesAsync(); await _packageDeletesRepository.CommitChangesAsync(); transaction.Commit(); } } // handle in separate transaction because of concurrency check with retry await UpdateIsLatestAsync(packageRegistrations); // Force refresh the index UpdateSearchIndex(); }
/// <summary> /// Asynchronously sets the signer as the required signer on all package registrations owned by the signer. /// </summary> /// <param name="signer">A user.</param> /// <returns>A task that represents the asynchronous operation.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="signer" /> is <c>null</c>.</exception> public async Task SetRequiredSignerAsync(User signer) { if (signer == null) { throw new ArgumentNullException(nameof(signer)); } var registrations = FindPackageRegistrationsByOwner(signer); var auditRecords = new List <PackageRegistrationAuditRecord>(); var packageIds = new List <string>(); var isCommitRequired = false; foreach (var registration in registrations) { string previousRequiredSigner = null; string newRequiredSigner = null; if (!registration.RequiredSigners.Contains(signer)) { previousRequiredSigner = registration.RequiredSigners.FirstOrDefault()?.Username; registration.RequiredSigners.Clear(); isCommitRequired = true; registration.RequiredSigners.Add(signer); newRequiredSigner = signer.Username; var auditRecord = PackageRegistrationAuditRecord.CreateForSetRequiredSigner( registration, previousRequiredSigner, newRequiredSigner); auditRecords.Add(auditRecord); packageIds.Add(registration.Id); } } if (isCommitRequired) { await _packageRegistrationRepository.CommitChangesAsync(); foreach (var auditRecord in auditRecords) { await _auditingService.SaveAuditRecordAsync(auditRecord); } foreach (var packageId in packageIds) { _telemetryService.TrackRequiredSignerSet(packageId); } } }
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 AddPackageOwnerAsync(PackageRegistration package, User newOwner) { package.Owners.Add(newOwner); await _packageRepository.CommitChangesAsync(); var request = _packageOwnerRequestService.GetPackageOwnershipRequests(package: package, newOwner: newOwner).FirstOrDefault(); if (request != null) { await _packageOwnerRequestService.DeletePackageOwnershipRequest(request); } await _auditingService.SaveAuditRecordAsync( new PackageRegistrationAuditRecord(package, AuditedPackageRegistrationAction.AddOwner, newOwner.Username)); }
public async Task AddPackageOwnerAsync(PackageRegistration package, User newOwner) { package.Owners.Add(newOwner); await _packageRepository.CommitChangesAsync(); var request = FindExistingPackageOwnerRequest(package, newOwner); if (request != null) { _packageOwnerRequestRepository.DeleteOnCommit(request); await _packageOwnerRequestRepository.CommitChangesAsync(); } await _auditingService.SaveAuditRecordAsync( new PackageRegistrationAuditRecord(package, AuditedPackageRegistrationAction.AddOwner, newOwner.Username)); }
public async Task MarkPackageListedAsync(Package package, bool commitChanges = true, bool updateIndex = true) { if (package == null) { throw new ArgumentNullException(nameof(package)); } if (package.Listed) { return; } if (package.PackageStatusKey == PackageStatus.Deleted) { throw new InvalidOperationException("A deleted package should never be listed!"); } if (package.PackageStatusKey == PackageStatus.FailedValidation) { throw new InvalidOperationException("A package that failed validation should never be listed!"); } if (!package.Listed && (package.IsLatestStable || package.IsLatest || package.IsLatestSemVer2 || package.IsLatestStableSemVer2)) { throw new InvalidOperationException("An unlisted package should never be latest or latest stable!"); } package.Listed = true; package.LastUpdated = DateTime.UtcNow; // NOTE: LastEdited will be overwritten by a trigger defined in the migration named "AddTriggerForPackagesLastEdited". package.LastEdited = DateTime.UtcNow; await _packageService.UpdateIsLatestAsync(package.PackageRegistration, commitChanges: false); await _auditingService.SaveAuditRecordAsync(new PackageAuditRecord(package, AuditedPackageAction.List)); _telemetryService.TrackPackageListed(package); if (commitChanges) { await _entitiesContext.SaveChangesAsync(); } if (updateIndex) { _indexingService.UpdatePackage(package); } }
/// <summary> /// On subscribe, set API keys with push capability to expire in <see cref="PushKeysExpirationInDays" /> days. /// </summary> /// <param name="context"></param> public async Task OnSubscribeAsync(UserSecurityPolicySubscriptionContext context) { var pushKeys = context.User.Credentials.Where(c => CredentialTypes.IsApiKey(c.Type) && ( c.Scopes.Count == 0 || c.Scopes.Any(s => s.AllowedAction.Equals(NuGetScopes.PackagePush, StringComparison.OrdinalIgnoreCase) || s.AllowedAction.Equals(NuGetScopes.PackagePushVersion, StringComparison.OrdinalIgnoreCase) )) ); var expires = DateTime.UtcNow.AddDays(PushKeysExpirationInDays); var expireTasks = new List <Task>(); foreach (var key in pushKeys) { if (!key.Expires.HasValue || key.Expires > expires) { expireTasks.Add(_auditing.SaveAuditRecordAsync( new UserAuditRecord(context.User, AuditedUserAction.ExpireCredential, key))); key.Expires = expires; } } await Task.WhenAll(expireTasks); _diagnostics.Information($"Expiring {pushKeys.Count()} keys with push capability for user '{context.User.Username}'."); }
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 }); } }
public async Task <FeatureFlagSaveResult> TrySaveAsync(FeatureFlags flags, string contentId) { var result = await TrySaveInternalAsync(flags, contentId); await _auditing.SaveAuditRecordAsync( new FeatureFlagsAuditRecord( AuditedFeatureFlagsAction.Update, flags, contentId, result)); return(result); }
public async Task <Certificate> AddCertificateAsync(HttpPostedFileBase file) { if (file == null) { throw new ArgumentNullException(nameof(file)); } _certificateValidator.Validate(file); using (var certificateFile = CertificateFile.Create(file.InputStream)) { var certificate = GetCertificate(certificateFile.Sha256Thumbprint); if (certificate == null) { await SaveToFileStorageAsync(certificateFile); certificate = new Certificate() { #pragma warning disable CS0618 // Only set the SHA1 thumbprint, for backwards compatibility. Never read it. Sha1Thumbprint = certificateFile.Sha1Thumbprint, #pragma warning restore CS0618 Thumbprint = certificateFile.Sha256Thumbprint, UserCertificates = new List <UserCertificate>() }; _certificateRepository.InsertOnCommit(certificate); await _certificateRepository.CommitChangesAsync(); await _auditingService.SaveAuditRecordAsync( new CertificateAuditRecord(AuditedCertificateAction.Add, certificate.Thumbprint)); _telemetryService.TrackCertificateAdded(certificateFile.Sha256Thumbprint); } return(certificate); } }
public async Task <Certificate> AddCertificateAsync(HttpPostedFileBase file) { if (file == null) { throw new ArgumentNullException(nameof(file)); } _certificateValidator.Validate(file); using (var certificateFile = CertificateFile.Create(file.InputStream)) { var certificate = GetCertificate(certificateFile.Sha256Thumbprint); if (certificate == null) { await SaveToFileStorageAsync(certificateFile); certificate = new Certificate() { Sha1Thumbprint = certificateFile.Sha1Thumbprint, Thumbprint = certificateFile.Sha256Thumbprint, UserCertificates = new List <UserCertificate>() }; _certificateRepository.InsertOnCommit(certificate); await _certificateRepository.CommitChangesAsync(); await _auditingService.SaveAuditRecordAsync( new CertificateAuditRecord(AuditedCertificateAction.Add, certificate.Thumbprint)); _telemetryService.TrackCertificateAdded(certificateFile.Sha256Thumbprint); } return(certificate); } }
public async Task <bool> TryAddDeleteSupportRequestAsync(User user) { var requestSent = await AddNewSupportRequestAsync( Strings.AccountDelete_SupportRequestTitle, Strings.AccountDelete_SupportRequestTitle, user.EmailAddress, "The user requested to have the account deleted.", user) != null; var status = requestSent ? DeleteAccountAuditRecord.ActionStatus.Success : DeleteAccountAuditRecord.ActionStatus.Failure; await _auditingService.SaveAuditRecordAsync(new DeleteAccountAuditRecord(username : user.Username, status : status, action : AuditedDeleteAccountAction.RequestAccountDeletion)); return(requestSent); }
public virtual async Task <JsonResult> Deprecate( string id, IEnumerable <string> versions, bool isLegacy, bool hasCriticalBugs, bool isOther, string alternatePackageId, string alternatePackageVersion, string customMessage) { var status = PackageDeprecationStatus.NotDeprecated; if (isLegacy) { status |= PackageDeprecationStatus.Legacy; } if (hasCriticalBugs) { status |= PackageDeprecationStatus.CriticalBugs; } if (isOther) { if (string.IsNullOrWhiteSpace(customMessage)) { return(DeprecateErrorResponse(HttpStatusCode.BadRequest, Strings.DeprecatePackage_CustomMessageRequired)); } status |= PackageDeprecationStatus.Other; } var currentUser = GetCurrentUser(); if (!_featureFlagService.IsManageDeprecationEnabled(GetCurrentUser())) { return(DeprecateErrorResponse(HttpStatusCode.Forbidden, Strings.DeprecatePackage_Forbidden)); } if (versions == null || !versions.Any()) { return(DeprecateErrorResponse(HttpStatusCode.BadRequest, Strings.DeprecatePackage_NoVersions)); } var packages = _packageService.FindPackagesById(id, PackageDeprecationFieldsToInclude.DeprecationAndRelationships); var registration = packages.FirstOrDefault()?.PackageRegistration; if (registration == null) { // This should only happen if someone hacks the form or if the package is deleted while the user is filling out the form. return(DeprecateErrorResponse( HttpStatusCode.NotFound, string.Format(Strings.DeprecatePackage_MissingRegistration, id))); } if (ActionsRequiringPermissions.DeprecatePackage.CheckPermissionsOnBehalfOfAnyAccount(currentUser, registration) != PermissionsCheckResult.Allowed) { return(DeprecateErrorResponse(HttpStatusCode.Forbidden, Strings.DeprecatePackage_Forbidden)); } if (registration.IsLocked) { return(DeprecateErrorResponse( HttpStatusCode.Forbidden, string.Format(Strings.DeprecatePackage_Locked, id))); } PackageRegistration alternatePackageRegistration = null; Package alternatePackage = null; if (!string.IsNullOrWhiteSpace(alternatePackageId)) { if (!string.IsNullOrWhiteSpace(alternatePackageVersion)) { alternatePackage = _packageService.FindPackageByIdAndVersionStrict(alternatePackageId, alternatePackageVersion); if (alternatePackage == null) { return(DeprecateErrorResponse( HttpStatusCode.NotFound, string.Format(Strings.DeprecatePackage_NoAlternatePackage, alternatePackageId, alternatePackageVersion))); } } else { alternatePackageRegistration = _packageService.FindPackageRegistrationById(alternatePackageId); if (alternatePackageRegistration == null) { return(DeprecateErrorResponse( HttpStatusCode.NotFound, string.Format(Strings.DeprecatePackage_NoAlternatePackageRegistration, alternatePackageId))); } } } var packagesToUpdate = new List <Package>(); foreach (var version in versions) { var normalizedVersion = NuGetVersionFormatter.Normalize(version); var package = packages.SingleOrDefault(v => v.NormalizedVersion == normalizedVersion); if (package == null) { // This should only happen if someone hacks the form or if a version of the package is deleted while the user is filling out the form. return(DeprecateErrorResponse( HttpStatusCode.NotFound, string.Format(Strings.DeprecatePackage_MissingVersion, id))); } else { packagesToUpdate.Add(package); } } await _deprecationService.UpdateDeprecation( packagesToUpdate, status, alternatePackageRegistration, alternatePackage, customMessage, currentUser); foreach (var packageToUpdate in packagesToUpdate) { await _auditingService.SaveAuditRecordAsync( new PackageAuditRecord( packageToUpdate, status == PackageDeprecationStatus.NotDeprecated ? AuditedPackageAction.Undeprecate : AuditedPackageAction.Deprecate, status == PackageDeprecationStatus.NotDeprecated ? PackageUndeprecatedVia.Web : PackageDeprecatedVia.Web)); } return(Json(HttpStatusCode.OK)); }
/// <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 }); } }
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)); } } }