Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into 1526-account-aliases

This commit is contained in:
lain 2021-01-05 12:42:30 +01:00
commit f0e6cff583
18 changed files with 246 additions and 121 deletions

View file

@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm` - **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
- Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries. - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
- Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators. - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
- Admin API: Reports now ordered by newest
### Added ### Added

View file

@ -134,6 +134,10 @@
config :pleroma, :cachex, provider: Pleroma.CachexMock config :pleroma, :cachex, provider: Pleroma.CachexMock
config :pleroma, :side_effects,
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
logger: Pleroma.LoggerMock
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View file

@ -1123,6 +1123,7 @@ Loads json generated from `config/descriptions.exs`.
```json ```json
[ [
{ {
"id": 1234,
"data": { "data": {
"actor": { "actor": {
"id": 1, "id": 1,

7
lib/pleroma/logging.ex Normal file
View file

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Logging do
@callback error(String.t()) :: any()
end

View file

@ -33,6 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
require Pleroma.Constants require Pleroma.Constants
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting @behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Streaming
defp get_recipients(%{"type" => "Create"} = data) do defp get_recipients(%{"type" => "Create"} = data) do
to = Map.get(data, "to", []) to = Map.get(data, "to", [])
@ -224,6 +225,7 @@ def stream_out_participations(participations) do
Streamer.stream("participation", participations) Streamer.stream("participation", participations)
end end
@impl true
def stream_out_participations(%Object{data: %{"context" => context}}, user) do def stream_out_participations(%Object{data: %{"context" => context}}, user) do
with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do
conversation = Repo.preload(conversation, :participations) conversation = Repo.preload(conversation, :participations)
@ -240,8 +242,10 @@ def stream_out_participations(%Object{data: %{"context" => context}}, user) do
end end
end end
@impl true
def stream_out_participations(_, _), do: :noop def stream_out_participations(_, _), do: :noop
@impl true
def stream_out(%Activity{data: %{"type" => data_type}} = activity) def stream_out(%Activity{data: %{"type" => data_type}} = activity)
when data_type in ["Create", "Announce", "Delete"] do when data_type in ["Create", "Announce", "Delete"] do
activity activity
@ -249,6 +253,7 @@ def stream_out(%Activity{data: %{"type" => data_type}} = activity)
|> Streamer.stream(activity) |> Streamer.stream(activity)
end end
@impl true
def stream_out(_activity) do def stream_out(_activity) do
:noop :noop
end end
@ -603,12 +608,18 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|> Map.put(:muting_user, reading_user) |> Map.put(:muting_user, reading_user)
end end
pagination_type =
cond do
!Map.has_key?(params, :offset) -> :keyset
true -> :offset
end
%{ %{
godmode: params[:godmode], godmode: params[:godmode],
reading_user: reading_user reading_user: reading_user
} }
|> user_activities_recipients() |> user_activities_recipients()
|> fetch_activities(params) |> fetch_activities(params, pagination_type)
|> Enum.reverse() |> Enum.reverse()
end end

View file

@ -0,0 +1,12 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
@callback stream_out(Activity.t()) :: any()
@callback stream_out_participations(Object.t(), User.t()) :: any()
end

View file

@ -28,6 +28,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
require Logger require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex) @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
@ -287,12 +289,12 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
MessageReference.delete_for_object(deleted_object) MessageReference.delete_for_object(deleted_object)
ActivityPub.stream_out(object) @ap_streamer.stream_out(object)
ActivityPub.stream_out_participations(deleted_object, user) @ap_streamer.stream_out_participations(deleted_object, user)
:ok :ok
else else
{:actor, _} -> {:actor, _} ->
Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") @logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
:no_object_actor :no_object_actor
end end

View file

@ -103,11 +103,12 @@ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickna
godmode = params["godmode"] == "true" || params["godmode"] == true godmode = params["godmode"] == "true" || params["godmode"] == true
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{_, page_size} = page_params(params) {page, page_size} = page_params(params)
activities = activities =
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, nil, %{
limit: page_size, limit: page_size,
offset: (page - 1) * page_size,
godmode: godmode, godmode: godmode,
exclude_reblogs: not with_reblogs exclude_reblogs: not with_reblogs
}) })

View file

@ -21,6 +21,7 @@ def render("show.json", %{log_entry: log_entry}) do
|> DateTime.to_unix() |> DateTime.to_unix()
%{ %{
id: log_entry.id,
data: log_entry.data, data: log_entry.data,
time: time, time: time,
message: ModerationLog.get_log_entry_message(log_entry) message: ModerationLog.get_log_entry_message(log_entry)

View file

@ -19,8 +19,7 @@ def render("index.json", %{reports: reports}) do
reports: reports:
reports[:items] reports[:items]
|> Enum.map(&Report.extract_report_info/1) |> Enum.map(&Report.extract_report_info/1)
|> Enum.map(&render(__MODULE__, "show.json", &1)) |> Enum.map(&render(__MODULE__, "show.json", &1)),
|> Enum.reverse(),
total: reports[:total] total: reports[:total]
} }
end end

View file

@ -0,0 +1,147 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.SideEffects.DeleteTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase, async: true
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.SideEffects
alias Pleroma.Web.CommonAPI
alias Pleroma.LoggerMock
alias Pleroma.Web.ActivityPub.ActivityPubMock
import Mox
import Pleroma.Factory
describe "user deletion" do
setup do
user = insert(:user)
{:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
{:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
%{
user: user,
delete_user: delete_user
}
end
test "it handles user deletions", %{delete_user: delete, user: user} do
{:ok, _delete, _} = SideEffects.handle(delete)
ObanHelpers.perform_all()
assert User.get_cached_by_ap_id(user.ap_id).deactivated
end
end
describe "object deletion" do
setup do
user = insert(:user)
other_user = insert(:user)
{:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
{:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
{:ok, favorite} = CommonAPI.favorite(user, post.id)
object = Object.normalize(post)
{:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
{:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
%{
user: user,
delete: delete,
post: post,
object: object,
op: op,
favorite: favorite
}
end
test "it handles object deletions", %{
delete: delete,
post: post,
object: object,
user: user,
op: op,
favorite: favorite
} do
object_id = object.id
user_id = user.id
ActivityPubMock
|> expect(:stream_out, fn ^delete -> nil end)
|> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} ->
nil
end)
{:ok, _delete, _} = SideEffects.handle(delete)
user = User.get_cached_by_ap_id(object.data["actor"])
object = Object.get_by_id(object.id)
assert object.data["type"] == "Tombstone"
refute Activity.get_by_id(post.id)
refute Activity.get_by_id(favorite.id)
user = User.get_by_id(user.id)
assert user.note_count == 0
object = Object.normalize(op.data["object"], false)
assert object.data["repliesCount"] == 0
end
test "it handles object deletions when the object itself has been pruned", %{
delete: delete,
post: post,
object: object,
user: user,
op: op
} do
object_id = object.id
user_id = user.id
ActivityPubMock
|> expect(:stream_out, fn ^delete -> nil end)
|> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} ->
nil
end)
{:ok, _delete, _} = SideEffects.handle(delete)
user = User.get_cached_by_ap_id(object.data["actor"])
object = Object.get_by_id(object.id)
assert object.data["type"] == "Tombstone"
refute Activity.get_by_id(post.id)
user = User.get_by_id(user.id)
assert user.note_count == 0
object = Object.normalize(op.data["object"], false)
assert object.data["repliesCount"] == 0
end
test "it logs issues with objects deletion", %{
delete: delete,
object: object
} do
{:ok, _object} =
object
|> Object.change(%{data: Map.delete(object.data, "actor")})
|> Repo.update()
LoggerMock
|> expect(:error, fn str -> assert str =~ "The object doesn't have an actor" end)
{:error, :no_object_actor} = SideEffects.handle(delete)
end
end
end

View file

@ -19,7 +19,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.ActivityPub.SideEffects
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import ExUnit.CaptureLog
import Mock import Mock
import Pleroma.Factory import Pleroma.Factory
@ -131,115 +130,6 @@ test "it uses a given changeset to update", %{user: user, update: update} do
end end
end end
describe "delete objects" do
setup do
user = insert(:user)
other_user = insert(:user)
{:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
{:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
{:ok, favorite} = CommonAPI.favorite(user, post.id)
object = Object.normalize(post)
{:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
{:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
{:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
{:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
%{
user: user,
delete: delete,
post: post,
object: object,
delete_user: delete_user,
op: op,
favorite: favorite
}
end
test "it handles object deletions", %{
delete: delete,
post: post,
object: object,
user: user,
op: op,
favorite: favorite
} do
with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
stream_out: fn _ -> nil end,
stream_out_participations: fn _, _ -> nil end do
{:ok, delete, _} = SideEffects.handle(delete)
user = User.get_cached_by_ap_id(object.data["actor"])
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
end
object = Object.get_by_id(object.id)
assert object.data["type"] == "Tombstone"
refute Activity.get_by_id(post.id)
refute Activity.get_by_id(favorite.id)
user = User.get_by_id(user.id)
assert user.note_count == 0
object = Object.normalize(op.data["object"], false)
assert object.data["repliesCount"] == 0
end
test "it handles object deletions when the object itself has been pruned", %{
delete: delete,
post: post,
object: object,
user: user,
op: op
} do
with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
stream_out: fn _ -> nil end,
stream_out_participations: fn _, _ -> nil end do
{:ok, delete, _} = SideEffects.handle(delete)
user = User.get_cached_by_ap_id(object.data["actor"])
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
end
object = Object.get_by_id(object.id)
assert object.data["type"] == "Tombstone"
refute Activity.get_by_id(post.id)
user = User.get_by_id(user.id)
assert user.note_count == 0
object = Object.normalize(op.data["object"], false)
assert object.data["repliesCount"] == 0
end
test "it handles user deletions", %{delete_user: delete, user: user} do
{:ok, _delete, _} = SideEffects.handle(delete)
ObanHelpers.perform_all()
assert User.get_cached_by_ap_id(user.ap_id).deactivated
end
test "it logs issues with objects deletion", %{
delete: delete,
object: object
} do
{:ok, object} =
object
|> Object.change(%{data: Map.delete(object.data, "actor")})
|> Repo.update()
Object.invalid_object_cache(object)
assert capture_log(fn ->
{:error, :no_object_actor} = SideEffects.handle(delete)
end) =~ "object doesn't have an actor"
end
end
describe "EmojiReact objects" do describe "EmojiReact objects" do
setup do setup do
poster = insert(:user) poster = insert(:user)

View file

@ -422,10 +422,20 @@ test "renders user's statuses", %{conn: conn, user: user} do
assert json_response(conn, 200) |> length() == 3 assert json_response(conn, 200) |> length() == 3
end end
test "renders user's statuses with a limit", %{conn: conn, user: user} do test "renders user's statuses with pagination", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2") conn1 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
assert json_response(conn, 200) |> length() == 2 response1 = json_response(conn1, 200)
assert response1 |> length() == 1
conn2 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
response2 = json_response(conn2, 200)
assert response2 |> length() == 1
refute response1 == response2
end end
test "doesn't return private statuses by default", %{conn: conn, user: user} do test "doesn't return private statuses by default", %{conn: conn, user: user} do

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
describe "renders `report_note_delete` log messages" do describe "renders `report_note_delete` log messages" do
setup do setup do
log1 = %Pleroma.ModerationLog{ log1 = %Pleroma.ModerationLog{
id: 1,
data: %{ data: %{
"action" => "report_note_delete", "action" => "report_note_delete",
"actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@ -21,6 +22,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do
} }
log2 = %Pleroma.ModerationLog{ log2 = %Pleroma.ModerationLog{
id: 2,
data: %{ data: %{
"action" => "report_note_delete", "action" => "report_note_delete",
"actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@ -42,6 +44,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do
) == %{ ) == %{
items: [ items: [
%{ %{
id: 1,
data: %{ data: %{
"action" => "report_note_delete", "action" => "report_note_delete",
"actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@ -59,6 +62,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do
time: 1_605_622_400 time: 1_605_622_400
}, },
%{ %{
id: 2,
data: %{ data: %{
"action" => "report_note_delete", "action" => "report_note_delete",
"actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},
@ -82,6 +86,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do
test "renders `report_note_delete` log message", %{log1: log} do test "renders `report_note_delete` log message", %{log1: log} do
assert ModerationLogView.render("show.json", %{log_entry: log}) == %{ assert ModerationLogView.render("show.json", %{log_entry: log}) == %{
id: 1,
data: %{ data: %{
"action" => "report_note_delete", "action" => "report_note_delete",
"actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"},

View file

@ -143,4 +143,29 @@ test "doesn't error out when the user doesn't exists" do
assert %{} = ReportView.render("show.json", Report.extract_report_info(activity)) assert %{} = ReportView.render("show.json", Report.extract_report_info(activity))
end end
test "reports are ordered newest first" do
user = insert(:user)
other_user = insert(:user)
{:ok, report1} =
CommonAPI.report(user, %{
account_id: other_user.id,
comment: "first report"
})
{:ok, report2} =
CommonAPI.report(user, %{
account_id: other_user.id,
comment: "second report"
})
%{reports: rendered} =
ReportView.render("index.json",
reports: Pleroma.Web.ActivityPub.Utils.get_reports(%{}, 1, 50)
)
assert report2.id == rendered |> Enum.at(0) |> Map.get(:id)
assert report1.id == rendered |> Enum.at(1) |> Map.get(:id)
end
end end

View file

@ -138,6 +138,8 @@ defp json_response_and_validate_schema(conn, _status) do
Pleroma.DataCase.stub_pipeline() Pleroma.DataCase.stub_pipeline()
Mox.verify_on_exit!()
{:ok, conn: Phoenix.ConnTest.build_conn()} {:ok, conn: Phoenix.ConnTest.build_conn()}
end end
end end

View file

@ -85,6 +85,8 @@ def clear_cachex do
stub_pipeline() stub_pipeline()
Mox.verify_on_exit!()
:ok :ok
end end

View file

@ -13,7 +13,10 @@
) )
Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock, Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock,
for: Pleroma.Web.ActivityPub.ActivityPub.Persisting for: [
Pleroma.Web.ActivityPub.ActivityPub.Persisting,
Pleroma.Web.ActivityPub.ActivityPub.Streaming
]
) )
Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock, Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock,
@ -23,3 +26,5 @@
Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing) Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting) Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)