FEATURE: Support anonymizing a user's IP addresses
This commit is contained in:
parent
93b40d5e59
commit
e21a4ce1dd
|
@ -2,20 +2,23 @@ class UserAnonymizer
|
||||||
|
|
||||||
attr_reader :user_history
|
attr_reader :user_history
|
||||||
|
|
||||||
def initialize(user, actor = nil)
|
# opts:
|
||||||
|
# anonymize_ip - an optional new IP to update their logs with
|
||||||
|
def initialize(user, actor = nil, opts = nil)
|
||||||
@user = user
|
@user = user
|
||||||
@actor = actor
|
@actor = actor
|
||||||
@user_history = nil
|
@user_history = nil
|
||||||
|
@opts = opts || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.make_anonymous(user, actor = nil)
|
def self.make_anonymous(user, actor = nil, opts = nil)
|
||||||
self.new(user, actor).make_anonymous
|
self.new(user, actor, opts).make_anonymous
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_anonymous
|
def make_anonymous
|
||||||
User.transaction do
|
User.transaction do
|
||||||
prev_email = @user.email
|
@prev_email = @user.email
|
||||||
prev_username = @user.username
|
@prev_username = @user.username
|
||||||
|
|
||||||
if !UsernameChanger.change(@user, make_anon_username)
|
if !UsernameChanger.change(@user, make_anon_username)
|
||||||
raise "Failed to change username"
|
raise "Failed to change username"
|
||||||
|
@ -28,6 +31,11 @@ class UserAnonymizer
|
||||||
@user.date_of_birth = nil
|
@user.date_of_birth = nil
|
||||||
@user.title = nil
|
@user.title = nil
|
||||||
@user.uploaded_avatar_id = nil
|
@user.uploaded_avatar_id = nil
|
||||||
|
|
||||||
|
if @opts.has_key?(:anonymize_ip)
|
||||||
|
anonymize_ips(@opts[:anonymize_ip])
|
||||||
|
end
|
||||||
|
|
||||||
@user.save
|
@user.save
|
||||||
|
|
||||||
options = @user.user_option
|
options = @user.user_option
|
||||||
|
@ -60,8 +68,8 @@ class UserAnonymizer
|
||||||
}
|
}
|
||||||
|
|
||||||
if SiteSetting.log_anonymizer_details?
|
if SiteSetting.log_anonymizer_details?
|
||||||
history_details[:email] = prev_email
|
history_details[:email] = @prev_email
|
||||||
history_details[:details] = "username: #{prev_username}"
|
history_details[:details] = "username: #{@prev_username}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@user_history = UserHistory.create(history_details)
|
@user_history = UserHistory.create(history_details)
|
||||||
|
@ -69,11 +77,39 @@ class UserAnonymizer
|
||||||
@user
|
@user
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_anon_username
|
private
|
||||||
100.times do
|
|
||||||
new_username = "anon#{(SecureRandom.random_number * 100000000).to_i}"
|
def make_anon_username
|
||||||
return new_username unless User.where(username_lower: new_username).exists?
|
100.times do
|
||||||
|
new_username = "anon#{(SecureRandom.random_number * 100000000).to_i}"
|
||||||
|
return new_username unless User.where(username_lower: new_username).exists?
|
||||||
|
end
|
||||||
|
raise "Failed to generate an anon username"
|
||||||
end
|
end
|
||||||
raise "Failed to generate an anon username"
|
|
||||||
|
def ip_where(column = 'user_id')
|
||||||
|
["#{column} = :user_id AND ip_address IS NOT NULL", user_id: @user.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def anonymize_ips(new_ip)
|
||||||
|
@user.ip_address = new_ip
|
||||||
|
@user.registration_ip_address = new_ip
|
||||||
|
|
||||||
|
IncomingLink.where(ip_where('current_user_id')).update_all(ip_address: new_ip)
|
||||||
|
ScreenedEmail.where(email: @prev_email).update_all(ip_address: new_ip)
|
||||||
|
SearchLog.where(ip_where).update_all(ip_address: new_ip)
|
||||||
|
TopicLinkClick.where(ip_where).update_all(ip_address: new_ip)
|
||||||
|
TopicViewItem.where(ip_where).update_all(ip_address: new_ip)
|
||||||
|
UserHistory.where(ip_where('acting_user_id')).update_all(ip_address: new_ip)
|
||||||
|
UserProfileView.where(ip_where).update_all(ip_address: new_ip)
|
||||||
|
|
||||||
|
# UserHistory for delete_user logs the user's IP. Note this is quite ugly but we don't
|
||||||
|
# have a better way of querying on details right now.
|
||||||
|
UserHistory.where(
|
||||||
|
"action = :action AND details LIKE 'id: #{@user.id}\n%'",
|
||||||
|
action: UserHistory.actions[:delete_user]
|
||||||
|
).update_all(ip_address: new_ip)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,10 +2,10 @@ require "rails_helper"
|
||||||
|
|
||||||
describe UserAnonymizer do
|
describe UserAnonymizer do
|
||||||
|
|
||||||
describe "make_anonymous" do
|
let(:admin) { Fabricate(:admin) }
|
||||||
let(:admin) { Fabricate(:admin) }
|
|
||||||
let(:user) { Fabricate(:user, username: "edward") }
|
|
||||||
|
|
||||||
|
describe "make_anonymous" do
|
||||||
|
let(:user) { Fabricate(:user, username: "edward") }
|
||||||
subject(:make_anonymous) { described_class.make_anonymous(user, admin) }
|
subject(:make_anonymous) { described_class.make_anonymous(user, admin) }
|
||||||
|
|
||||||
it "changes username" do
|
it "changes username" do
|
||||||
|
@ -157,4 +157,75 @@ describe UserAnonymizer do
|
||||||
|
|
||||||
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) }
|
||||||
|
let(: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
|
||||||
|
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)
|
||||||
|
|
||||||
|
expect("failed").to eq("success")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue