From eb7313b0d364ce6a0298d43fc86403d2e7dfc739 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 21 Oct 2020 10:23:10 +0200 Subject: [PATCH 01/15] Pipeline Ingestion: Page --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../web/activity_pub/object_validator.ex | 15 ++--- ...ator.ex => article_note_page_validator.ex} | 4 +- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- .../web/activity_pub/transmogrifier.ex | 62 +------------------ test/fixtures/tesla_mock/lemmy-page.json | 17 +++++ test/fixtures/tesla_mock/lemmy-user.json | 27 ++++++++ ...s => article_note_page_validator_test.exs} | 6 +- .../transmogrifier/page_handling_test.exs | 36 +++++++++++ 9 files changed, 96 insertions(+), 75 deletions(-) rename lib/pleroma/web/activity_pub/object_validators/{article_note_validator.ex => article_note_page_validator.ex} (96%) create mode 100644 test/fixtures/tesla_mock/lemmy-page.json create mode 100644 test/fixtures/tesla_mock/lemmy-user.json rename test/pleroma/web/activity_pub/object_validators/{article_note_validator_test.exs => article_note_page_validator_test.exs} (76%) create mode 100644 test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 18368943d..30b4f65d3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -88,7 +88,7 @@ defp increase_replies_count_if_reply(%{ defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note] + @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page] @impl true def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 248a12a36..e642916d8 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator @@ -102,7 +102,7 @@ def validate( %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Video Event Article Note] do + when objtype in ~w[Question Answer Audio Video Event Article Note Page] do with {:ok, object_data} <- cast_and_apply(object), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), {:ok, create_activity} <- @@ -115,15 +115,16 @@ def validate( end def validate(%{"type" => type} = object, meta) - when type in ~w[Event Question Audio Video Article Note] do + when type in ~w[Event Question Audio Video Article Note Page] do validator = case type do "Event" -> EventValidator "Question" -> QuestionValidator "Audio" -> AudioVideoValidator "Video" -> AudioVideoValidator - "Article" -> ArticleNoteValidator - "Note" -> ArticleNoteValidator + "Article" -> ArticleNotePageValidator + "Note" -> ArticleNotePageValidator + "Page" -> ArticleNotePageValidator end with {:ok, object} <- @@ -195,8 +196,8 @@ def cast_and_apply(%{"type" => "Event"} = object) do EventValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do - ArticleNoteValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do + ArticleNotePageValidator.cast_and_apply(object) end def cast_and_apply(o), do: {:error, {:validator_not_set, o}} diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex similarity index 96% rename from lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex rename to lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 193f85f49..0d987116c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do use Ecto.Schema alias Pleroma.EctoType.ActivityPub.ObjectValidators @@ -113,7 +113,7 @@ def changeset(struct, data) do defp validate_data(data_cng) do data_cng - |> validate_inclusion(:type, ["Article", "Note"]) + |> validate_inclusion(:type, ["Article", "Note", "Page"]) |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) |> CommonValidations.validate_any_presence([:cc, :to]) |> CommonValidations.validate_fields_match([:actor, :attributedTo]) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 674356d9a..3670de45c 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -436,7 +436,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do end def handle_object_creation(%{"type" => objtype} = object, meta) - when objtype in ~w[Audio Video Question Event Article Note] do + when objtype in ~w[Audio Video Question Event Article Note Page] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 51c0cc860..142af1a13 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -353,29 +353,6 @@ defp get_reported(objects) do end) end - # Compatibility wrapper for Mastodon votes - defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do - handle_incoming(data) - end - - defp handle_create(%{"object" => object} = data, user) do - %{ - to: data["to"], - object: object, - actor: user, - context: object["context"], - local: false, - published: data["published"], - additional: - Map.take(data, [ - "cc", - "directMessage", - "id" - ]) - } - |> ActivityPub.create() - end - def handle_incoming(data, options \\ []) # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them @@ -407,43 +384,6 @@ def handle_incoming(%{"id" => ""}, _options), do: :error def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8, do: :error - # TODO: validate those with a Ecto scheme - # - tags - # - emoji - def handle_incoming( - %{"type" => "Create", "object" => %{"type" => "Page"} = object} = data, - options - ) do - actor = Containment.get_actor(data) - - with nil <- Activity.get_create_by_object_ap_id(object["id"]), - {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do - data = - data - |> Map.put("object", fix_object(object, options)) - |> Map.put("actor", actor) - |> fix_addressing() - - with {:ok, created_activity} <- handle_create(data, user) do - reply_depth = (options[:depth] || 0) + 1 - - if Federator.allowed_thread_distance?(reply_depth) do - for reply_id <- replies(object) do - Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ - "id" => reply_id, - "depth" => reply_depth - }) - end - end - - {:ok, created_activity} - end - else - %Activity{} = activity -> {:ok, activity} - _e -> :error - end - end - def handle_incoming( %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data, options @@ -507,7 +447,7 @@ def handle_incoming( %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, options ) - when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) object = diff --git a/test/fixtures/tesla_mock/lemmy-page.json b/test/fixtures/tesla_mock/lemmy-page.json new file mode 100644 index 000000000..f07097a0e --- /dev/null +++ b/test/fixtures/tesla_mock/lemmy-page.json @@ -0,0 +1,17 @@ +{ + "commentsEnabled": true, + "sensitive": false, + "stickied": false, + "attributedTo": "https://enterprise.lemmy.ml/u/nutomic", + "summary": "Hello Federation!", + "url": "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg", + "image": { + "type": "Image", + "url": "https://enterprise.lemmy.ml/pictrs/image/lwFAcXHUjS.jpg" + }, + "published": "2020-09-14T15:03:11.909105+00:00", + "to": "https://enterprise.lemmy.ml/c/main", + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://enterprise.lemmy.ml/post/3", + "type": "Page" +} diff --git a/test/fixtures/tesla_mock/lemmy-user.json b/test/fixtures/tesla_mock/lemmy-user.json new file mode 100644 index 000000000..d0e9066ac --- /dev/null +++ b/test/fixtures/tesla_mock/lemmy-user.json @@ -0,0 +1,27 @@ +{ + "publicKey": { + "id": "https://enterprise.lemmy.ml/u/nutomic#main-key", + "owner": "https://enterprise.lemmy.ml/u/nutomic", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfwAYPxp1gOk2HcCRoUd\nupoecvmnpzRc5Gu6/N3YQyOyRsrYuiYLNQq2cgM3kcU80ZeEetkwkYgXkRJOKu/b\nBWb7i1zt2tdr5k6lUdW8dfCyjht8ooFPQdov8J3QYHfgBHyUYxuCNfSujryxx2wu\nLQcdjRQa5NIWcomSO8OXmCF5/Yhg2XWCbtnlxEq6Y+AFddr1mAlTOy5pBr5d+xZz\njLw/U3CioNJ79yGi/sJhgp6IyJqtUSoN3b4BgRIEts2QVvn44W1rQy9wCbRYQrO1\nBcB9Wel4k3rJJK8uHg+LpHVMaZppkNaWGkMBhMbzr8qmIlcNWNi7cbMK/p5vyviy\nSwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "inbox": "https://enterprise.lemmy.ml/u/nutomic/inbox", + "preferredUsername": "Nutomic", + "endpoints": { + "sharedInbox": "https://enterprise.lemmy.ml/inbox" + }, + "summary": "some bio", + "icon": { + "type": "Image", + "url": "https://enterprise.lemmy.ml/pictrs/image/F6Z7QcWZRJ.jpg" + }, + "image": { + "type": "Image", + "url": "https://enterprise.lemmy.ml:/pictrs/image/Q79N9oCDEG.png" + }, + "published": "2020-09-14T14:54:53.080949+00:00", + "updated": "2020-10-14T10:58:28.139178+00:00", + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://enterprise.lemmy.ml/u/nutomic", + "type": "Person", + "name": "nutomic" +} diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs similarity index 76% rename from test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs rename to test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index e408c85c3..720c17d8d 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest do use Pleroma.DataCase, async: true - alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator alias Pleroma.Web.ActivityPub.Utils import Pleroma.Factory @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do end test "a basic note validates", %{note: note} do - %{valid?: true} = ArticleNoteValidator.cast_and_validate(note) + %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) end end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs new file mode 100644 index 000000000..4ac71e066 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.PageHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Object.Fetcher + + test "Lemmy Page" do + Tesla.Mock.mock(fn + %{url: "https://enterprise.lemmy.ml/post/3"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/activity+json"}], + body: File.read!("test/fixtures/tesla_mock/lemmy-page.json") + } + + %{url: "https://enterprise.lemmy.ml/u/nutomic"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/activity+json"}], + body: File.read!("test/fixtures/tesla_mock/lemmy-user.json") + } + end) + + {:ok, object} = Fetcher.fetch_object_from_id("https://enterprise.lemmy.ml/post/3") + + assert object.data["summary"] == "Hello Federation!" + assert object.data["published"] == "2020-09-14T15:03:11.909105Z" + + # WAT + assert object.data["url"] == "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg" + end +end From be2da95c36c14ac42eee4009c6e3e803bafd3d2c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 29 Jun 2021 21:45:38 -0500 Subject: [PATCH 02/15] Correctly purge a remote user --- lib/pleroma/user.ex | 16 ++++++++++------ test/pleroma/user_test.exs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9942617d8..aebb5da95 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1713,6 +1713,12 @@ def purge_user_changeset(user) do }) end + def purge(%User{} = user) do + user + |> purge_user_changeset() + |> update_and_set_cache() + end + def delete(users) when is_list(users) do for user <- users, do: delete(user) end @@ -1726,9 +1732,9 @@ defp delete_and_invalidate_cache(%User{} = user) do Repo.delete(user) end - defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) + defp delete_or_purge(%User{local: false} = user), do: purge(user) - defp delete_or_deactivate(%User{local: true} = user) do + defp delete_or_purge(%User{local: true} = user) do status = account_status(user) case status do @@ -1739,9 +1745,7 @@ defp delete_or_deactivate(%User{local: true} = user) do delete_and_invalidate_cache(user) _ -> - user - |> purge_user_changeset() - |> update_and_set_cache() + purge(user) end end @@ -1769,7 +1773,7 @@ def perform(:delete, %User{} = user) do delete_outgoing_pending_follow_requests(user) - delete_or_deactivate(user) + delete_or_purge(user) end def perform(:set_activation_async, user, status), do: set_activation(user, status) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 6f5bcab57..529f837e8 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1684,6 +1684,24 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do } = user end + test "delete/1 purges a remote user" do + user = + insert(:user, %{ + name: "qqqqqqq", + avatar: %{"a" => "b"}, + banner: %{"a" => "b"}, + local: false + }) + + {:ok, job} = User.delete(user) + {:ok, _} = ObanHelpers.perform(job) + user = User.get_by_id(user.id) + + assert user.name == nil + assert user.avatar == %{} + assert user.banner == %{} + end + test "get_public_key_for_ap_id fetches a user that's not in the db" do assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end From c6d4133727ba623d4c96358e3c4de5f2194d07f8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 29 Jun 2021 22:30:48 -0500 Subject: [PATCH 03/15] Deletions: purge the user immediately --- lib/pleroma/user.ex | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index aebb5da95..406a7f5f9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1724,31 +1724,27 @@ def delete(users) when is_list(users) do end def delete(%User{} = user) do + purge(user) BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end - defp delete_and_invalidate_cache(%User{} = user) do + defp delete_from_db(%User{} = user) do invalidate_cache(user) Repo.delete(user) end - defp delete_or_purge(%User{local: false} = user), do: purge(user) - - defp delete_or_purge(%User{local: true} = user) do + defp maybe_delete_from_db(%User{local: true} = user) do status = account_status(user) - case status do - :confirmation_pending -> - delete_and_invalidate_cache(user) - - :approval_pending -> - delete_and_invalidate_cache(user) - - _ -> - purge(user) + if status in [:confirmation_pending, :approval_pending] do + delete_from_db(user) + else + {:ok, user} end end + defp maybe_delete_from_db(user), do: {:ok, user} + def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} @@ -1770,10 +1766,9 @@ def perform(:delete, %User{} = user) do delete_user_activities(user) delete_notifications_from_user_activities(user) - delete_outgoing_pending_follow_requests(user) - delete_or_purge(user) + maybe_delete_from_db(user) end def perform(:set_activation_async, user, status), do: set_activation(user, status) From 01c2d2a29670d8b3a4acee06c5f91b52e371fd00 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 29 Jun 2021 22:53:33 -0500 Subject: [PATCH 04/15] Also purge the user in User.perform/2 --- lib/pleroma/user.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 406a7f5f9..f3cf3c69b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1724,6 +1724,7 @@ def delete(users) when is_list(users) do end def delete(%User{} = user) do + # Purge the user immediately purge(user) BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end @@ -1749,6 +1750,9 @@ def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do + # Purge the user again, in case perform/2 is called directly + purge(user) + # Remove all relationships user |> get_followers() From a7929c4d89a07a7f577e7cde5638bde8b1cb586a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 29 Jun 2021 23:56:19 -0500 Subject: [PATCH 05/15] Deletions: preserve account status fields during purge, fix checks --- lib/pleroma/user.ex | 22 ++++++++++++---------- test/pleroma/user_test.exs | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f3cf3c69b..5d8b936aa 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1692,9 +1692,7 @@ def purge_user_changeset(user) do follower_count: 0, following_count: 0, is_locked: false, - is_confirmed: true, password_reset_pending: false, - is_approved: true, registration_reason: nil, confirmation_token: nil, domain_blocks: [], @@ -1710,9 +1708,15 @@ def purge_user_changeset(user) do raw_fields: [], is_discoverable: false, also_known_as: [] + # id: preserved + # ap_id: preserved + # nickname: preserved }) end + # Purge doesn't delete the user from the database. + # It just nulls all its fields and deactivates it. + # See `User.purge_user_changeset/1` above. def purge(%User{} = user) do user |> purge_user_changeset() @@ -1729,20 +1733,18 @@ def delete(%User{} = user) do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end + # *Actually* delete the user from the DB defp delete_from_db(%User{} = user) do invalidate_cache(user) Repo.delete(user) end - defp maybe_delete_from_db(%User{local: true} = user) do - status = account_status(user) + # If the user never finalized their account, it's safe to delete them. + defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user), + do: delete_from_db(user) - if status in [:confirmation_pending, :approval_pending] do - delete_from_db(user) - else - {:ok, user} - end - end + defp maybe_delete_from_db(%User{local: true, is_approved: false} = user), + do: delete_from_db(user) defp maybe_delete_from_db(user), do: {:ok, user} diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 529f837e8..60bc58a48 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1663,9 +1663,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do follower_count: 0, following_count: 0, is_locked: false, - is_confirmed: true, + is_confirmed: false, password_reset_pending: false, - is_approved: true, + is_approved: false, registration_reason: nil, confirmation_token: nil, domain_blocks: [], From 43800d83f4fc3b251cdd93c28dab2df7297021b3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 30 Jun 2021 01:14:34 -0500 Subject: [PATCH 06/15] Deletions: allow deactivated users to be deleted --- lib/pleroma/web/activity_pub/activity_pub.ex | 9 ++++++--- .../object_validators/delete_validator.ex | 12 +++++++++++- .../activity_pub/object_validators/undo_validator.ex | 12 +++++++++++- test/pleroma/user_test.exs | 8 ++++---- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5b45e2ca1..787b5884f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -52,15 +52,18 @@ defp get_recipients(data) do {recipients, to, cc} end - defp check_actor_is_active(nil), do: true + defp check_actor_can_insert(%{"type" => "Delete"}), do: true + defp check_actor_can_insert(%{"type" => "Undo"}), do: true - defp check_actor_is_active(actor) when is_binary(actor) do + defp check_actor_can_insert(%{"actor" => actor}) when is_binary(actor) do case User.get_cached_by_ap_id(actor) do %User{is_active: true} -> true _ -> false end end + defp check_actor_can_insert(_), do: true + defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do limit = Config.get([:instance, :remote_limit]) String.length(content) <= limit @@ -116,7 +119,7 @@ def persist(object, meta) do def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), - {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, + {_, true} <- {:actor_check, bypass_actor_check || check_actor_can_insert(map)}, {_, true} <- {:remote_limit_pass, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index fc1a79a72..750ea0f7f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.User import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -57,7 +58,7 @@ def validate_data(cng) do cng |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Delete"]) - |> validate_actor_presence() + |> validate_delete_actor(:actor) |> validate_modification_rights() |> validate_object_or_user_presence(allowed_types: @deletable_types) |> add_deleted_activity_id() @@ -72,4 +73,13 @@ def cast_and_validate(data) do |> cast_data |> validate_data end + + defp validate_delete_actor(cng, field_name) do + validate_change(cng, field_name, fn field_name, actor -> + case User.get_cached_by_ap_id(actor) do + %User{} -> [] + _ -> [{field_name, "can't find user"}] + end + end) + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex index 783a79ddb..ab29f9820 100644 --- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.User import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -42,7 +43,7 @@ def validate_data(data_cng) do data_cng |> validate_inclusion(:type, ["Undo"]) |> validate_required([:id, :type, :object, :actor, :to, :cc]) - |> validate_actor_presence() + |> validate_undo_actor(:actor) |> validate_object_presence() |> validate_undo_rights() end @@ -59,4 +60,13 @@ def validate_undo_rights(cng) do _ -> cng end end + + defp validate_undo_actor(cng, field_name) do + validate_change(cng, field_name, fn field_name, actor -> + case User.get_cached_by_ap_id(actor) do + %User{} -> [] + _ -> [{field_name, "can't find user"}] + end + end) + end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 60bc58a48..181990e4b 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1621,9 +1621,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do follower_count: 9, following_count: 9001, is_locked: true, - is_confirmed: false, + is_confirmed: true, password_reset_pending: true, - is_approved: false, + is_approved: true, registration_reason: "ahhhhh", confirmation_token: "qqqq", domain_blocks: ["lain.com"], @@ -1663,9 +1663,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do follower_count: 0, following_count: 0, is_locked: false, - is_confirmed: false, + is_confirmed: true, password_reset_pending: false, - is_approved: false, + is_approved: true, registration_reason: nil, confirmation_token: nil, domain_blocks: [], From beb1c98ab5e0848127a4490180364552f6fcdbf5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 30 Jun 2021 01:48:17 -0500 Subject: [PATCH 07/15] Deletions: don't purge keys so Delete/Undo activities can be signed --- lib/pleroma/user.ex | 2 -- test/pleroma/user_test.exs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 5d8b936aa..de3b8ca3b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1680,8 +1680,6 @@ def purge_user_changeset(user) do email: nil, name: nil, password_hash: nil, - keys: nil, - public_key: nil, avatar: %{}, tags: [], last_refreshed_at: nil, diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 181990e4b..ec0aaa9eb 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1651,8 +1651,8 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do email: nil, name: nil, password_hash: nil, - keys: nil, - public_key: nil, + keys: "RSA begin buplic key", + public_key: "--PRIVATE KEYE--", avatar: %{}, tags: [], last_refreshed_at: nil, From 310ef6b70d9ca18d857f43677d857d09d91ffe0e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 30 Jun 2021 12:25:20 -0500 Subject: [PATCH 08/15] Deletions: change User.purge/1 to defp, add CHANGELOG entry --- CHANGELOG.md | 2 ++ lib/pleroma/user.ex | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d92c6d2..330802b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Don't crash so hard when email settings are invalid. - Checking activated Upload Filters for required commands. +- Remote users can no longer reappear after being deleted. +- Deactivated users may now be deleted. - Mix task `pleroma.database prune_objects` ### Removed diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f5b12abad..62506f37a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1730,7 +1730,7 @@ def purge_user_changeset(user) do # Purge doesn't delete the user from the database. # It just nulls all its fields and deactivates it. # See `User.purge_user_changeset/1` above. - def purge(%User{} = user) do + defp purge(%User{} = user) do user |> purge_user_changeset() |> update_and_set_cache() From 6ef8e1776dc6b797daec923a365ae367d8279452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 2 Jul 2021 13:03:41 +0000 Subject: [PATCH 09/15] fix the fucking list timelines on mastofe/soapbox-fe --- .../web/mastodon_api/controllers/timeline_controller.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 4b49b74ca..10c279893 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -193,7 +193,9 @@ def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do |> ActivityPub.fetch_activities_bounded(following, params) |> Enum.reverse() - render(conn, "index.json", + conn + |> add_link_headers(activities) + |> render("index.json", activities: activities, for: user, as: :activity, From 64d009693e35039025b0ff1cc536206054c2b918 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 8 Jul 2021 12:33:17 -0500 Subject: [PATCH 10/15] Update Linkify to fix crash on posts with a URL we failed to parse correctly --- CHANGELOG.md | 1 + mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330802b29..9854eb531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Remote users can no longer reappear after being deleted. - Deactivated users may now be deleted. - Mix task `pleroma.database prune_objects` +- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters ### Removed - **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`) diff --git a/mix.exs b/mix.exs index e4b160971..1a7aac6a4 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,7 @@ defp deps do {:floki, "~> 0.27"}, {:timex, "~> 3.6"}, {:ueberauth, "~> 0.4"}, - {:linkify, "~> 0.5.0"}, + {:linkify, "~> 0.5.1"}, {:http_signatures, "~> 0.1.0"}, {:telemetry, "~> 0.3"}, {:poolboy, "~> 1.5"}, diff --git a/mix.lock b/mix.lock index 65a225504..b78ae0bc9 100644 --- a/mix.lock +++ b/mix.lock @@ -67,7 +67,7 @@ "jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, - "linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"}, + "linkify": {:hex, :linkify, "0.5.1", "6dc415cbc948b2f6ecec7cb226aab7ba9d3a1815bb501ae33e042334d707ecee", [:mix], [], "hexpm", "a3128c7e22fada4aa7214009501d8131e1fa3faf2f0a68b33dba379dc84ff944"}, "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, From 6dc78f5f6f8c607c90246ff30520aeb2f84634df Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 18 Sep 2020 14:22:27 +0200 Subject: [PATCH 11/15] AP C2S: Remove restrictions and make it go through pipeline --- CHANGELOG.md | 1 + lib/pleroma/activity.ex | 3 +- .../activity_pub/activity_pub_controller.ex | 104 +++++++++--------- .../web/activity_pub/object_validator.ex | 2 + .../activity_pub_controller_test.exs | 31 +++--- 5 files changed, 77 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9854eb531..036b9e775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising. - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs. - Improved Twittercard and OpenGraph meta tag generation including thumbnails and image dimension metadata when available. +- ActivityPub Client-to-Server(C2S): Limitation on the type of Activity/Object are lifted as they are now passed through ObjectValidators ### Added diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 7e36c1b53..6a991c48e 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -292,7 +292,8 @@ def get_in_reply_to_activity(%Activity{} = activity) do get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false)) end - def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) + def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id) + def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id) def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) def normalize(_), do: nil diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 5aa3b281a..57ac40b42 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Object.Fetcher alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Pipeline @@ -403,83 +402,90 @@ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ |> json(err) end - defp handle_user_activity( - %User{} = user, - %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params - ) do - content = if is_binary(object["content"]), do: object["content"], else: "" - name = if is_binary(object["name"]), do: object["name"], else: "" - summary = if is_binary(object["summary"]), do: object["summary"], else: "" - length = String.length(content <> name <> summary) + defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity) + when is_map(object) do + length = + [object["content"], object["summary"], object["name"]] + |> Enum.filter(&is_binary(&1)) + |> Enum.join("") + |> String.length() - if length > Pleroma.Config.get([:instance, :limit]) do - {:error, dgettext("errors", "Note is over the character limit")} - else + limit = Pleroma.Config.get([:instance, :limit]) + + if length < limit do object = object - |> Map.merge(Map.take(params, ["to", "cc"])) - |> Map.put("attributedTo", user.ap_id) - |> Transmogrifier.fix_object() + |> Transmogrifier.strip_internal_fields() + |> Map.put("attributedTo", actor) + |> Map.put("actor", actor) + |> Map.put("id", Utils.generate_object_id()) - ActivityPub.create(%{ - to: params["to"], - actor: user, - context: object["context"], - object: object, - additional: Map.take(params, ["cc"]) - }) - end - end - - defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do - with %Object{} = object <- Object.normalize(params["object"], fetch: false), - true <- user.is_moderator || user.ap_id == object.data["actor"], - {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), - {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do - {:ok, delete} + {:ok, Map.put(activity, "object", object)} else - _ -> {:error, dgettext("errors", "Can't delete object")} + {:error, + dgettext( + "errors", + "Character limit (%{limit} characters) exceeded, contains %{length} characters", + limit: limit, + length: length + )} end end - defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do - with %Object{} = object <- Object.normalize(params["object"], fetch: false), - {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, - {_, {:ok, %Activity{} = activity, _meta}} <- - {:common_pipeline, - Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do + defp fix_user_message( + %User{ap_id: actor} = user, + %{"type" => "Delete", "object" => object} = activity + ) do + with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)}, + {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do {:ok, activity} else - _ -> {:error, dgettext("errors", "Can't like object")} + {:normalize, _} -> + {:error, "No such object found"} + + {:permission, _} -> + {:forbidden, "You can't delete this object"} end end - defp handle_user_activity(_, _) do - {:error, dgettext("errors", "Unhandled activity type")} + defp fix_user_message(%User{}, activity) do + {:ok, activity} end def update_outbox( - %{assigns: %{user: %User{nickname: nickname} = user}} = conn, + %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn, %{"nickname" => nickname} = params ) do - actor = user.ap_id - params = params - |> Map.drop(["id"]) + |> Map.drop(["nickname"]) + |> Map.put("id", Utils.generate_activity_id()) |> Map.put("actor", actor) - |> Transmogrifier.fix_addressing() - with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do + with {:ok, params} <- fix_user_message(user, params), + {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true), + %Activity{data: activity_data} <- Activity.normalize(activity) do conn |> put_status(:created) - |> put_resp_header("location", activity.data["id"]) - |> json(activity.data) + |> put_resp_header("location", activity_data["id"]) + |> json(activity_data) else + {:forbidden, message} -> + conn + |> put_status(:forbidden) + |> json(message) + {:error, message} -> conn |> put_status(:bad_request) |> json(message) + + e -> + Logger.warn(fn -> "AP C2S: #{inspect(e)}" end) + + conn + |> put_status(:bad_request) + |> json("Bad Request") end end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 248a12a36..50999539c 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -175,6 +175,8 @@ def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do end end + def validate(o, m), do: {:error, {:validator_not_set, {o, m}}} + def cast_and_apply(%{"type" => "ChatMessage"} = object) do ChatMessageValidator.cast_and_apply(object) end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index c7039d1f8..50315e21f 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1334,9 +1334,12 @@ test "It returns poll Answers when authenticated", %{conn: conn} do activity: %{ "@context" => "https://www.w3.org/ns/activitystreams", "type" => "Create", - "object" => %{"type" => "Note", "content" => "AP C2S test"}, - "to" => "https://www.w3.org/ns/activitystreams#Public", - "cc" => [] + "object" => %{ + "type" => "Note", + "content" => "AP C2S test", + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [] + } } ] end @@ -1442,19 +1445,19 @@ test "it erects a tombstone when receiving a delete activity", %{conn: conn} do user = User.get_cached_by_ap_id(note_activity.data["actor"]) data = %{ - type: "Delete", - object: %{ - id: note_object.data["id"] + "type" => "Delete", + "object" => %{ + "id" => note_object.data["id"] } } - conn = + result = conn |> assign(:user, user) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/outbox", data) + |> json_response(201) - result = json_response(conn, 201) assert Activity.get_by_ap_id(result["id"]) assert object = Object.get_by_ap_id(note_object.data["id"]) @@ -1479,7 +1482,7 @@ test "it rejects delete activity of object from other actor", %{conn: conn} do |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/outbox", data) - assert json_response(conn, 400) + assert json_response(conn, 403) end test "it increases like count when receiving a like action", %{conn: conn} do @@ -1557,7 +1560,7 @@ test "Character limitation", %{conn: conn, activity: activity} do |> post("/users/#{user.nickname}/outbox", activity) |> json_response(400) - assert result == "Note is over the character limit" + assert result == "Character limit (5 characters) exceeded, contains 11 characters" end end @@ -1934,10 +1937,10 @@ test "POST /api/ap/upload_media", %{conn: conn} do "object" => %{ "type" => "Note", "content" => "AP C2S test, attachment", - "attachment" => [object] - }, - "to" => "https://www.w3.org/ns/activitystreams#Public", - "cc" => [] + "attachment" => [object], + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [] + } } activity_response = From 5e88796784e0ac2dbf57d9cf954fdc8ae8aeae43 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 13 Jul 2021 21:37:25 -0500 Subject: [PATCH 12/15] AdminAPI: sort user results by ID descending --- lib/pleroma/web/admin_api/search.ex | 2 +- .../controllers/user_controller_test.exs | 123 ++++++++---------- test/pleroma/web/admin_api/search_test.exs | 4 +- 3 files changed, 58 insertions(+), 71 deletions(-) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index eeeebdf4e..f7d2b7327 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -23,7 +23,7 @@ def user(params \\ %{}) do |> Map.drop([:page, :page_size]) |> Map.put(:invisible, false) |> User.Query.build() - |> order_by([u], u.nickname) + |> order_by(desc: :id) paginated_query = User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs index beb8a5d58..5ae2c9180 100644 --- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs @@ -376,24 +376,22 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do conn = get(conn, "/api/pleroma/admin/users?page=1") - users = - [ - user_response( - admin, - %{"roles" => %{"admin" => true, "moderator" => false}} - ), - user_response(user, %{"local" => false, "tags" => ["foo", "bar"]}), - user_response( - user2, - %{ - "local" => true, - "is_approved" => false, - "registration_reason" => "I'm a chill dude", - "actor_type" => "Person" - } - ) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response( + user2, + %{ + "local" => true, + "is_approved" => false, + "registration_reason" => "I'm a chill dude", + "actor_type" => "Person" + } + ), + user_response(user, %{"local" => false, "tags" => ["foo", "bar"]}), + user_response( + admin, + %{"roles" => %{"admin" => true, "moderator" => false}} + ) + ] assert json_response(conn, 200) == %{ "count" => 3, @@ -517,7 +515,7 @@ test "regular search with page size", %{conn: conn} do assert json_response(conn1, 200) == %{ "count" => 2, "page_size" => 1, - "users" => [user_response(user)] + "users" => [user_response(user2)] } conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2") @@ -525,7 +523,7 @@ test "regular search with page size", %{conn: conn} do assert json_response(conn2, 200) == %{ "count" => 2, "page_size" => 1, - "users" => [user_response(user2)] + "users" => [user_response(user)] } end @@ -557,18 +555,16 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do conn = get(conn, "/api/pleroma/admin/users?filters=local") - users = - [ - user_response(user), - user_response(admin, %{ - "roles" => %{"admin" => true, "moderator" => false} - }), - user_response(old_admin, %{ - "is_active" => true, - "roles" => %{"admin" => true, "moderator" => false} - }) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response(user), + user_response(admin, %{ + "roles" => %{"admin" => true, "moderator" => false} + }), + user_response(old_admin, %{ + "is_active" => true, + "roles" => %{"admin" => true, "moderator" => false} + }) + ] assert json_response(conn, 200) == %{ "count" => 3, @@ -596,7 +592,6 @@ test "only unconfirmed users", %{conn: conn} do "is_approved" => true }) end) - |> Enum.sort_by(& &1["nickname"]) assert result == %{"count" => 2, "page_size" => 50, "users" => users} end @@ -634,18 +629,16 @@ test "load only admins", %{conn: conn, admin: admin} do conn = get(conn, "/api/pleroma/admin/users?filters=is_admin") - users = - [ - user_response(admin, %{ - "is_active" => true, - "roles" => %{"admin" => true, "moderator" => false} - }), - user_response(second_admin, %{ - "is_active" => true, - "roles" => %{"admin" => true, "moderator" => false} - }) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response(second_admin, %{ + "is_active" => true, + "roles" => %{"admin" => true, "moderator" => false} + }), + user_response(admin, %{ + "is_active" => true, + "roles" => %{"admin" => true, "moderator" => false} + }) + ] assert json_response(conn, 200) == %{ "count" => 2, @@ -685,13 +678,11 @@ test "load users with actor_type is Person", %{admin: admin, conn: conn} do |> get(user_path(conn, :list), %{actor_types: ["Person"]}) |> json_response(200) - users = - [ - user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}), - user_response(user1), - user_response(user2) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response(user2), + user_response(user1), + user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}) + ] assert response == %{"count" => 3, "page_size" => 50, "users" => users} end @@ -708,14 +699,12 @@ test "load users with actor_type is Person and Service", %{admin: admin, conn: c |> get(user_path(conn, :list), %{actor_types: ["Person", "Service"]}) |> json_response(200) - users = - [ - user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}), - user_response(user1), - user_response(user2), - user_response(user_service, %{"actor_type" => "Service"}) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response(user2), + user_response(user1), + user_response(user_service, %{"actor_type" => "Service"}), + user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}) + ] assert response == %{"count" => 4, "page_size" => 50, "users" => users} end @@ -744,12 +733,10 @@ test "load users with tags list", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second") - users = - [ - user_response(user1, %{"tags" => ["first"]}), - user_response(user2, %{"tags" => ["second"]}) - ] - |> Enum.sort_by(& &1["nickname"]) + users = [ + user_response(user2, %{"tags" => ["second"]}), + user_response(user1, %{"tags" => ["first"]}) + ] assert json_response(conn, 200) == %{ "count" => 2, @@ -773,8 +760,8 @@ test "`active` filters out users pending approval", %{token: token} do "count" => 2, "page_size" => 50, "users" => [ - %{"id" => ^admin_id}, - %{"id" => ^user_id} + %{"id" => ^user_id}, + %{"id" => ^admin_id} ] } = json_response(conn, 200) end diff --git a/test/pleroma/web/admin_api/search_test.exs b/test/pleroma/web/admin_api/search_test.exs index b8eeec65b..2335c5228 100644 --- a/test/pleroma/web/admin_api/search_test.exs +++ b/test/pleroma/web/admin_api/search_test.exs @@ -151,9 +151,9 @@ test "it returns users by actor_types" do {:ok, [^user_service], 1} = Search.user(%{actor_types: ["Service"]}) {:ok, [^user_application], 1} = Search.user(%{actor_types: ["Application"]}) - {:ok, [^user1, ^user2], 2} = Search.user(%{actor_types: ["Person"]}) + {:ok, [^user2, ^user1], 2} = Search.user(%{actor_types: ["Person"]}) - {:ok, [^user_service, ^user1, ^user2], 3} = + {:ok, [^user2, ^user1, ^user_service], 3} = Search.user(%{actor_types: ["Person", "Service"]}) end From 5681a007d73453d941e00530cef99a78f5bb5251 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 13 Jul 2021 21:44:42 -0500 Subject: [PATCH 13/15] CHANGELOG: AdminAPI users sort --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 036b9e775..171429592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising. - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs. - Improved Twittercard and OpenGraph meta tag generation including thumbnails and image dimension metadata when available. +- AdminAPI: sort users so the newest are at the top. - ActivityPub Client-to-Server(C2S): Limitation on the type of Activity/Object are lifted as they are now passed through ObjectValidators ### Added From 167e14416bec2846d3282a121cfcfd147a04a8bf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 13 Jul 2021 23:51:32 -0500 Subject: [PATCH 14/15] AdminAPI: add date to users --- lib/pleroma/web/admin_api/views/account_view.ex | 4 +++- .../web/admin_api/controllers/user_controller_test.exs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index d7c63d385..022e97489 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do alias Pleroma.User alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.AccountView + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI alias Pleroma.Web.MediaProxy @@ -81,7 +82,8 @@ def render("show.json", %{user: user}) do "is_approved" => user.is_approved, "url" => user.uri || user.ap_id, "registration_reason" => user.registration_reason, - "actor_type" => user.actor_type + "actor_type" => user.actor_type, + "created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at) } end diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs index beb8a5d58..6c410e94c 100644 --- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs @@ -907,7 +907,8 @@ defp user_response(user, attrs \\ %{}) do "is_approved" => true, "url" => user.ap_id, "registration_reason" => nil, - "actor_type" => "Person" + "actor_type" => "Person", + "created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at) } |> Map.merge(attrs) end From 117b3edf54bef4b2d9c2e13d584da7f14923e998 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 14 Jul 2021 09:03:20 -0500 Subject: [PATCH 15/15] CHANGELOG: AdminAPI return date with users --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 036b9e775..e66412606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded. - Return OAuth token `id` (primary key) in POST `/oauth/token`. +- AdminAPI: return `created_at` date with users. - `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time. - Attachment dimensions and blurhashes are federated when available. - Pinned posts federation