安全: 修复凭据、遥测与账号数据清理风险
This commit is contained in:
@@ -552,6 +552,104 @@ func TestDeactivatePurgesUser(t *testing.T) {
|
||||
|
||||
// TestBanUserAndCleanupRemovesRow 封禁与停用的唯一差别:封禁连用户行一起物理删除,
|
||||
// 该邮箱/域名被彻底释放。
|
||||
func TestDeactivatePurgesAllOwnedContent(t *testing.T) {
|
||||
db, _ := newTestDB(t)
|
||||
ctx := context.Background()
|
||||
usersStore := NewUsersStore(db)
|
||||
|
||||
createUser := func(email, domain string) *User {
|
||||
require.Nil(t, usersStore.Create(ctx, CreateUserOptions{
|
||||
Name: domain,
|
||||
Password: "test-only-password",
|
||||
Email: email,
|
||||
Domain: domain,
|
||||
}))
|
||||
user, err := usersStore.GetByEmail(ctx, email)
|
||||
require.Nil(t, err)
|
||||
return user
|
||||
}
|
||||
owner := createUser("[email protected]", "purge-all")
|
||||
other := createUser("[email protected]", "purge-other")
|
||||
|
||||
question, err := NewQuestionsStore(db).Create(ctx, CreateQuestionOptions{UserID: owner.ID, Content: "owned"})
|
||||
require.Nil(t, err)
|
||||
otherQuestion, err := NewQuestionsStore(db).Create(ctx, CreateQuestionOptions{UserID: other.ID, Content: "other"})
|
||||
require.Nil(t, err)
|
||||
|
||||
images := NewUploadImagesStore(db)
|
||||
ownedImage, err := images.Create(ctx, CreateUploadImageOptions{
|
||||
QuestionID: question.ID, UploaderUserID: owner.ID, Key: "owned.webp",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
sharedImage, err := images.Create(ctx, CreateUploadImageOptions{
|
||||
QuestionID: question.ID, UploaderUserID: owner.ID, Key: "shared.webp",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, db.WithContext(ctx).Create(&UploadImageQuestion{
|
||||
UploadImageID: sharedImage.ID, QuestionID: otherQuestion.ID,
|
||||
}).Error)
|
||||
orphanImage := &UploadImage{UploaderUserID: owner.ID, Key: "orphan.webp"}
|
||||
require.Nil(t, db.WithContext(ctx).Create(orphanImage).Error)
|
||||
otherImage, err := images.Create(ctx, CreateUploadImageOptions{
|
||||
QuestionID: otherQuestion.ID, UploaderUserID: other.ID, Key: "other.webp",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
conversations := NewConversationStore(db)
|
||||
conversation, message, err := conversations.CreateWithFirstMessage(ctx, CreateConversationOptions{
|
||||
OwnerID: owner.ID, AskerID: other.ID, MaxMessages: 20,
|
||||
}, other.ID, "private")
|
||||
require.Nil(t, err)
|
||||
_, err = NewMessageImageStore(db).Create(ctx, CreateMessageImageOptions{
|
||||
MessageID: message.ID, URL: "message.webp", FileName: "message.webp",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
vote, err := NewVotesStore(db).Create(ctx, CreateVoteOptions{
|
||||
UserID: owner.ID, Title: "owned vote", Type: VoteTypeForm,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
option, err := NewVoteOptionsStore(db).Create(ctx, CreateVoteOptionOptions{
|
||||
VoteID: vote.ID, Content: "option",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
_, err = NewVoteFormFieldsStore(db).Create(ctx, CreateVoteFormFieldOptions{
|
||||
VoteID: vote.ID, FieldType: VoteFormFieldTypeText, Label: "field",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
_, err = NewVoteSubmissionsStore(db).Create(ctx, CreateVoteSubmissionOptions{
|
||||
VoteID: vote.ID, OptionID: option.ID,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, NewBoxViewsStore(db).Record(ctx, RecordBoxViewOptions{
|
||||
UserID: owner.ID, ViewedOn: time.Now(), Visitor: "visitor",
|
||||
}))
|
||||
|
||||
require.Nil(t, usersStore.Deactivate(ctx, owner.ID))
|
||||
|
||||
assertCount := func(model interface{}, query string, args []interface{}, want int64) {
|
||||
t.Helper()
|
||||
var count int64
|
||||
require.Nil(t, db.WithContext(ctx).Unscoped().Model(model).Where(query, args...).Count(&count).Error)
|
||||
require.Equal(t, want, count)
|
||||
}
|
||||
assertCount(&MessageImage{}, "message_id = ?", []interface{}{message.ID}, 0)
|
||||
assertCount(&Message{}, "conversation_id = ?", []interface{}{conversation.ID}, 0)
|
||||
assertCount(&Conversation{}, "id = ?", []interface{}{conversation.ID}, 0)
|
||||
assertCount(&VoteSubmission{}, "vote_id = ?", []interface{}{vote.ID}, 0)
|
||||
assertCount(&VoteFormField{}, "vote_id = ?", []interface{}{vote.ID}, 0)
|
||||
assertCount(&VoteOption{}, "vote_id = ?", []interface{}{vote.ID}, 0)
|
||||
assertCount(&Vote{}, "id = ?", []interface{}{vote.ID}, 0)
|
||||
assertCount(&UploadImage{}, "id = ?", []interface{}{ownedImage.ID}, 0)
|
||||
assertCount(&UploadImage{}, "id = ?", []interface{}{orphanImage.ID}, 0)
|
||||
assertCount(&UploadImage{}, "id = ?", []interface{}{sharedImage.ID}, 1)
|
||||
assertCount(&UploadImage{}, "id = ?", []interface{}{otherImage.ID}, 1)
|
||||
assertCount(&UploadImageQuestion{}, "question_id = ?", []interface{}{question.ID}, 0)
|
||||
assertCount(&UploadImageQuestion{}, "upload_image_id = ? AND question_id = ?", []interface{}{sharedImage.ID, otherQuestion.ID}, 1)
|
||||
assertCount(&BoxView{}, "user_id = ?", []interface{}{owner.ID}, 0)
|
||||
assertCount(&Question{}, "id = ?", []interface{}{otherQuestion.ID}, 1)
|
||||
}
|
||||
|
||||
func TestBanUserAndCleanupRemovesRow(t *testing.T) {
|
||||
db, cleanup := newTestDB(t)
|
||||
defer func() { _ = cleanup("users", "questions", "feature_controls") }()
|
||||
|
||||
Reference in New Issue
Block a user