mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
Merge pull request #334 from philomena-dev/user-eraser
Add more thorough user change eraser
This commit is contained in:
commit
7e92d5a734
8 changed files with 251 additions and 0 deletions
|
@ -18,6 +18,7 @@ defmodule Philomena.Users do
|
|||
alias Philomena.Galleries
|
||||
alias Philomena.Reports
|
||||
alias Philomena.Filters
|
||||
alias Philomena.UserEraseWorker
|
||||
alias Philomena.UserRenameWorker
|
||||
|
||||
## Database getters
|
||||
|
@ -683,6 +684,20 @@ defmodule Philomena.Users do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
def erase_user(%User{} = user, %User{} = moderator) do
|
||||
# Deactivate to prevent the user from racing these changes
|
||||
{:ok, user} = deactivate_user(moderator, user)
|
||||
|
||||
# Rename to prevent usage for brand recognition SEO
|
||||
random_hex = Base.encode16(:crypto.strong_rand_bytes(16), case: :lower)
|
||||
{:ok, user} = update_user(user, %{name: "deactivated_#{random_hex}"})
|
||||
|
||||
# Enqueue a background job to perform the rest of the deletion
|
||||
Exq.enqueue(Exq, "indexing", UserEraseWorker, [user.id, moderator.id])
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp setup_roles(nil), do: nil
|
||||
|
||||
defp setup_roles(user) do
|
||||
|
|
125
lib/philomena/users/eraser.ex
Normal file
125
lib/philomena/users/eraser.ex
Normal file
|
@ -0,0 +1,125 @@
|
|||
defmodule Philomena.Users.Eraser do
|
||||
import Ecto.Query
|
||||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Bans
|
||||
alias Philomena.Comments.Comment
|
||||
alias Philomena.Comments
|
||||
alias Philomena.Galleries.Gallery
|
||||
alias Philomena.Galleries
|
||||
alias Philomena.Posts.Post
|
||||
alias Philomena.Posts
|
||||
alias Philomena.Topics.Topic
|
||||
alias Philomena.Topics
|
||||
alias Philomena.Images
|
||||
alias Philomena.SourceChanges.SourceChange
|
||||
|
||||
alias Philomena.Users
|
||||
|
||||
@reason "Site abuse"
|
||||
@wipe_ip %Postgrex.INET{address: {127, 0, 1, 1}, netmask: 32}
|
||||
@wipe_fp "ffff"
|
||||
|
||||
def erase_permanently!(user, moderator) do
|
||||
# Erase avatar
|
||||
{:ok, user} = Users.remove_avatar(user)
|
||||
|
||||
# Erase "about me" and personal title
|
||||
{:ok, user} = Users.update_description(user, %{description: "", personal_title: ""})
|
||||
|
||||
# Delete all forum posts
|
||||
Post
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn post ->
|
||||
{:ok, post} = Posts.hide_post(post, %{deletion_reason: @reason}, moderator)
|
||||
{:ok, _post} = Posts.destroy_post(post)
|
||||
end)
|
||||
|
||||
# Delete all comments
|
||||
Comment
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn comment ->
|
||||
{:ok, comment} = Comments.hide_comment(comment, %{deletion_reason: @reason}, moderator)
|
||||
{:ok, _comment} = Comments.destroy_comment(comment)
|
||||
end)
|
||||
|
||||
# Delete all galleries
|
||||
Gallery
|
||||
|> where(creator_id: ^user.id)
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn gallery ->
|
||||
{:ok, _gallery} = Galleries.delete_gallery(gallery)
|
||||
end)
|
||||
|
||||
# Delete all posted topics
|
||||
Topic
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn topic ->
|
||||
{:ok, _topic} = Topics.hide_topic(topic, @reason, moderator)
|
||||
end)
|
||||
|
||||
# Revert all source changes
|
||||
SourceChange
|
||||
|> where(user_id: ^user.id)
|
||||
|> order_by(desc: :created_at)
|
||||
|> preload(:image)
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn source_change ->
|
||||
if source_change.added do
|
||||
revert_added_source_change(source_change, user)
|
||||
else
|
||||
revert_removed_source_change(source_change, user)
|
||||
end
|
||||
end)
|
||||
|
||||
# Delete all source changes
|
||||
SourceChange
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.delete_all()
|
||||
|
||||
# Ban the user
|
||||
{:ok, _ban} =
|
||||
Bans.create_user(
|
||||
moderator,
|
||||
%{
|
||||
"user_id" => user.id,
|
||||
"reason" => @reason,
|
||||
"valid_until" => "permanent"
|
||||
}
|
||||
)
|
||||
|
||||
# We succeeded
|
||||
:ok
|
||||
end
|
||||
|
||||
defp revert_removed_source_change(source_change, user) do
|
||||
old_sources = %{}
|
||||
new_sources = %{"0" => %{"source" => source_change.source_url}}
|
||||
|
||||
revert_source_change(source_change, user, old_sources, new_sources)
|
||||
end
|
||||
|
||||
defp revert_added_source_change(source_change, user) do
|
||||
old_sources = %{"0" => %{"source" => source_change.source_url}}
|
||||
new_sources = %{}
|
||||
|
||||
revert_source_change(source_change, user, old_sources, new_sources)
|
||||
end
|
||||
|
||||
defp revert_source_change(source_change, user, old_sources, new_sources) do
|
||||
attrs = %{"old_sources" => old_sources, "sources" => new_sources}
|
||||
|
||||
attribution = [
|
||||
user: user,
|
||||
ip: @wipe_ip,
|
||||
fingerprint: @wipe_fp,
|
||||
user_agent: "",
|
||||
referrer: ""
|
||||
]
|
||||
|
||||
{:ok, _} = Images.update_sources(source_change.image, attribution, attrs)
|
||||
end
|
||||
end
|
11
lib/philomena/workers/user_erase_worker.ex
Normal file
11
lib/philomena/workers/user_erase_worker.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defmodule Philomena.UserEraseWorker do
|
||||
alias Philomena.Users.Eraser
|
||||
alias Philomena.Users
|
||||
|
||||
def perform(user_id, moderator_id) do
|
||||
moderator = Users.get_user!(moderator_id)
|
||||
user = Users.get_user!(user_id)
|
||||
|
||||
Eraser.erase_permanently!(user, moderator)
|
||||
end
|
||||
end
|
74
lib/philomena_web/controllers/admin/user/erase_controller.ex
Normal file
74
lib/philomena_web/controllers/admin/user/erase_controller.ex
Normal file
|
@ -0,0 +1,74 @@
|
|||
defmodule PhilomenaWeb.Admin.User.EraseController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Users
|
||||
|
||||
plug :verify_authorized
|
||||
|
||||
plug :load_resource,
|
||||
model: User,
|
||||
id_name: "user_id",
|
||||
id_field: "slug",
|
||||
persisted: true,
|
||||
preload: [:roles]
|
||||
|
||||
plug :prevent_deleting_privileged_users
|
||||
plug :prevent_deleting_verified_users
|
||||
plug :prevent_deleting_old_users
|
||||
|
||||
def new(conn, _params) do
|
||||
render(conn, "new.html", title: "Erase user")
|
||||
end
|
||||
|
||||
def create(conn, _params) do
|
||||
{:ok, user} = Users.erase_user(conn.assigns.user, conn.assigns.current_user)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "User erase started")
|
||||
|> redirect(to: ~p"/profiles/#{user}")
|
||||
end
|
||||
|
||||
defp verify_authorized(conn, _opts) do
|
||||
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
|
||||
true -> conn
|
||||
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp prevent_deleting_privileged_users(conn, _opts) do
|
||||
if conn.assigns.user.role != "user" do
|
||||
conn
|
||||
|> put_flash(:error, "Cannot erase a privileged user")
|
||||
|> redirect(to: ~p"/profiles/#{conn.assigns.user}")
|
||||
|> Plug.Conn.halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp prevent_deleting_verified_users(conn, _opts) do
|
||||
if conn.assigns.user.verified do
|
||||
conn
|
||||
|> put_flash(:error, "Cannot erase a verified user")
|
||||
|> redirect(to: ~p"/profiles/#{conn.assigns.user}")
|
||||
|> Plug.Conn.halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp prevent_deleting_old_users(conn, _opts) do
|
||||
now = DateTime.utc_now(:second)
|
||||
two_weeks = 1_209_600
|
||||
|
||||
if DateTime.compare(now, DateTime.add(conn.assigns.user.created_at, two_weeks)) == :gt do
|
||||
conn
|
||||
|> put_flash(:error, "Cannot erase a user older than two weeks")
|
||||
|> redirect(to: ~p"/profiles/#{conn.assigns.user}")
|
||||
|> Plug.Conn.halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
|
@ -398,6 +398,7 @@ defmodule PhilomenaWeb.Router do
|
|||
singleton: true
|
||||
|
||||
resources "/unlock", User.UnlockController, only: [:create], singleton: true
|
||||
resources "/erase", User.EraseController, only: [:new, :create], singleton: true
|
||||
resources "/api_key", User.ApiKeyController, only: [:delete], singleton: true
|
||||
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
|
||||
resources "/votes", User.VoteController, only: [:delete], singleton: true
|
||||
|
|
16
lib/philomena_web/templates/admin/user/erase/new.html.slime
Normal file
16
lib/philomena_web/templates/admin/user/erase/new.html.slime
Normal file
|
@ -0,0 +1,16 @@
|
|||
h1
|
||||
' Deleting all changes for user
|
||||
= @user.name
|
||||
|
||||
.block.block--fixed.block--warning
|
||||
p This is IRREVERSIBLE.
|
||||
p All user details will be destroyed.
|
||||
p Are you really sure?
|
||||
|
||||
.field
|
||||
=> button_to "Abort", ~p"/profiles/#{@user}", class: "button"
|
||||
=> button_to "Erase user", ~p"/admin/users/#{@user}/erase", method: "post", class: "button button--state-danger", data: [confirm: "Are you really, really sure?"]
|
||||
|
||||
p
|
||||
' This automatically creates user and IP bans but does not create a fingerprint ban.
|
||||
' Check to see if one is necessary after erasing.
|
|
@ -171,6 +171,12 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
|
|||
i.fa.fa-fw.fa-arrow-down
|
||||
span.admin__button Remove All Downvotes
|
||||
|
||||
= if @user.role == "user" do
|
||||
li
|
||||
= link to: ~p"/admin/users/#{@user}/erase/new", data: [confirm: "Are you really, really sure?"] do
|
||||
i.fa.fa-fw.fa-warning
|
||||
span.admin__button Erase for spam
|
||||
|
||||
= if @user.role == "user" and can?(@conn, :revert, Philomena.TagChanges.TagChange) do
|
||||
li
|
||||
= link to: ~p"/tag_changes/full_revert?#{[user_id: @user.id]}", data: [confirm: "Are you really, really sure?", method: "create"] do
|
||||
|
|
3
lib/philomena_web/views/admin/user/erase_view.ex
Normal file
3
lib/philomena_web/views/admin/user/erase_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule PhilomenaWeb.Admin.User.EraseView do
|
||||
use PhilomenaWeb, :view
|
||||
end
|
Loading…
Reference in a new issue