public async Task DeleteOwnerFromReservedNamespaceAsync(string prefix, string username, bool commitChanges = true) { if (string.IsNullOrWhiteSpace(prefix)) { throw new ArgumentException(ServicesStrings.ReservedNamespace_InvalidNamespace); } if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentException(ServicesStrings.ReservedNamespace_InvalidUsername); } var namespaceToModify = FindReservedNamespaceForPrefix(prefix) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, ServicesStrings.ReservedNamespace_NamespaceNotFound, prefix)); List <PackageRegistration> packageRegistrationsToMarkUnverified; if (commitChanges) { using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = EntitiesContext.GetDatabase().BeginTransaction()) { packageRegistrationsToMarkUnverified = await DeleteOwnerFromReservedNamespaceImplAsync(prefix, username, namespaceToModify); transaction.Commit(); } } else { packageRegistrationsToMarkUnverified = await DeleteOwnerFromReservedNamespaceImplAsync(prefix, username, namespaceToModify, commitChanges : false); } await AuditingService.SaveAuditRecordAsync( new ReservedNamespaceAuditRecord(namespaceToModify, AuditedReservedNamespaceAction.RemoveOwner, username, packageRegistrationsToMarkUnverified)); }
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 DeleteReservedNamespaceAsync(string existingNamespace) { if (string.IsNullOrWhiteSpace(existingNamespace)) { throw new ArgumentException(ServicesStrings.ReservedNamespace_InvalidNamespace); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = EntitiesContext.GetDatabase().BeginTransaction()) { var namespaceToDelete = FindReservedNamespaceForPrefix(existingNamespace) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, ServicesStrings.ReservedNamespace_NamespaceNotFound, existingNamespace)); // Delete verified flags on corresponding packages for this prefix if // it is the only prefix matching the package registration. var packageRegistrationsToMarkUnverified = namespaceToDelete .PackageRegistrations .Where(pr => pr.ReservedNamespaces.Count() == 1) .ToList(); if (packageRegistrationsToMarkUnverified.Any()) { await PackageService.UpdatePackageVerifiedStatusAsync(packageRegistrationsToMarkUnverified, isVerified : false); } ReservedNamespaceRepository.DeleteOnCommit(namespaceToDelete); await ReservedNamespaceRepository.CommitChangesAsync(); transaction.Commit(); await AuditingService.SaveAuditRecordAsync( new ReservedNamespaceAuditRecord(namespaceToDelete, AuditedReservedNamespaceAction.UnreserveNamespace)); } }
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 HardDeletePackagesAsync(IEnumerable <Package> packages, User deletedBy, string reason, string signature, bool deleteEmptyPackageRegistration) { 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); // Remove the package and related entities from the database foreach (var package in packages) { UnlinkPackageDeprecations(package); await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pa FROM PackageAuthors pa JOIN Packages p ON p.[Key] = pa.PackageKey WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pd FROM PackageDependencies pd JOIN Packages p ON p.[Key] = pd.PackageKey WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await ExecuteSqlCommandAsync(_entitiesContext.GetDatabase(), "DELETE pf FROM PackageFrameworks pf JOIN Packages p ON p.[Key] = pf.Package_Key WHERE p.[Key] = @key", new SqlParameter("@key", package.Key)); await _auditingService.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.Delete, reason)); _telemetryService.TrackPackageDelete(package, isHardDelete: true); package.PackageRegistration.Packages.Remove(package); _packageRepository.DeleteOnCommit(package); } // Update latest versions await UpdateIsLatestAsync(packageRegistrations); // Commit changes to package repository await _packageRepository.CommitChangesAsync(); // Remove package registrations that have no more packages? if (deleteEmptyPackageRegistration) { await RemovePackageRegistrationsWithoutPackages(packageRegistrations); } // Commit transaction transaction.Commit(); } // Force refresh the index UpdateSearchIndex(); }
public async Task DeleteOwnerFromReservedNamespaceAsync(string prefix, string username) { if (string.IsNullOrWhiteSpace(prefix)) { throw new ArgumentException(Strings.ReservedNamespace_InvalidNamespace); } if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentException(Strings.ReservedNamespace_InvalidUsername); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = EntitiesContext.GetDatabase().BeginTransaction()) { var namespaceToModify = FindReservedNamespaceForPrefix(prefix) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, Strings.ReservedNamespace_NamespaceNotFound, prefix)); var userToRemove = UserService.FindByUsername(username) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, Strings.ReservedNamespace_UserNotFound, username)); if (!namespaceToModify.Owners.Contains(userToRemove)) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ReservedNamespace_UserNotAnOwner, username)); } var packagesOwnedByUserMatchingPrefix = namespaceToModify .PackageRegistrations .Where(pr => pr .Owners .Any(pro => pro.Username == userToRemove.Username)) .ToList(); // Remove verified mark for package registrations if the user to be removed is the only prefix owner // for the given package registration. var packageRegistrationsToMarkUnverified = packagesOwnedByUserMatchingPrefix .Where(pr => pr.Owners.Intersect(namespaceToModify.Owners).Count() == 1) .ToList(); if (packageRegistrationsToMarkUnverified.Any()) { packageRegistrationsToMarkUnverified .ForEach(pr => namespaceToModify.PackageRegistrations.Remove(pr)); await PackageService.UpdatePackageVerifiedStatusAsync(packageRegistrationsToMarkUnverified, isVerified : false); } namespaceToModify.Owners.Remove(userToRemove); await ReservedNamespaceRepository.CommitChangesAsync(); transaction.Commit(); } }
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) { package.Listed = false; package.Deleted = true; package.PackageStatusKey = PackageStatus.Deleted; packageDelete.Packages.Add(package); await _auditingService.SaveAuditRecordAsync(CreateAuditRecord(package, package.PackageRegistration, AuditedPackageAction.SoftDelete, reason)); } _packageDeletesRepository.InsertOnCommit(packageDelete); // Update latest versions await UpdateIsLatestAsync(packageRegistrations); // Commit changes await _packageRepository.CommitChangesAsync(); await _packageDeletesRepository.CommitChangesAsync(); transaction.Commit(); } // Force refresh the index UpdateSearchIndex(); }
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 }); } }
/// <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 }); } }
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 AddOwnerToReservedNamespaceAsync(string prefix, string username) { if (string.IsNullOrWhiteSpace(prefix)) { throw new ArgumentException(Strings.ReservedNamespace_InvalidNamespace); } if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentException(Strings.ReservedNamespace_InvalidUsername); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = EntitiesContext.GetDatabase().BeginTransaction()) { var namespaceToModify = FindReservedNamespaceForPrefix(prefix) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, Strings.ReservedNamespace_NamespaceNotFound, prefix)); var userToAdd = UserService.FindByUsername(username) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, Strings.ReservedNamespace_UserNotFound, username)); if (namespaceToModify.Owners.Contains(userToAdd)) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ReservedNamespace_UserAlreadyOwner, username)); } // Mark all packages owned by this user that start with the given namespace as verified. var allPackageRegistrationsForUser = PackageService.FindPackageRegistrationsByOwner(userToAdd); var packageRegistrationsMatchingNamespace = allPackageRegistrationsForUser .Where(pr => pr.Id.StartsWith(namespaceToModify.Value, StringComparison.OrdinalIgnoreCase)) .ToList(); if (packageRegistrationsMatchingNamespace.Any()) { packageRegistrationsMatchingNamespace .ForEach(pr => namespaceToModify.PackageRegistrations.Add(pr)); await PackageService.UpdatePackageVerifiedStatusAsync(packageRegistrationsMatchingNamespace.AsReadOnly(), isVerified : true); } namespaceToModify.Owners.Add(userToAdd); await ReservedNamespaceRepository.CommitChangesAsync(); transaction.Commit(); } }
public async Task RemovePackageOwnerAsync(PackageRegistration packageRegistration, User requestingOwner, User ownerToBeRemoved, bool commitAsTransaction = true) { if (packageRegistration == null) { throw new ArgumentNullException(nameof(packageRegistration)); } if (requestingOwner == null) { throw new ArgumentNullException(nameof(requestingOwner)); } if (ownerToBeRemoved == null) { throw new ArgumentNullException(nameof(ownerToBeRemoved)); } if (OwnerHasPermissionsToRemove(requestingOwner, ownerToBeRemoved, packageRegistration)) { if (commitAsTransaction) { using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { await RemovePackageOwnerImplAsync(packageRegistration, ownerToBeRemoved); transaction.Commit(); } } else { await RemovePackageOwnerImplAsync(packageRegistration, ownerToBeRemoved); } await _auditingService.SaveAuditRecordAsync( new PackageRegistrationAuditRecord(packageRegistration, AuditedPackageRegistrationAction.RemoveOwner, ownerToBeRemoved.Username)); } else { throw new InvalidOperationException(string.Format(Strings.RemoveOwner_NotAllowed, requestingOwner.Username, ownerToBeRemoved.Username)); } }
public async Task UpdateVulnerabilityAsync(PackageVulnerability vulnerability, bool withdrawn) { if (vulnerability == null) { throw new ArgumentNullException(nameof(vulnerability)); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { var packagesToUpdate = UpdateVulnerabilityInternal(vulnerability, withdrawn); await _entitiesContext.SaveChangesAsync(); if (packagesToUpdate.Any()) { await _packageUpdateService.UpdatePackagesAsync(packagesToUpdate.ToList()); } transaction.Commit(); } }
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)); } } }
public async Task AddOwnerToReservedNamespaceAsync(string prefix, string username) { if (string.IsNullOrWhiteSpace(prefix)) { throw new ArgumentException(ServicesStrings.ReservedNamespace_InvalidNamespace); } if (string.IsNullOrWhiteSpace(username)) { throw new ArgumentException(ServicesStrings.ReservedNamespace_InvalidUsername); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = EntitiesContext.GetDatabase().BeginTransaction()) { var namespaceToModify = FindReservedNamespaceForPrefix(prefix) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, ServicesStrings.ReservedNamespace_NamespaceNotFound, prefix)); var userToAdd = UserService.FindByUsername(username) ?? throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, ServicesStrings.ReservedNamespace_UserNotFound, username)); if (namespaceToModify.Owners.Contains(userToAdd)) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ServicesStrings.ReservedNamespace_UserAlreadyOwner, username)); } Expression <Func <PackageRegistration, bool> > predicate; if (namespaceToModify.IsPrefix) { predicate = registration => registration.Id.StartsWith(namespaceToModify.Value); } else { predicate = registration => registration.Id.Equals(namespaceToModify.Value); } // Mark all packages owned by this user that start with the given namespace as verified. var allPackageRegistrationsForUser = PackageService.FindPackageRegistrationsByOwner(userToAdd); // We need 'AsQueryable' here because FindPackageRegistrationsByOwner returns an IEnumerable // and to evaluate the predicate server side, the casting is essential. var packageRegistrationsMatchingNamespace = allPackageRegistrationsForUser .AsQueryable() .Where(predicate) .ToList(); if (packageRegistrationsMatchingNamespace.Any()) { packageRegistrationsMatchingNamespace .ForEach(pr => namespaceToModify.PackageRegistrations.Add(pr)); await PackageService.UpdatePackageVerifiedStatusAsync(packageRegistrationsMatchingNamespace.AsReadOnly(), isVerified : true); } namespaceToModify.Owners.Add(userToAdd); await ReservedNamespaceRepository.CommitChangesAsync(); transaction.Commit(); await AuditingService.SaveAuditRecordAsync( new ReservedNamespaceAuditRecord(namespaceToModify, AuditedReservedNamespaceAction.AddOwner, username, packageRegistrationsMatchingNamespace)); } }
public async Task RemovePackageOwnerAsync(PackageRegistration packageRegistration, User requestingOwner, User ownerToBeRemoved) { if (packageRegistration == null) { throw new ArgumentNullException(nameof(packageRegistration)); } if (requestingOwner == null) { throw new ArgumentNullException(nameof(requestingOwner)); } if (ownerToBeRemoved == null) { throw new ArgumentNullException(nameof(ownerToBeRemoved)); } if (OwnerHasPermissionsToRemove(requestingOwner, ownerToBeRemoved, packageRegistration)) { using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { // 1. Remove this package registration from the namespaces owned by this user if he is the only package owner in the set of matching namespaces // 2. Remove the IsVerified flag from package registration if all the matching namespaces are owned by this user alone (no other package owner owns a matching namespace for this PR) var allMatchingNamespacesForRegistration = packageRegistration.ReservedNamespaces; if (allMatchingNamespacesForRegistration.Any()) { var allPackageOwners = packageRegistration.Owners; var matchingNamespacesOwnedByUser = allMatchingNamespacesForRegistration .Where(rn => rn.Owners.Any(o => o == ownerToBeRemoved)); var namespacesToModify = matchingNamespacesOwnedByUser .Where(rn => rn.Owners.Intersect(allPackageOwners).Count() == 1) .ToList(); if (namespacesToModify.Any()) { // The package will lose its 'IsVerified' flag if the user is the only package owner who owns all the namespaces that match this registration var shouldModifyIsVerified = allMatchingNamespacesForRegistration.Count() == namespacesToModify.Count(); if (shouldModifyIsVerified && packageRegistration.IsVerified) { await _packageService.UpdatePackageVerifiedStatusAsync(new List <PackageRegistration> { packageRegistration }, isVerified : false); } namespacesToModify .ForEach(rn => _reservedNamespaceService.RemovePackageRegistrationFromNamespace(rn.Value, packageRegistration)); await _entitiesContext.SaveChangesAsync(); } } // Remove the user from owners list of package registration await _packageService.RemovePackageOwnerAsync(packageRegistration, ownerToBeRemoved); transaction.Commit(); } await _auditingService.SaveAuditRecordAsync( new PackageRegistrationAuditRecord(packageRegistration, AuditedPackageRegistrationAction.RemoveOwner, ownerToBeRemoved.Username)); } else { throw new InvalidOperationException(string.Format(Strings.RemoveOwner_NotAllowed, requestingOwner.Username, ownerToBeRemoved.Username)); } }
/// <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 }); } }