Compare commits

...

3 Commits

Author SHA1 Message Date
Alex Gleason 5736c57f10
Purge last_known_ip when a user is deleted 2021-01-06 15:05:07 -06:00
Alex Gleason 490ca3be58
AdminAPI: return last_known_ip, fixes #1708 2021-01-06 14:59:03 -06:00
Alex Gleason 4c76e3e3b7
Optionally store user IP addresses, #1708 2021-01-06 14:53:22 -06:00
11 changed files with 172 additions and 4 deletions

View File

@ -686,6 +686,8 @@
"192.168.0.0/16"
]
config :pleroma, Pleroma.Web.Plugs.StoreUserIpPlug, enabled: false
config :pleroma, :static_fe, enabled: false
# Example of frontend configuration

View File

@ -2844,7 +2844,7 @@
%{
key: :enabled,
type: :boolean,
description: "Enable/disable the plug. Default: disabled."
description: "Enable/disable the plug. Default: enabled."
},
%{
key: :headers,
@ -2868,6 +2868,21 @@
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.Plugs.StoreUserIpPlug,
type: :group,
description: """
Stores the user's last known IP address in the database if enabled. IP addresses are shown in AdminAPI.
""",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enable/disable the plug. Default: disabled."
}
]
},
%{
group: :pleroma,
key: :web_cache_ttl,

View File

@ -0,0 +1,22 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.IpAddress do
alias Postgrex.INET
@behaviour Ecto.Type
def type, do: :inet
def cast(%INET{address: ip, netmask: nil}), do: {:ok, ip}
def cast(ip) when is_tuple(ip), do: {:ok, ip}
def load(%INET{address: ip, netmask: nil}), do: {:ok, ip}
def dump(ip) when is_tuple(ip), do: {:ok, %INET{address: ip, netmask: nil}}
def dump(_), do: :error
def equal?(a, b), do: a == b
def embed_as(_), do: :self
end

View File

@ -146,6 +146,7 @@ defmodule Pleroma.User do
field(:inbox, :string)
field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil)
field(:last_known_ip, Pleroma.EctoType.IpAddress)
embeds_one(
:notification_settings,
@ -1699,7 +1700,8 @@ def purge_user_changeset(user) do
fields: [],
raw_fields: [],
is_discoverable: false,
also_known_as: []
also_known_as: [],
last_known_ip: nil
})
end
@ -2457,4 +2459,12 @@ def sanitize_html(%User{} = user, filter) do
def get_host(%User{ap_id: ap_id} = _user) do
URI.parse(ap_id).host
end
def update_last_known_ip(%User{last_known_ip: ip} = user, ip), do: {:ok, user}
def update_last_known_ip(%User{} = user, ip) when is_tuple(ip) do
user
|> change(last_known_ip: ip)
|> update_and_set_cache()
end
end

View File

@ -80,7 +80,8 @@ def render("show.json", %{user: user}) do
"approval_pending" => user.approval_pending,
"url" => user.uri || user.ap_id,
"registration_reason" => user.registration_reason,
"actor_type" => user.actor_type
"actor_type" => user.actor_type,
"last_known_ip" => user.last_known_ip
}
end

View File

@ -0,0 +1,39 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.StoreUserIpPlug do
@moduledoc """
Stores the user's last known IP address in the database if enabled.
User IP addresses are shown in AdminAPI.
"""
alias Pleroma.Config
alias Pleroma.User
import Plug.Conn
@behaviour Plug
def init(_), do: nil
# IP address hasn't changed, so skip
def call(
%{remote_ip: ip, assigns: %{remote_ip_found: true, user: %User{last_known_ip: ip}}} =
conn,
_
),
do: conn
# Store user IP if enabled
def call(%{remote_ip: ip, assigns: %{remote_ip_found: true, user: %User{} = user}} = conn, _) do
with true <- Config.get([__MODULE__, :enabled]),
{:ok, %User{} = user} <- User.update_last_known_ip(user, ip) do
assign(conn, :user, user)
else
_ -> conn
end
end
# Fail silently
def call(conn, _), do: conn
end

View File

@ -56,6 +56,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.UserEnabledPlug)
plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
plug(Pleroma.Web.Plugs.StoreUserIpPlug)
end
pipeline :base_api do

View File

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddLastKnownIpToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add(:last_known_ip, :inet)
end
create(index(:users, [:last_known_ip]))
end
end

View File

@ -2271,4 +2271,11 @@ test "get_host/1" do
user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain")
assert User.get_host(user) == "lain.com"
end
test "update_last_known_ip/2" do
%User{id: user_id} = user = insert(:user, last_known_ip: {1, 2, 3, 4})
assert {:ok, %User{id: ^user_id, last_known_ip: {5, 4, 3, 2}}} =
User.update_last_known_ip(user, {5, 4, 3, 2})
end
end

View File

@ -963,7 +963,8 @@ defp user_response(user, attrs \\ %{}) do
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil,
"actor_type" => "Person"
"actor_type" => "Person",
"last_known_ip" => nil
}
|> Map.merge(attrs)
end

View File

@ -0,0 +1,59 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.StoreUserIpPlugTest do
use Pleroma.Web.ConnCase, async: true
use Plug.Test
alias Pleroma.User
alias Pleroma.Web.Plugs.RemoteIp
alias Pleroma.Web.Plugs.StoreUserIpPlug
import Pleroma.Factory
setup do: clear_config(StoreUserIpPlug, enabled: true)
setup do:
clear_config(RemoteIp,
enabled: true,
headers: ["x-forwarded-for"],
proxies: [],
reserved: [
"127.0.0.0/8",
"::1/128",
"fc00::/7",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
)
test "stores the user's IP address", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> put_req_header("x-forwarded-for", "1.2.3.4")
|> RemoteIp.call(nil)
|> StoreUserIpPlug.call(nil)
user = User.get_by_id(user.id)
assert user.last_known_ip == {1, 2, 3, 4}
assert %Plug.Conn{assigns: %{user: %User{last_known_ip: {1, 2, 3, 4}} = ^user}} = conn
end
test "does nothing when disabled", %{conn: conn} do
clear_config(StoreUserIpPlug, enabled: false)
user = insert(:user, last_known_ip: {1, 2, 3, 4})
conn =
conn
|> assign(:user, user)
|> put_req_header("x-forwarded-for", "5.4.3.2")
|> RemoteIp.call(nil)
|> StoreUserIpPlug.call(nil)
assert user == User.get_by_id(user.id)
assert %Plug.Conn{assigns: %{user: %User{last_known_ip: {1, 2, 3, 4}} = ^user}} = conn
end
end