mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 02:22:26 -05:00 
			
		
		
		
	[bugfix] fix possible infinite loop on federated AP profile delete (#1091)
* refactor federator account statuses delete to better catch errors, ensure next maxID is always set Signed-off-by: kim <grufwub@gmail.com> * fix error statement missing 2nd format operator Signed-off-by: kim <grufwub@gmail.com> Signed-off-by: kim <grufwub@gmail.com>
This commit is contained in:
		
					parent
					
						
							
								0490440fe0
							
						
					
				
			
			
				commit
				
					
						9be16852f2
					
				
			
		
					 1 changed files with 58 additions and 55 deletions
				
			
		|  | @ -56,17 +56,16 @@ import ( | ||||||
| // 17. Delete account's timeline | // 17. Delete account's timeline | ||||||
| // 18. Delete account itself | // 18. Delete account itself | ||||||
| func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode { | func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode { | ||||||
| 	fields := kv.Fields{ | 	fields := kv.Fields{{"username", account.Username}} | ||||||
| 		{"username", account.Username}, | 
 | ||||||
| 	} |  | ||||||
| 	if account.Domain != "" { | 	if account.Domain != "" { | ||||||
| 		fields = append(fields, kv.Field{ | 		fields = append(fields, kv.Field{ | ||||||
| 			"domain", account.Domain, | 			"domain", account.Domain, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	l := log.WithFields(fields...) |  | ||||||
| 
 | 
 | ||||||
| 	l.Debug("beginning account delete process") | 	l := log.WithFields(fields...) | ||||||
|  | 	l.Trace("beginning account delete process") | ||||||
| 
 | 
 | ||||||
| 	// 1. Delete account's application(s), clients, and oauth tokens | 	// 1. Delete account's application(s), clients, and oauth tokens | ||||||
| 	// we only need to do this step for local account since remote ones won't have any tokens or applications on our server | 	// we only need to do this step for local account since remote ones won't have any tokens or applications on our server | ||||||
|  | @ -98,7 +97,7 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 2. Delete account's blocks | 	// 2. Delete account's blocks | ||||||
| 	l.Debug("deleting account blocks") | 	l.Trace("deleting account blocks") | ||||||
| 	// first delete any blocks that this account created | 	// first delete any blocks that this account created | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Block{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Block{}); err != nil { | ||||||
| 		l.Errorf("error deleting blocks created by account: %s", err) | 		l.Errorf("error deleting blocks created by account: %s", err) | ||||||
|  | @ -114,7 +113,7 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi | ||||||
| 
 | 
 | ||||||
| 	// 4. Delete account's follow requests | 	// 4. Delete account's follow requests | ||||||
| 	// TODO: federate these if necessary | 	// TODO: federate these if necessary | ||||||
| 	l.Debug("deleting account follow requests") | 	l.Trace("deleting account follow requests") | ||||||
| 	// first delete any follow requests that this account created | 	// first delete any follow requests that this account created | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil { | ||||||
| 		l.Errorf("error deleting follow requests created by account: %s", err) | 		l.Errorf("error deleting follow requests created by account: %s", err) | ||||||
|  | @ -127,7 +126,7 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi | ||||||
| 
 | 
 | ||||||
| 	// 5. Delete account's follows | 	// 5. Delete account's follows | ||||||
| 	// TODO: federate these if necessary | 	// TODO: federate these if necessary | ||||||
| 	l.Debug("deleting account follows") | 	l.Trace("deleting account follows") | ||||||
| 	// first delete any follows that this account created | 	// first delete any follows that this account created | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil { | ||||||
| 		l.Errorf("error deleting follows created by account: %s", err) | 		l.Errorf("error deleting follows created by account: %s", err) | ||||||
|  | @ -138,73 +137,80 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi | ||||||
| 		l.Errorf("error deleting follows targeting account: %s", err) | 		l.Errorf("error deleting follows targeting account: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var maxID string | ||||||
|  | 
 | ||||||
| 	// 6. Delete account's statuses | 	// 6. Delete account's statuses | ||||||
| 	l.Debug("deleting account statuses") | 	l.Trace("deleting account statuses") | ||||||
|  | 
 | ||||||
| 	// we'll select statuses 20 at a time so we don't wreck the db, and pass them through to the client api channel | 	// we'll select statuses 20 at a time so we don't wreck the db, and pass them through to the client api channel | ||||||
| 	// Deleting the statuses in this way also handles 7. Delete account's media attachments, 8. Delete account's mentions, and 9. Delete account's polls, | 	// Deleting the statuses in this way also handles 7. Delete account's media attachments, 8. Delete account's mentions, and 9. Delete account's polls, | ||||||
| 	// since these are all attached to statuses. | 	// since these are all attached to statuses. | ||||||
| 	var maxID string | 
 | ||||||
| selectStatusesLoop: |  | ||||||
| 	for { | 	for { | ||||||
|  | 		// Fetch next block of account statuses from database | ||||||
| 		statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, false, maxID, "", false, false, false) | 		statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, false, maxID, "", false, false, false) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if errors.Is(err, db.ErrNoEntries) { | 			if !errors.Is(err, db.ErrNoEntries) { | ||||||
| 				// no statuses left for this instance so we're done | 				// an actual error has occurred | ||||||
| 				l.Infof("Delete: done iterating through statuses for account %s", account.Username) | 				l.Errorf("Delete: db error selecting statuses for account %s: %s", account.Username, err) | ||||||
| 				break selectStatusesLoop |  | ||||||
| 			} | 			} | ||||||
| 
 | 			break | ||||||
| 			// an actual error has occurred |  | ||||||
| 			l.Errorf("Delete: db error selecting statuses for account %s: %s", account.Username, err) |  | ||||||
| 			break selectStatusesLoop |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for i, s := range statuses { | 		for _, status := range statuses { | ||||||
| 			// pass the status delete through the client api channel for processing | 			// Ensure account is set | ||||||
| 			s.Account = account | 			status.Account = account | ||||||
| 
 | 
 | ||||||
| 			l.Debug("putting status delete in the client api channel") | 			l.Tracef("queue client API status delete: %s", status.ID) | ||||||
|  | 
 | ||||||
|  | 			// pass the status delete through the client api channel for processing | ||||||
| 			p.clientWorker.Queue(messages.FromClientAPI{ | 			p.clientWorker.Queue(messages.FromClientAPI{ | ||||||
| 				APObjectType:   ap.ObjectNote, | 				APObjectType:   ap.ObjectNote, | ||||||
| 				APActivityType: ap.ActivityDelete, | 				APActivityType: ap.ActivityDelete, | ||||||
| 				GTSModel:       s, | 				GTSModel:       status, | ||||||
| 				OriginAccount:  account, | 				OriginAccount:  account, | ||||||
| 				TargetAccount:  account, | 				TargetAccount:  account, | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			// if there are any boosts of this status, delete them as well | 			// Look for any boosts of this status in DB | ||||||
| 			if boosts, err := p.db.GetStatusReblogs(ctx, s); err == nil { | 			boosts, err := p.db.GetStatusReblogs(ctx, status) | ||||||
| 				for _, b := range boosts { | 			if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
| 					if b.Account == nil { | 				l.Errorf("error fetching status reblogs for %q: %v", status.ID, err) | ||||||
| 						bAccount, err := p.db.GetAccountByID(ctx, b.AccountID) | 				continue | ||||||
| 						if err != nil { | 			} | ||||||
| 							l.Errorf("Delete: db error populating boosted status account: %v", err) | 
 | ||||||
| 							continue | 			for _, boost := range boosts { | ||||||
| 						} | 				if boost.Account == nil { | ||||||
| 						b.Account = bAccount | 					// Fetch the relevant account for this status boost | ||||||
|  | 					boostAcc, err := p.db.GetAccountByID(ctx, boost.AccountID) | ||||||
|  | 					if err != nil { | ||||||
|  | 						l.Errorf("error fetching boosted status account for %q: %v", boost.AccountID, err) | ||||||
|  | 						continue | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					l.Debug("putting boost undo in the client api channel") | 					// Set account model | ||||||
| 					p.clientWorker.Queue(messages.FromClientAPI{ | 					boost.Account = boostAcc | ||||||
| 						APObjectType:   ap.ActivityAnnounce, |  | ||||||
| 						APActivityType: ap.ActivityUndo, |  | ||||||
| 						GTSModel:       s, |  | ||||||
| 						OriginAccount:  b.Account, |  | ||||||
| 						TargetAccount:  account, |  | ||||||
| 					}) |  | ||||||
| 				} | 				} | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			// if this is the last status in the slice, set the maxID appropriately for the next query | 				l.Tracef("queue client API boost delete: %s", status.ID) | ||||||
| 			if i == len(statuses)-1 { | 
 | ||||||
| 				maxID = s.ID | 				// pass the boost delete through the client api channel for processing | ||||||
|  | 				p.clientWorker.Queue(messages.FromClientAPI{ | ||||||
|  | 					APObjectType:   ap.ActivityAnnounce, | ||||||
|  | 					APActivityType: ap.ActivityUndo, | ||||||
|  | 					GTSModel:       status, | ||||||
|  | 					OriginAccount:  boost.Account, | ||||||
|  | 					TargetAccount:  account, | ||||||
|  | 				}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		// Update next maxID from last status | ||||||
|  | 		maxID = statuses[len(statuses)-1].ID | ||||||
| 	} | 	} | ||||||
| 	l.Debug("done deleting statuses") |  | ||||||
| 
 | 
 | ||||||
| 	// 10. Delete account's notifications | 	// 10. Delete account's notifications | ||||||
| 	l.Debug("deleting account notifications") | 	l.Trace("deleting account notifications") | ||||||
| 	// first notifications created by account | 	// first notifications created by account | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "origin_account_id", Value: account.ID}}, &[]*gtsmodel.Notification{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "origin_account_id", Value: account.ID}}, &[]*gtsmodel.Notification{}); err != nil { | ||||||
| 		l.Errorf("error deleting notifications created by account: %s", err) | 		l.Errorf("error deleting notifications created by account: %s", err) | ||||||
|  | @ -216,20 +222,20 @@ selectStatusesLoop: | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 11. Delete account's bookmarks | 	// 11. Delete account's bookmarks | ||||||
| 	l.Debug("deleting account bookmarks") | 	l.Trace("deleting account bookmarks") | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusBookmark{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusBookmark{}); err != nil { | ||||||
| 		l.Errorf("error deleting bookmarks created by account: %s", err) | 		l.Errorf("error deleting bookmarks created by account: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 12. Delete account's faves | 	// 12. Delete account's faves | ||||||
| 	// TODO: federate these if necessary | 	// TODO: federate these if necessary | ||||||
| 	l.Debug("deleting account faves") | 	l.Trace("deleting account faves") | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusFave{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusFave{}); err != nil { | ||||||
| 		l.Errorf("error deleting faves created by account: %s", err) | 		l.Errorf("error deleting faves created by account: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 13. Delete account's mutes | 	// 13. Delete account's mutes | ||||||
| 	l.Debug("deleting account mutes") | 	l.Trace("deleting account mutes") | ||||||
| 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusMute{}); err != nil { | 	if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusMute{}); err != nil { | ||||||
| 		l.Errorf("error deleting status mutes created by account: %s", err) | 		l.Errorf("error deleting status mutes created by account: %s", err) | ||||||
| 	} | 	} | ||||||
|  | @ -242,7 +248,7 @@ selectStatusesLoop: | ||||||
| 
 | 
 | ||||||
| 	// 16. Delete account's user | 	// 16. Delete account's user | ||||||
| 	if user != nil { | 	if user != nil { | ||||||
| 		l.Debug("deleting account user") | 		l.Trace("deleting account user") | ||||||
| 		if err := p.db.DeleteUserByID(ctx, user.ID); err != nil { | 		if err := p.db.DeleteUserByID(ctx, user.ID); err != nil { | ||||||
| 			return gtserror.NewErrorInternalError(err) | 			return gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
|  | @ -254,7 +260,6 @@ selectStatusesLoop: | ||||||
| 	// 18. Delete account itself | 	// 18. Delete account itself | ||||||
| 	// to prevent the account being created again, set all these fields and update it in the db | 	// to prevent the account being created again, set all these fields and update it in the db | ||||||
| 	// the account won't actually be *removed* from the database but it will be set to just a stub | 	// the account won't actually be *removed* from the database but it will be set to just a stub | ||||||
| 
 |  | ||||||
| 	account.Note = "" | 	account.Note = "" | ||||||
| 	account.DisplayName = "" | 	account.DisplayName = "" | ||||||
| 	account.AvatarMediaAttachmentID = "" | 	account.AvatarMediaAttachmentID = "" | ||||||
|  | @ -269,10 +274,8 @@ selectStatusesLoop: | ||||||
| 	account.HideCollections = &hideCollections | 	account.HideCollections = &hideCollections | ||||||
| 	discoverable := false | 	discoverable := false | ||||||
| 	account.Discoverable = &discoverable | 	account.Discoverable = &discoverable | ||||||
| 
 |  | ||||||
| 	account.SuspendedAt = time.Now() | 	account.SuspendedAt = time.Now() | ||||||
| 	account.SuspensionOrigin = origin | 	account.SuspensionOrigin = origin | ||||||
| 
 |  | ||||||
| 	err := p.db.UpdateAccount(ctx, account) | 	err := p.db.UpdateAccount(ctx, account) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return gtserror.NewErrorInternalError(err) | 		return gtserror.NewErrorInternalError(err) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue