Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/undo-validator-reduced
This commit is contained in:
commit
f0c22df226
157 changed files with 5707 additions and 1650 deletions
|
|
@ -820,21 +820,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
activity: activity
|
||||
} do
|
||||
user = insert(:user)
|
||||
conn = assign(conn, :user, user)
|
||||
object = Map.put(activity["object"], "sensitive", true)
|
||||
activity = Map.put(activity, "object", object)
|
||||
|
||||
result =
|
||||
response =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/outbox", activity)
|
||||
|> json_response(201)
|
||||
|
||||
assert Activity.get_by_ap_id(result["id"])
|
||||
assert result["object"]
|
||||
assert %Object{data: object} = Object.normalize(result["object"])
|
||||
assert object["sensitive"] == activity["object"]["sensitive"]
|
||||
assert object["content"] == activity["object"]["content"]
|
||||
assert Activity.get_by_ap_id(response["id"])
|
||||
assert response["object"]
|
||||
assert %Object{data: response_object} = Object.normalize(response["object"])
|
||||
assert response_object["sensitive"] == true
|
||||
assert response_object["content"] == activity["object"]["content"]
|
||||
|
||||
representation =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(response["id"])
|
||||
|> json_response(200)
|
||||
|
||||
assert representation["object"]["sensitive"] == true
|
||||
end
|
||||
|
||||
test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
|
||||
|
|
|
|||
|
|
@ -1138,143 +1138,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "deletion" do
|
||||
setup do: clear_config([:instance, :rewrite_policy])
|
||||
|
||||
test "it reverts deletion on error" do
|
||||
note = insert(:note_activity)
|
||||
object = Object.normalize(note)
|
||||
|
||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
||||
assert {:error, :reverted} = ActivityPub.delete(object)
|
||||
end
|
||||
|
||||
assert Repo.aggregate(Activity, :count, :id) == 1
|
||||
assert Repo.get(Object, object.id) == object
|
||||
assert Activity.get_by_id(note.id) == note
|
||||
end
|
||||
|
||||
test "it creates a delete activity and deletes the original object" do
|
||||
note = insert(:note_activity)
|
||||
object = Object.normalize(note)
|
||||
{:ok, delete} = ActivityPub.delete(object)
|
||||
|
||||
assert delete.data["type"] == "Delete"
|
||||
assert delete.data["actor"] == note.data["actor"]
|
||||
assert delete.data["object"] == object.data["id"]
|
||||
|
||||
assert Activity.get_by_id(delete.id) != nil
|
||||
|
||||
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
||||
end
|
||||
|
||||
test "it doesn't fail when an activity was already deleted" do
|
||||
{:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
|
||||
|
||||
assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
|
||||
end
|
||||
|
||||
test "decrements user note count only for public activities" do
|
||||
user = insert(:user, note_count: 10)
|
||||
|
||||
{:ok, a1} =
|
||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
||||
"status" => "yeah",
|
||||
"visibility" => "public"
|
||||
})
|
||||
|
||||
{:ok, a2} =
|
||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
||||
"status" => "yeah",
|
||||
"visibility" => "unlisted"
|
||||
})
|
||||
|
||||
{:ok, a3} =
|
||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
||||
"status" => "yeah",
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, a4} =
|
||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
||||
"status" => "yeah",
|
||||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
{:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
|
||||
{:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
|
||||
{:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
|
||||
{:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
|
||||
|
||||
user = User.get_cached_by_id(user.id)
|
||||
assert user.note_count == 10
|
||||
end
|
||||
|
||||
test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
|
||||
user = insert(:user)
|
||||
note = insert(:note_activity)
|
||||
object = Object.normalize(note)
|
||||
|
||||
{:ok, object} =
|
||||
object
|
||||
|> Object.change(%{
|
||||
data: %{
|
||||
"actor" => object.data["actor"],
|
||||
"id" => object.data["id"],
|
||||
"to" => [user.ap_id],
|
||||
"type" => "Note"
|
||||
}
|
||||
})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
{:ok, delete} = ActivityPub.delete(object)
|
||||
|
||||
assert user.ap_id in delete.data["to"]
|
||||
end
|
||||
|
||||
test "decreases reply count" do
|
||||
user = insert(:user)
|
||||
user2 = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
|
||||
reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
|
||||
ap_id = activity.data["id"]
|
||||
|
||||
{:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
|
||||
{:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
|
||||
{:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
|
||||
{:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
|
||||
|
||||
_ = CommonAPI.delete(direct_reply.id, user2)
|
||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||
assert object.data["repliesCount"] == 2
|
||||
|
||||
_ = CommonAPI.delete(private_reply.id, user2)
|
||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||
assert object.data["repliesCount"] == 2
|
||||
|
||||
_ = CommonAPI.delete(public_reply.id, user2)
|
||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||
assert object.data["repliesCount"] == 1
|
||||
|
||||
_ = CommonAPI.delete(unlisted_reply.id, user2)
|
||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||
assert object.data["repliesCount"] == 0
|
||||
end
|
||||
|
||||
test "it passes delete activity through MRF before deleting the object" do
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
|
||||
|
||||
note = insert(:note_activity)
|
||||
object = Object.normalize(note)
|
||||
|
||||
{:error, {:reject, _}} = ActivityPub.delete(object)
|
||||
|
||||
assert Activity.get_by_id(note.id)
|
||||
assert Repo.get(Object, object.id).data["type"] == object.data["type"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "timeline post-processing" do
|
||||
test "it filters broken threads" do
|
||||
user1 = insert(:user)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
|
|
@ -49,6 +50,99 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "deletes" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"})
|
||||
|
||||
{:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
|
||||
{:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
|
||||
|
||||
%{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
|
||||
end
|
||||
|
||||
test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
|
||||
{:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
|
||||
|
||||
assert valid_post_delete["deleted_activity_id"]
|
||||
end
|
||||
|
||||
test "it is invalid if the object isn't in a list of certain types", %{
|
||||
valid_post_delete: valid_post_delete
|
||||
} do
|
||||
object = Object.get_by_ap_id(valid_post_delete["object"])
|
||||
|
||||
data =
|
||||
object.data
|
||||
|> Map.put("type", "Like")
|
||||
|
||||
{:ok, _object} =
|
||||
object
|
||||
|> Ecto.Changeset.change(%{data: data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(valid_post_delete, [])
|
||||
assert {:object, {"object not in allowed types", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
|
||||
assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
|
||||
end
|
||||
|
||||
test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
|
||||
no_id =
|
||||
valid_post_delete
|
||||
|> Map.delete("id")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(no_id, [])
|
||||
|
||||
assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
end
|
||||
|
||||
test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
|
||||
missing_object =
|
||||
valid_post_delete
|
||||
|> Map.put("object", "http://does.not/exist")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(missing_object, [])
|
||||
|
||||
assert {:object, {"can't find object", []}} in cng.errors
|
||||
assert length(cng.errors) == 1
|
||||
end
|
||||
|
||||
test "it's invalid if the actor of the object and the actor of delete are from different domains",
|
||||
%{valid_post_delete: valid_post_delete} do
|
||||
valid_user = insert(:user)
|
||||
|
||||
valid_other_actor =
|
||||
valid_post_delete
|
||||
|> Map.put("actor", valid_user.ap_id)
|
||||
|
||||
assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
|
||||
|
||||
invalid_other_actor =
|
||||
valid_post_delete
|
||||
|> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
|
||||
|
||||
assert {:actor, {"is not allowed to delete object", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it's valid if the actor of the object is a local superuser",
|
||||
%{valid_post_delete: valid_post_delete} do
|
||||
user =
|
||||
insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
valid_other_actor =
|
||||
valid_post_delete
|
||||
|> Map.put("actor", user.ap_id)
|
||||
|
||||
{:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
|
||||
assert meta[:do_not_federate]
|
||||
end
|
||||
end
|
||||
|
||||
describe "likes" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients
|
||||
use Pleroma.DataCase
|
||||
|
||||
test "it asserts that all elements of the list are object ids" do
|
||||
list = ["https://lain.com/users/lain", "invalid"]
|
||||
|
||||
assert :error == Recipients.cast(list)
|
||||
end
|
||||
|
||||
test "it works with a list" do
|
||||
list = ["https://lain.com/users/lain"]
|
||||
assert {:ok, list} == Recipients.cast(list)
|
||||
end
|
||||
|
||||
test "it works with a list with whole objects" do
|
||||
list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}]
|
||||
resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"]
|
||||
assert {:ok, resulting_list} == Recipients.cast(list)
|
||||
end
|
||||
|
||||
test "it turns a single string into a list" do
|
||||
recipient = "https://lain.com/users/lain"
|
||||
|
||||
assert {:ok, [recipient]} == Recipients.cast(recipient)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,12 +3,14 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
|
|
@ -16,6 +18,59 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
|||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
import Mock
|
||||
|
||||
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})
|
||||
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}
|
||||
end
|
||||
|
||||
test "it handles object deletions", %{
|
||||
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
|
||||
end
|
||||
|
||||
describe "Undo objects" do
|
||||
setup do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# 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.Transmogrifier.DeleteHandlingTest do
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "it works for incoming deletes" do
|
||||
activity = insert(:note_activity)
|
||||
deleting_user = insert(:user)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("actor", deleting_user.ap_id)
|
||||
|> put_in(["object", "id"], activity.data["object"])
|
||||
|
||||
{:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
|
||||
Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert id == data["id"]
|
||||
|
||||
# We delete the Create activity because we base our timelines on it.
|
||||
# This should be changed after we unify objects and activities
|
||||
refute Activity.get_by_id(activity.id)
|
||||
assert actor == deleting_user.ap_id
|
||||
|
||||
# Objects are replaced by a tombstone object.
|
||||
object = Object.normalize(activity.data["object"])
|
||||
assert object.data["type"] == "Tombstone"
|
||||
end
|
||||
|
||||
test "it fails for incoming deletes with spoofed origin" do
|
||||
activity = insert(:note_activity)
|
||||
%{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("actor", ap_id)
|
||||
|> put_in(["object", "id"], activity.data["object"])
|
||||
|
||||
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "it works for incoming user deletes" do
|
||||
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Poison.decode!()
|
||||
|
||||
{:ok, _} = Transmogrifier.handle_incoming(data)
|
||||
ObanHelpers.perform_all()
|
||||
|
||||
assert User.get_cached_by_ap_id(ap_id).deactivated
|
||||
end
|
||||
|
||||
test "it fails for incoming user deletes with spoofed origin" do
|
||||
%{ap_id: ap_id} = insert(:user)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("actor", ap_id)
|
||||
|
||||
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
||||
|
||||
assert User.get_cached_by_ap_id(ap_id)
|
||||
end
|
||||
end
|
||||
|
|
@ -763,6 +763,60 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
assert User.get_cached_by_ap_id(ap_id)
|
||||
end
|
||||
|
||||
test "it works for incoming unannounces with an existing notice" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
|
||||
announce_data =
|
||||
File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|
||||
{:ok, %Activity{data: announce_data, local: false}} =
|
||||
Transmogrifier.handle_incoming(announce_data)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-undo-announce.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", announce_data)
|
||||
|> Map.put("actor", announce_data["actor"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["type"] == "Undo"
|
||||
assert object_data = data["object"]
|
||||
assert object_data["type"] == "Announce"
|
||||
assert object_data["object"] == activity.data["object"]
|
||||
|
||||
assert object_data["id"] ==
|
||||
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||
end
|
||||
|
||||
test "it works for incomming unfollows with an existing follow" do
|
||||
user = insert(:user)
|
||||
|
||||
follow_data =
|
||||
File.read!("test/fixtures/mastodon-follow-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", user.ap_id)
|
||||
|
||||
{:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-unfollow-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("object", follow_data)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["type"] == "Undo"
|
||||
assert data["object"]["type"] == "Follow"
|
||||
assert data["object"]["object"] == user.ap_id
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
|
||||
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
|
||||
end
|
||||
|
||||
test "it works for incoming follows to locked account" do
|
||||
pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
||||
user = insert(:user, locked: true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue