mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 04:43:43 +08:00
c2332d7505
* FEATURE: reduce avatar sizes to 6 from 20 This PR introduces 3 changes: 1. SiteSetting.avatar_sizes, now does what is says on the tin. previously it would introduce a large number of extra sizes, to allow for various DPIs. Instead we now trust the admin with the size list. 2. When `avatar_sizes` changes, we ensure consistency and remove resized avatars that are not longer allowed per site setting. This happens on the 12 hourly job and limited out of the box to 20k cleanups per cycle, given this may reach out to AWS 20k times to remove things. 3.Our default avatar sizes are now "24|48|72|96|144|288" these sizes were very specifically picked to limit amount of bluriness introduced by webkit. Our avatars are already blurry due to 1px border, so this corrects old blur. This change heavily reduces storage required by forums which simplifies site moves and more. Co-authored-by: David Taylor <david@taylorhq.com>
405 lines
13 KiB
Ruby
405 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe UserAnonymizer do
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
describe "event" do
|
|
let(:user) { Fabricate(:user, username: "edward") }
|
|
subject(:make_anonymous) do
|
|
described_class.make_anonymous(user, admin, anonymize_ip: "2.2.2.2")
|
|
end
|
|
|
|
it "triggers the event" do
|
|
events = DiscourseEvent.track_events { make_anonymous }
|
|
|
|
anon_event = events.detect { |e| e[:event_name] == :user_anonymized }
|
|
expect(anon_event).to be_present
|
|
params_hash = anon_event[:params][0]
|
|
|
|
expect(params_hash[:user]).to eq(user)
|
|
expect(params_hash[:opts][:anonymize_ip]).to eq("2.2.2.2")
|
|
end
|
|
end
|
|
|
|
describe ".make_anonymous" do
|
|
let(:original_email) { "edward@example.net" }
|
|
let(:user) { Fabricate(:user, username: "edward", email: original_email) }
|
|
fab!(:another_user) { Fabricate(:evil_trout) }
|
|
subject(:make_anonymous) { described_class.make_anonymous(user, admin) }
|
|
|
|
it "changes username" do
|
|
make_anonymous
|
|
expect(user.reload.username).to match(/^anon\d{3,}$/)
|
|
end
|
|
|
|
it "changes the primary email address" do
|
|
make_anonymous
|
|
expect(user.reload.email).to eq("#{user.username}@anonymized.invalid")
|
|
end
|
|
|
|
it "changes the primary email address when there is an email domain allowlist" do
|
|
SiteSetting.allowed_email_domains = "example.net|wayne.com|discourse.org"
|
|
|
|
make_anonymous
|
|
expect(user.reload.email).to eq("#{user.username}@anonymized.invalid")
|
|
end
|
|
|
|
it "deletes secondary email addresses" do
|
|
Fabricate(:secondary_email, user: user, email: "secondary_email@example.com")
|
|
|
|
make_anonymous
|
|
expect(user.reload.secondary_emails).to be_blank
|
|
end
|
|
|
|
it "turns off all notifications" do
|
|
user.user_option.update_columns(
|
|
email_level: UserOption.email_level_types[:always],
|
|
email_messages_level: UserOption.email_level_types[:always],
|
|
)
|
|
|
|
make_anonymous
|
|
user.reload
|
|
expect(user.user_option.email_digests).to eq(false)
|
|
expect(user.user_option.email_level).to eq(UserOption.email_level_types[:never])
|
|
expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:never])
|
|
expect(user.user_option.mailing_list_mode).to eq(false)
|
|
end
|
|
|
|
context "when Site Settings do not require full name" do
|
|
before { SiteSetting.full_name_required = false }
|
|
|
|
it "resets profile to default values" do
|
|
user.update!(name: "Bibi", date_of_birth: 19.years.ago, title: "Super Star")
|
|
|
|
profile = user.reload.user_profile
|
|
upload = Fabricate(:upload)
|
|
|
|
profile.update!(
|
|
location: "Moose Jaw",
|
|
website: "http://www.bim.com",
|
|
bio_raw: "I'm Bibi from Moosejaw. I sing and dance.",
|
|
bio_cooked: "I'm Bibi from Moosejaw. I sing and dance.",
|
|
profile_background_upload: upload,
|
|
bio_cooked_version: 2,
|
|
card_background_upload: upload,
|
|
)
|
|
|
|
prev_username = user.username
|
|
|
|
UserAuthToken.generate!(user_id: user.id)
|
|
|
|
make_anonymous
|
|
user.reload
|
|
|
|
expect(user.username).not_to eq(prev_username)
|
|
expect(user.name).not_to be_present
|
|
expect(user.date_of_birth).to eq(nil)
|
|
expect(user.title).not_to be_present
|
|
expect(user.user_auth_tokens.count).to eq(0)
|
|
|
|
profile = user.reload.user_profile
|
|
expect(profile.location).to eq(nil)
|
|
expect(profile.website).to eq(nil)
|
|
expect(profile.bio_cooked).to eq(nil)
|
|
expect(profile.profile_background_upload).to eq(nil)
|
|
expect(profile.bio_cooked_version).to eq(UserProfile::BAKED_VERSION)
|
|
expect(profile.card_background_upload).to eq(nil)
|
|
end
|
|
end
|
|
|
|
it "clears existing user status" do
|
|
user_status = Fabricate(:user_status, user: user)
|
|
|
|
expect do
|
|
make_anonymous
|
|
user.reload
|
|
end.to change { user.user_status }.from(user_status).to(nil)
|
|
end
|
|
|
|
context "when Site Settings require full name" do
|
|
before { SiteSetting.full_name_required = true }
|
|
|
|
it "changes name to anonymized username" do
|
|
prev_username = user.username
|
|
|
|
user.update(name: "Bibi", date_of_birth: 19.years.ago, title: "Super Star")
|
|
|
|
make_anonymous
|
|
user.reload
|
|
|
|
expect(user.name).not_to eq(prev_username)
|
|
expect(user.name).to eq(user.username)
|
|
end
|
|
end
|
|
|
|
it "removes the avatar" do
|
|
upload = Fabricate(:upload, user: user)
|
|
user.user_avatar = UserAvatar.new(user_id: user.id, custom_upload_id: upload.id)
|
|
user.uploaded_avatar_id = upload.id # chosen in user preferences
|
|
user.save!
|
|
make_anonymous
|
|
user.reload
|
|
expect(user.user_avatar).to eq(nil)
|
|
expect(user.uploaded_avatar_id).to eq(nil)
|
|
end
|
|
|
|
it "updates the avatar in posts" do
|
|
Jobs.run_immediately!
|
|
upload = Fabricate(:upload, user: user)
|
|
user.user_avatar = UserAvatar.new(user_id: user.id, custom_upload_id: upload.id)
|
|
user.uploaded_avatar_id = upload.id # chosen in user preferences
|
|
user.save!
|
|
|
|
topic = Fabricate(:topic, user: user)
|
|
quoted_post = create_post(user: user, topic: topic, post_number: 1, raw: "quoted post")
|
|
|
|
stub_image_size
|
|
post = create_post(raw: <<~RAW)
|
|
Lorem ipsum
|
|
|
|
[quote="#{quoted_post.username}, post:1, topic:#{quoted_post.topic.id}"]
|
|
quoted post
|
|
[/quote]
|
|
RAW
|
|
|
|
old_avatar_url = user.avatar_template.gsub("{size}", "48")
|
|
expect(post.cooked).to include(old_avatar_url)
|
|
|
|
make_anonymous
|
|
post.reload
|
|
new_avatar_url = user.reload.avatar_template.gsub("{size}", "48")
|
|
|
|
expect(post.cooked).to_not include(old_avatar_url)
|
|
expect(post.cooked).to include(new_avatar_url)
|
|
end
|
|
|
|
it "logs the action with the original details" do
|
|
SiteSetting.log_anonymizer_details = true
|
|
helper = UserAnonymizer.new(user, admin)
|
|
orig_email = user.email
|
|
orig_username = user.username
|
|
helper.make_anonymous
|
|
|
|
history = helper.user_history
|
|
expect(history).to be_present
|
|
expect(history.email).to eq(orig_email)
|
|
expect(history.details).to match(orig_username)
|
|
end
|
|
|
|
it "logs the action without the original details" do
|
|
SiteSetting.log_anonymizer_details = false
|
|
helper = UserAnonymizer.new(user, admin)
|
|
orig_email = user.email
|
|
orig_username = user.username
|
|
helper.make_anonymous
|
|
|
|
history = helper.user_history
|
|
expect(history).to be_present
|
|
expect(history.email).not_to eq(orig_email)
|
|
expect(history.details).not_to match(orig_username)
|
|
end
|
|
|
|
it "removes external auth associations" do
|
|
user.user_associated_accounts = [
|
|
UserAssociatedAccount.create(
|
|
user_id: user.id,
|
|
provider_uid: "example",
|
|
provider_name: "facebook",
|
|
),
|
|
]
|
|
user.single_sign_on_record =
|
|
SingleSignOnRecord.create(
|
|
user_id: user.id,
|
|
external_id: "example",
|
|
last_payload: "looks good",
|
|
)
|
|
make_anonymous
|
|
user.reload
|
|
expect(user.user_associated_accounts).to be_empty
|
|
expect(user.single_sign_on_record).to eq(nil)
|
|
end
|
|
|
|
it "removes api key" do
|
|
ApiKey.create!(user_id: user.id)
|
|
|
|
expect { make_anonymous }.to change { ApiKey.count }.by(-1)
|
|
|
|
user.reload
|
|
expect(user.api_keys).to be_empty
|
|
end
|
|
|
|
it "removes user api key" do
|
|
user_api_key = Fabricate(:user_api_key, user: user)
|
|
|
|
expect { make_anonymous }.to change { UserApiKey.count }.by(-1)
|
|
|
|
user.reload
|
|
expect(user.user_api_keys).to be_empty
|
|
end
|
|
|
|
context "when executing jobs" do
|
|
before { Jobs.run_immediately! }
|
|
|
|
it "removes invites" do
|
|
Fabricate(:invited_user, invite: Fabricate(:invite), user: user)
|
|
Fabricate(:invited_user, invite: Fabricate(:invite), user: another_user)
|
|
|
|
expect { make_anonymous }.to change { InvitedUser.count }.by(-1)
|
|
expect(InvitedUser.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
|
|
it "removes email tokens" do
|
|
Fabricate(:email_token, user: user)
|
|
Fabricate(:email_token, user: another_user)
|
|
|
|
expect { make_anonymous }.to change { EmailToken.count }.by(-1)
|
|
expect(EmailToken.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
|
|
it "removes email log entries" do
|
|
Fabricate(:email_log, user: user)
|
|
Fabricate(:email_log, user: another_user)
|
|
|
|
expect { make_anonymous }.to change { EmailLog.count }.by(-1)
|
|
expect(EmailLog.where(user_id: user.id).count).to eq(0)
|
|
end
|
|
|
|
it "removes incoming emails" do
|
|
Fabricate(:incoming_email, user: user, from_address: user.email)
|
|
Fabricate(:incoming_email, from_address: user.email, error: "Some error")
|
|
Fabricate(:incoming_email, user: another_user, from_address: another_user.email)
|
|
|
|
expect { make_anonymous }.to change { IncomingEmail.count }.by(-2)
|
|
expect(IncomingEmail.where(user_id: user.id).count).to eq(0)
|
|
expect(IncomingEmail.where(from_address: original_email).count).to eq(0)
|
|
end
|
|
|
|
it "removes raw email from posts" do
|
|
post1 = Fabricate(:post, user: user, via_email: true, raw_email: "raw email from user")
|
|
post2 =
|
|
Fabricate(
|
|
:post,
|
|
user: another_user,
|
|
via_email: true,
|
|
raw_email: "raw email from another user",
|
|
)
|
|
|
|
make_anonymous
|
|
|
|
expect(post1.reload).to have_attributes(via_email: true, raw_email: nil)
|
|
expect(post2.reload).to have_attributes(
|
|
via_email: true,
|
|
raw_email: "raw email from another user",
|
|
)
|
|
end
|
|
|
|
it "does not delete profile views" do
|
|
UserProfileView.add(user.id, "127.0.0.1", another_user.id, Time.now, true)
|
|
expect { make_anonymous }.to_not change { UserProfileView.count }
|
|
end
|
|
|
|
it "removes user field values" do
|
|
field1 = Fabricate(:user_field)
|
|
field2 = Fabricate(:user_field)
|
|
|
|
user.custom_fields = {
|
|
some_field: "123",
|
|
"user_field_#{field1.id}": "foo",
|
|
"user_field_#{field2.id}": "bar",
|
|
another_field: "456",
|
|
}
|
|
|
|
expect { make_anonymous }.to change { user.custom_fields }
|
|
expect(user.reload.custom_fields).to eq("some_field" => "123", "another_field" => "456")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "anonymize_ip" do
|
|
let(:old_ip) { "1.2.3.4" }
|
|
let(:anon_ip) { "0.0.0.0" }
|
|
let(:user) { Fabricate(:user, ip_address: old_ip, registration_ip_address: old_ip) }
|
|
fab!(:post) { Fabricate(:post) }
|
|
let(:topic) { post.topic }
|
|
|
|
it "doesn't anonymize ips by default" do
|
|
UserAnonymizer.make_anonymous(user, admin)
|
|
expect(user.ip_address).to eq(old_ip)
|
|
end
|
|
|
|
it "is called if you pass an option" do
|
|
UserAnonymizer.make_anonymous(user, admin, anonymize_ip: anon_ip)
|
|
user.reload
|
|
expect(user.ip_address).to eq(anon_ip)
|
|
end
|
|
|
|
it "exhaustively replaces all user ips" do
|
|
Jobs.run_immediately!
|
|
link = IncomingLink.create!(current_user_id: user.id, ip_address: old_ip, post_id: post.id)
|
|
|
|
screened_email = ScreenedEmail.create!(email: user.email, ip_address: old_ip)
|
|
|
|
search_log =
|
|
SearchLog.create!(
|
|
term: "wat",
|
|
search_type: SearchLog.search_types[:header],
|
|
user_id: user.id,
|
|
ip_address: old_ip,
|
|
)
|
|
|
|
topic_link =
|
|
TopicLink.create!(
|
|
user_id: admin.id,
|
|
topic_id: topic.id,
|
|
url: "https://discourse.org",
|
|
domain: "discourse.org",
|
|
)
|
|
|
|
topic_link_click =
|
|
TopicLinkClick.create!(topic_link_id: topic_link.id, user_id: user.id, ip_address: old_ip)
|
|
|
|
user_profile_view =
|
|
UserProfileView.create!(
|
|
user_id: user.id,
|
|
user_profile_id: admin.user_profile.id,
|
|
ip_address: old_ip,
|
|
viewed_at: Time.now,
|
|
)
|
|
|
|
TopicViewItem.create!(
|
|
topic_id: topic.id,
|
|
user_id: user.id,
|
|
ip_address: old_ip,
|
|
viewed_at: Time.now,
|
|
)
|
|
delete_history = StaffActionLogger.new(admin).log_user_deletion(user)
|
|
user_history = StaffActionLogger.new(user).log_backup_create
|
|
|
|
UserAnonymizer.make_anonymous(user, admin, anonymize_ip: anon_ip)
|
|
expect(user.registration_ip_address).to eq(anon_ip)
|
|
expect(link.reload.ip_address).to eq(anon_ip)
|
|
expect(screened_email.reload.ip_address).to eq(anon_ip)
|
|
expect(search_log.reload.ip_address).to eq(anon_ip)
|
|
expect(topic_link_click.reload.ip_address).to eq(anon_ip)
|
|
topic_view = TopicViewItem.where(topic_id: topic.id, user_id: user.id).first
|
|
expect(topic_view.ip_address).to eq(anon_ip)
|
|
expect(delete_history.reload.ip_address).to eq(anon_ip)
|
|
expect(user_history.reload.ip_address).to eq(anon_ip)
|
|
expect(user_profile_view.reload.ip_address).to eq(anon_ip)
|
|
end
|
|
end
|
|
|
|
describe "anonymize_emails" do
|
|
it "destroys all associated invites" do
|
|
invite = Fabricate(:invite, email: "test@example.com")
|
|
user = invite.redeem
|
|
|
|
Jobs.run_immediately!
|
|
described_class.make_anonymous(user, admin)
|
|
|
|
expect(user.email).not_to eq("test@example.com")
|
|
expect(Invite.exists?(id: invite.id)).to eq(false)
|
|
end
|
|
end
|
|
end
|