private async Task RemoveMemberships(User user, User requestingUser, AccountDeletionOrphanPackagePolicy orphanPackagePolicy) { foreach (var membership in user.Organizations.ToArray()) { var organization = membership.Organization; var members = organization.Members.ToList(); var collaborators = members.Where(m => !m.IsAdmin).ToList(); var memberCount = members.Count(); user.Organizations.Remove(membership); if (memberCount < 2) { // The user we are deleting is the only member of the organization. // We should delete the entire organization. await DeleteAccountImplAsync(organization, requestingUser, orphanPackagePolicy); } else if (memberCount - 1 <= collaborators.Count()) { // All other members of this organization are collaborators, so we should promote them to administrators. foreach (var collaborator in collaborators) { collaborator.IsAdmin = true; } } } foreach (var membershipRequest in user.OrganizationRequests.ToArray()) { user.OrganizationRequests.Remove(membershipRequest); } foreach (var transformationRequest in user.OrganizationMigrationRequests.ToArray()) { user.OrganizationMigrationRequests.Remove(transformationRequest); transformationRequest.NewOrganization.OrganizationMigrationRequest = null; } var migrationRequest = user.OrganizationMigrationRequest; user.OrganizationMigrationRequest = null; if (migrationRequest != null) { migrationRequest.AdminUser.OrganizationMigrationRequests.Remove(migrationRequest); } await _entitiesContext.SaveChangesAsync(); }
private async Task RemoveMemberships(User user, User requestingUser, AccountDeletionOrphanPackagePolicy orphanPackagePolicy) { foreach (var membership in user.Organizations.ToList()) { user.Organizations.Remove(membership); var organization = membership.Organization; var otherMembers = organization.Members .Where(m => !m.Member.MatchesUser(user)); if (!otherMembers.Any()) { // The user we are deleting is the only member of the organization. // We should delete the entire organization. await DeleteAccountImplAsync(organization, requestingUser, orphanPackagePolicy, commitChanges : false); } else if (otherMembers.All(m => !m.IsAdmin)) { // All other members of this organization are collaborators, so we should promote them to administrators. foreach (var collaborator in otherMembers) { collaborator.IsAdmin = true; } } } foreach (var membershipRequest in user.OrganizationRequests.ToList()) { user.OrganizationRequests.Remove(membershipRequest); } foreach (var transformationRequest in user.OrganizationMigrationRequests.ToList()) { user.OrganizationMigrationRequests.Remove(transformationRequest); transformationRequest.NewOrganization.OrganizationMigrationRequest = null; } var migrationRequest = user.OrganizationMigrationRequest; user.OrganizationMigrationRequest = null; if (migrationRequest != null) { migrationRequest.AdminUser.OrganizationMigrationRequests.Remove(migrationRequest); } }
private async Task RemovePackageOwnership(User user, User requestingUser, AccountDeletionOrphanPackagePolicy orphanPackagePolicy) { foreach (var package in GetPackagesOwnedByUser(user)) { if (_packageService.WillPackageBeOrphanedIfOwnerRemoved(package.PackageRegistration, user)) { if (orphanPackagePolicy == AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans) { throw new InvalidOperationException($"Deleting user '{user.Username}' will make package '{package.PackageRegistration.Id}' an orphan, but no orphans were expected."); } else if (orphanPackagePolicy == AccountDeletionOrphanPackagePolicy.UnlistOrphans) { await _packageService.MarkPackageUnlistedAsync(package, commitChanges : true); } } await _packageOwnershipManagementService.RemovePackageOwnerAsync(package.PackageRegistration, requestingUser, user, commitAsTransaction : false); } }
private async Task RemovePackageOwnership(User user, User requestingUser, AccountDeletionOrphanPackagePolicy orphanPackagePolicy) { foreach (var package in GetPackagesOwnedByUser(user)) { var owners = user is Organization ? package.PackageRegistration.Owners : _packageService.GetPackageUserAccountOwners(package); if (owners.Count() <= 1) { // Package will be orphaned by removing ownership. if (orphanPackagePolicy == AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans) { throw new InvalidOperationException($"Deleting user '{user.Username}' will make package '{package.PackageRegistration.Id}' an orphan, but no orphans were expected."); } else if (orphanPackagePolicy == AccountDeletionOrphanPackagePolicy.UnlistOrphans) { await _packageService.MarkPackageUnlistedAsync(package, commitChanges : true); } } await _packageOwnershipManagementService.RemovePackageOwnerAsync(package.PackageRegistration, requestingUser, user, commitAsTransaction : false); } }
public async Task <DeleteUserAccountStatus> DeleteAccountAsync(User userToBeDeleted, User userToExecuteTheDelete, bool commitAsTransaction, AccountDeletionOrphanPackagePolicy orphanPackagePolicy = AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans) { if (userToBeDeleted == null) { throw new ArgumentNullException(nameof(userToBeDeleted)); } if (userToExecuteTheDelete == null) { throw new ArgumentNullException(nameof(userToExecuteTheDelete)); } if (userToBeDeleted.IsDeleted) { return(new DeleteUserAccountStatus() { Success = false, Description = string.Format(CultureInfo.CurrentCulture, Strings.AccountDelete_AccountAlreadyDeleted, userToBeDeleted.Username), AccountName = userToBeDeleted.Username }); } var deleteUserAccountStatus = await RunAccountDeletionTask( () => DeleteAccountImplAsync( userToBeDeleted, userToExecuteTheDelete, orphanPackagePolicy), userToBeDeleted, userToExecuteTheDelete, commitAsTransaction); _telemetryService.TrackAccountDeletionCompleted(userToBeDeleted, userToExecuteTheDelete, deleteUserAccountStatus.Success); return(deleteUserAccountStatus); }
private async Task DeleteAccountImplAsync(User userToBeDeleted, User userToExecuteTheDelete, AccountDeletionOrphanPackagePolicy orphanPackagePolicy) { await RemoveReservedNamespaces(userToBeDeleted); await RemovePackageOwnership(userToBeDeleted, userToExecuteTheDelete, orphanPackagePolicy); await RemoveMemberships(userToBeDeleted, userToExecuteTheDelete, orphanPackagePolicy); await RemoveSecurityPolicies(userToBeDeleted); await RemoveUserCredentials(userToBeDeleted); await RemovePackageOwnershipRequests(userToBeDeleted); var organizationToBeDeleted = userToBeDeleted as Organization; if (organizationToBeDeleted != null) { await RemoveMembers(organizationToBeDeleted); } if (!userToBeDeleted.Confirmed) { // Unconfirmed users should be hard-deleted. // Another account with the same username can be created. await RemoveUser(userToBeDeleted); } else { // Confirmed users should be soft-deleted. // Another account with the same username cannot be created. await RemoveUserDataInUserTable(userToBeDeleted); await InsertDeleteAccount( userToBeDeleted, userToExecuteTheDelete); } }
private async Task DeleteAccountImplAsync(User userToBeDeleted, User userToExecuteTheDelete, AccountDeletionOrphanPackagePolicy orphanPackagePolicy, bool commitChanges = true) { await RemoveReservedNamespaces(userToBeDeleted); await RemovePackageOwnership(userToBeDeleted, userToExecuteTheDelete, orphanPackagePolicy); await RemoveMemberships(userToBeDeleted, userToExecuteTheDelete, orphanPackagePolicy); await RemoveSecurityPolicies(userToBeDeleted); await RemoveUserCredentials(userToBeDeleted); await RemovePackageOwnershipRequests(userToBeDeleted); ResetPackagesAndAccountsDeletedBy(userToBeDeleted); RemovePackagePushedBy(userToBeDeleted); RemovePackageDeprecatedBy(userToBeDeleted); var organizationToBeDeleted = userToBeDeleted as Organization; if (organizationToBeDeleted != null) { RemoveMembers(organizationToBeDeleted); } if (!userToBeDeleted.Confirmed) { // Unconfirmed users should be hard-deleted. // Another account with the same username can be created. RemoveUser(userToBeDeleted); } else { // Confirmed users should be soft-deleted. // Another account with the same username cannot be created. RemoveUserDataInUserTable(userToBeDeleted); InsertDeleteAccount( userToBeDeleted, userToExecuteTheDelete); } if (commitChanges) { await _entitiesContext.SaveChangesAsync(); } }
public async Task DeleteOrganization(bool isPackageOrphaned, AccountDeletionOrphanPackagePolicy orphanPolicy) { // Arrange var member = new User("testUser") { Key = Key++ }; var organization = new Organization("testOrganization") { Key = Key++, EmailAddress = "*****@*****.**" }; var membership = new Membership() { Organization = organization, Member = member }; member.Organizations.Add(membership); organization.Members.Add(membership); var requestedMember = new User("testRequestedMember") { Key = Key++ }; var memberRequest = new MembershipRequest() { Organization = organization, NewMember = requestedMember }; requestedMember.OrganizationRequests.Add(memberRequest); organization.MemberRequests.Add(memberRequest); PackageRegistration registration = new PackageRegistration(); registration.Owners.Add(organization); Package p = new Package() { Description = "TestPackage", Key = 1 }; p.PackageRegistration = registration; registration.Packages.Add(p); var testableService = new DeleteAccountTestService(organization, registration); var deleteAccountService = testableService.GetDeleteAccountService(isPackageOrphaned); // Act var status = await deleteAccountService. DeleteAccountAsync( organization, member, commitAsTransaction : false, orphanPackagePolicy : orphanPolicy); // Assert if (orphanPolicy == AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans && isPackageOrphaned) { Assert.False(status.Success); Assert.Equal(organization.Confirmed, organization.EmailAddress != null); Assert.True(registration.Owners.Any(o => o.MatchesUser(organization))); Assert.NotEmpty(organization.SecurityPolicies); Assert.Empty(testableService.DeletedAccounts); Assert.NotEmpty(testableService.PackageOwnerRequests); Assert.Empty(testableService.AuditService.Records); Assert.False(testableService.HasDeletedOwnerScope); Assert.Empty(testableService.AuditService.Records); } else { Assert.True(status.Success); Assert.Null(organization.EmailAddress); Assert.Equal( orphanPolicy == AccountDeletionOrphanPackagePolicy.UnlistOrphans && isPackageOrphaned, !registration.Packages.Single().Listed); Assert.False(registration.Owners.Any(o => o.MatchesUser(organization))); Assert.Empty(organization.SecurityPolicies); Assert.Single(testableService.DeletedAccounts); Assert.Empty(testableService.PackageOwnerRequests); Assert.Single(testableService.AuditService.Records); Assert.True(testableService.HasDeletedOwnerScope); var deleteRecord = testableService.AuditService.Records[0] as DeleteAccountAuditRecord; Assert.True(deleteRecord != null); } // Reserved namespaces and support requests are deleted before the request fails due to orphaned packages. // Because we are not committing as a transaction in these tests, they remain deleted. // In production, they would not be deleted because the transaction they were deleted in would fail. Assert.Empty(organization.ReservedNamespaces); Assert.Single(testableService.SupportRequests); }
public async Task DeleteHappyUser(bool isPackageOrphaned, AccountDeletionOrphanPackagePolicy orphanPolicy) { // Arrange PackageRegistration registration = null; var testUser = CreateTestUser(ref registration); var testUserOrganizations = testUser.Organizations.ToList(); var testableService = new DeleteAccountTestService(testUser, registration); var deleteAccountService = testableService.GetDeleteAccountService(isPackageOrphaned); // Act await deleteAccountService. DeleteAccountAsync(userToBeDeleted : testUser, userToExecuteTheDelete : testUser, commitAsTransaction : false, orphanPackagePolicy : orphanPolicy); if (orphanPolicy == AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans && isPackageOrphaned) { Assert.True(registration.Owners.Any(o => o.MatchesUser(testUser))); Assert.NotEmpty(testUser.SecurityPolicies); Assert.True(registration.Packages.Single().Listed); Assert.NotNull(testUser.EmailAddress); Assert.Empty(testableService.DeletedAccounts); Assert.NotEmpty(testableService.PackageOwnerRequests); Assert.False(testableService.HasDeletedOwnerScope); Assert.Empty(testableService.AuditService.Records); Assert.NotNull(testUser.OrganizationMigrationRequest); Assert.NotEmpty(testUser.OrganizationMigrationRequests); Assert.NotEmpty(testUser.OrganizationRequests); Assert.NotEmpty(testUser.Organizations); } else { Assert.False(registration.Owners.Any(o => o.MatchesUser(testUser))); Assert.Empty(testUser.SecurityPolicies); Assert.Equal( orphanPolicy == AccountDeletionOrphanPackagePolicy.UnlistOrphans && isPackageOrphaned, !registration.Packages.Single().Listed); Assert.Null(testUser.EmailAddress); Assert.Single(testableService.DeletedAccounts); Assert.Empty(testableService.PackageOwnerRequests); Assert.True(testableService.HasDeletedOwnerScope); Assert.Single(testableService.AuditService.Records); Assert.Null(testUser.OrganizationMigrationRequest); Assert.Empty(testUser.OrganizationMigrationRequests); Assert.Empty(testUser.OrganizationRequests); Assert.Empty(testUser.Organizations); foreach (var testUserOrganization in testUserOrganizations) { var notDeletedMembers = testUserOrganization.Organization.Members.Where(m => m.Member != testUser); if (notDeletedMembers.Any()) { // If an organization that the deleted user was a part of had other members, it should have at least one admin. Assert.Contains(notDeletedMembers, m => m.IsAdmin); } else { // If an organization that the deleted user was a part of had no other members, it should have been deleted. Assert.Contains(testUserOrganization.Organization, testableService.DeletedUsers); } } var deleteRecord = testableService.AuditService.Records[0] as DeleteAccountAuditRecord; Assert.True(deleteRecord != null); } // Reserved namespaces and support requests are deleted before the request fails due to orphaned packages. // Because we are not committing as a transaction in these tests, they remain deleted. // In production, they would not be deleted because the transaction they were deleted in would fail. Assert.Single(testableService.SupportRequests); Assert.Empty(testUser.ReservedNamespaces); }
public Task <DeleteAccountStatus> DeleteAccountAsync(User userToBeDeleted, User userToExecuteTheDelete, AccountDeletionOrphanPackagePolicy orphanPackagePolicy = AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans) { return(Task.FromResult(new DeleteAccountStatus() { Success = true, })); }
public async Task <DeleteAccountStatus> DeleteAccountAsync(User userToBeDeleted, User userToExecuteTheDelete, AccountDeletionOrphanPackagePolicy orphanPackagePolicy = AccountDeletionOrphanPackagePolicy.DoNotAllowOrphans) { if (userToBeDeleted == null) { throw new ArgumentNullException(nameof(userToBeDeleted)); } var result = new DeleteAccountStatus() { AccountName = userToBeDeleted.Username }; var isSupportRequestCreated = await _supportRequestService.TryAddDeleteSupportRequestAsync(userToBeDeleted); if (!isSupportRequestCreated) { result.Success = false; result.Description = ServicesStrings.AccountDelete_CreateSupportRequestFails; return(result); } var sourceName = GalleryUserAccountDeleteMessageSourceName; if (userToExecuteTheDelete.IsAdministrator) { sourceName = GalleryAdminAccountDeleteMessageSourceName; } var messageData = new AccountDeleteMessage(userToBeDeleted.Username, source: sourceName); var message = _serializer.Serialize(messageData); try { await _topicClient.SendAsync(message); // if SendAsync doesn't throw, as far as we can tell, the message went through. result.Description = string.Format(CultureInfo.CurrentCulture, ServicesStrings.AsyncAccountDelete_Success, userToBeDeleted.Username); result.Success = true; } catch (Exception ex) { // See https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-exceptions for a list of possible exceptions _logger.LogError(0, ex, "Failed to enqueue to AccountDeleter."); result.Success = false; result.Description = ServicesStrings.AsyncAccountDelete_Fail; } return(result); }