forked from AkkomaGang/akkoma
Merge branch 'follow-pipeline' into 'develop'
Handle `Follow` activities with the pipeline See merge request pleroma/pleroma!2734
This commit is contained in:
commit
68036f5a3b
31 changed files with 1006 additions and 827 deletions
|
@ -497,6 +497,10 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_i
|
|||
end
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
|
||||
[object_id]
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(activity) do
|
||||
[]
|
||||
|> Utils.maybe_notify_to_recipients(activity)
|
||||
|
|
|
@ -1543,7 +1543,7 @@ def perform(:follow_import, %User{} = follower, followed_identifiers)
|
|||
fn followed_identifier ->
|
||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
|
||||
followed
|
||||
else
|
||||
err ->
|
||||
|
|
|
@ -322,28 +322,6 @@ defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
|
||||
with {:ok, result} <-
|
||||
Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_follow(follower, followed, activity_id, local, opts) do
|
||||
skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
|
||||
data = make_follow_data(follower, followed, activity_id)
|
||||
|
||||
with {:ok, activity} <- insert(data, local),
|
||||
_ <- skip_notify_and_stream || notify_and_stream(activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, error} -> Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t()} | nil | {:error, any()}
|
||||
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||
|
|
|
@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def follow(follower, followed) do
|
||||
data = %{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"actor" => follower.ap_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed.ap_id,
|
||||
"to" => [followed.ap_id]
|
||||
}
|
||||
|
||||
{:ok, data, []}
|
||||
end
|
||||
|
||||
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
||||
def emoji_react(actor, object, emoji) do
|
||||
with {:ok, data, meta} <- object_action(actor, object) do
|
||||
|
|
|
@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
|
@ -25,6 +26,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => "Follow"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> FollowValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||
with {:ok, block_activity} <-
|
||||
block_activity
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# 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.ObjectValidators.FollowValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:state, :string, default: "pending")
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Follow"])
|
||||
|> validate_inclusion(:state, ~w{pending reject accept})
|
||||
|> validate_actor_presence()
|
||||
|> validate_actor_presence(field_name: :object)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> validate_data
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ def relay_ap_id do
|
|||
def follow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
{:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
alias Pleroma.Activity.Ir.Topics
|
||||
alias Pleroma.Chat
|
||||
alias Pleroma.Chat.MessageReference
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
@ -21,6 +22,69 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Tasks this handle
|
||||
# - Follows if possible
|
||||
# - Sends a notification
|
||||
# - Generates accept or reject if appropriate
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
"id" => follow_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed_user,
|
||||
"actor" => following_user
|
||||
}
|
||||
} = object,
|
||||
meta
|
||||
) do
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(following_user),
|
||||
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
||||
{_, {:ok, _}, _, _} <-
|
||||
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
|
||||
if followed.local && !followed.locked do
|
||||
Utils.update_follow_state_for_all(object, "accept")
|
||||
FollowingRelationship.update(follower, followed, :follow_accept)
|
||||
User.update_follower_count(followed)
|
||||
User.update_following_count(follower)
|
||||
|
||||
%{
|
||||
to: [following_user],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.accept()
|
||||
end
|
||||
else
|
||||
{:following, {:error, _}, follower, followed} ->
|
||||
Utils.update_follow_state_for_all(object, "reject")
|
||||
FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
if followed.local do
|
||||
%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.reject()
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{:ok, notifications} = Notification.create_notifications(object, do_send: false)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> add_notifications(notifications)
|
||||
|
||||
updated_object = Activity.get_by_ap_id(follow_id)
|
||||
|
||||
{:ok, updated_object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Unfollow and block
|
||||
def handle(
|
||||
|
|
|
@ -529,66 +529,6 @@ def handle_incoming(
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
|
||||
_options
|
||||
) do
|
||||
with %User{local: true} = followed <-
|
||||
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
|
||||
{:ok, %User{} = follower} <-
|
||||
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||
{_, {:ok, _}} <-
|
||||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
|
||||
{:ok, _relationship} <-
|
||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
else
|
||||
{:user_blocked, true} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:follow, {:error, _}} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:user_locked, true} ->
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
|
||||
:noop
|
||||
end
|
||||
|
||||
ActivityPub.notify_and_stream(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
|
||||
_options
|
||||
|
@ -696,7 +636,7 @@ def handle_incoming(
|
|||
%{"type" => type} = data,
|
||||
_options
|
||||
)
|
||||
when type in ~w{Update Block} do
|
||||
when type in ~w{Update Block Follow} do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
|
|
@ -101,12 +101,16 @@ def unblock(blocker, blocked) do
|
|||
def follow(follower, followed) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
||||
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
|
||||
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
|
||||
if activity.data["state"] == "reject" do
|
||||
{:error, :rejected}
|
||||
else
|
||||
{:ok, follower, followed, activity}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
|
|
|
@ -10,6 +10,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
|
|||
alias Pleroma.Web.ActivityPub.Utils
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
|
||||
|
@ -46,7 +48,8 @@ test "relay is followed" do
|
|||
|
||||
describe "running unfollow" do
|
||||
test "relay is unfollowed" do
|
||||
target_instance = "http://mastodon.example.org/users/admin"
|
||||
user = insert(:user)
|
||||
target_instance = user.ap_id
|
||||
|
||||
Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
|
||||
|
||||
|
@ -71,7 +74,7 @@ test "relay is unfollowed" do
|
|||
|
||||
assert undo_activity.data["type"] == "Undo"
|
||||
assert undo_activity.data["actor"] == local_user.ap_id
|
||||
assert undo_activity.data["object"] == cancelled_activity.data
|
||||
assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
|
||||
refute "#{target_instance}/followers" in User.following(local_user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -669,7 +669,7 @@ test "doesn't return activities from blocked domains" do
|
|||
refute activity in activities
|
||||
|
||||
followed_user = insert(:user)
|
||||
ActivityPub.follow(user, followed_user)
|
||||
CommonAPI.follow(user, followed_user)
|
||||
{:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
|
||||
|
@ -1013,24 +1013,12 @@ test "fetches the latest Follow activity" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "following / unfollowing" do
|
||||
test "it reverts follow activity" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
||||
assert {:error, :reverted} = ActivityPub.follow(follower, followed)
|
||||
end
|
||||
|
||||
assert Repo.aggregate(Activity, :count, :id) == 0
|
||||
assert Repo.aggregate(Object, :count, :id) == 0
|
||||
end
|
||||
|
||||
describe "unfollowing" do
|
||||
test "it reverts unfollow activity" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
||||
assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
|
||||
|
@ -1043,21 +1031,11 @@ test "it reverts unfollow activity" do
|
|||
assert activity.data["object"] == followed.ap_id
|
||||
end
|
||||
|
||||
test "creates a follow activity" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(follower, followed)
|
||||
assert activity.data["type"] == "Follow"
|
||||
assert activity.data["actor"] == follower.ap_id
|
||||
assert activity.data["object"] == followed.ap_id
|
||||
end
|
||||
|
||||
test "creates an undo activity for the last follow" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
{:ok, activity} = ActivityPub.unfollow(follower, followed)
|
||||
|
||||
assert activity.data["type"] == "Undo"
|
||||
|
@ -1074,7 +1052,7 @@ test "creates an undo activity for a pending follow request" do
|
|||
follower = insert(:user)
|
||||
followed = insert(:user, %{locked: true})
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
{:ok, activity} = ActivityPub.unfollow(follower, followed)
|
||||
|
||||
assert activity.data["type"] == "Undo"
|
||||
|
|
|
@ -1,684 +0,0 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "attachments" do
|
||||
test "works with honkerific attachments" do
|
||||
attachment = %{
|
||||
"mediaType" => "",
|
||||
"name" => "",
|
||||
"summary" => "298p3RG7j27tfsZ9RQ.jpg",
|
||||
"type" => "Document",
|
||||
"url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
|
||||
}
|
||||
|
||||
assert {:ok, attachment} =
|
||||
AttachmentValidator.cast_and_validate(attachment)
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert attachment.mediaType == "application/octet-stream"
|
||||
end
|
||||
|
||||
test "it turns mastodon attachments into our attachments" do
|
||||
attachment = %{
|
||||
"url" =>
|
||||
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
"type" => "Document",
|
||||
"name" => nil,
|
||||
"mediaType" => "image/jpeg"
|
||||
}
|
||||
|
||||
{:ok, attachment} =
|
||||
AttachmentValidator.cast_and_validate(attachment)
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert [
|
||||
%{
|
||||
href:
|
||||
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
type: "Link",
|
||||
mediaType: "image/jpeg"
|
||||
}
|
||||
] = attachment.url
|
||||
|
||||
assert attachment.mediaType == "image/jpeg"
|
||||
end
|
||||
|
||||
test "it handles our own uploads" do
|
||||
user = insert(:user)
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
{:ok, attachment} =
|
||||
attachment.data
|
||||
|> AttachmentValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert attachment.mediaType == "image/jpeg"
|
||||
end
|
||||
end
|
||||
|
||||
describe "chat message create activities" do
|
||||
test "it is invalid if the object already exists" do
|
||||
user = insert(:user)
|
||||
recipient = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
|
||||
object = Object.normalize(activity, false)
|
||||
|
||||
{:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
||||
|
||||
assert {:object, {"The object to create already exists", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is invalid if the object data has a different `to` or `actor` field" do
|
||||
user = insert(:user)
|
||||
recipient = insert(:user)
|
||||
{:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
|
||||
|
||||
{:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
||||
|
||||
assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
|
||||
assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
|
||||
describe "chat messages" do
|
||||
setup do
|
||||
clear_config([:instance, :remote_limit])
|
||||
user = insert(:user)
|
||||
recipient = insert(:user, local: false)
|
||||
|
||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
|
||||
|
||||
%{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
|
||||
end
|
||||
|
||||
test "let's through some basic html", %{user: user, recipient: recipient} do
|
||||
{:ok, valid_chat_message, _} =
|
||||
Builder.chat_message(
|
||||
user,
|
||||
recipient.ap_id,
|
||||
"hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
|
||||
)
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["content"] ==
|
||||
"hey <a href=\"https://example.org\">example</a> alert('uguu')"
|
||||
end
|
||||
|
||||
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert Map.put(valid_chat_message, "attachment", nil) == object
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", attachment.data)
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment in an array", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", [attachment.data])
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment but without content", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", attachment.data)
|
||||
|> Map.delete("content")
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "does not validate if the message has no content", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
contentless =
|
||||
valid_chat_message
|
||||
|> Map.delete("content")
|
||||
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
|
||||
end
|
||||
|
||||
test "does not validate if the message is longer than the remote_limit", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
Pleroma.Config.put([:instance, :remote_limit], 2)
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
||||
end
|
||||
|
||||
test "does not validate if the recipient is blocking the actor", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user,
|
||||
recipient: recipient
|
||||
} do
|
||||
Pleroma.User.block(recipient, user)
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
||||
end
|
||||
|
||||
test "does not validate if the actor or the recipient is not in our system", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("actor", "https://raymoo.com/raymoo")
|
||||
|
||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("to", ["https://raymoo.com/raymoo"])
|
||||
|
||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
end
|
||||
|
||||
test "does not validate for a message with multiple recipients", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user,
|
||||
recipient: recipient
|
||||
} do
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("to", [user.ap_id, recipient.ap_id])
|
||||
|
||||
assert {:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
end
|
||||
|
||||
test "does not validate if it doesn't concern local users" do
|
||||
user = insert(:user, local: false)
|
||||
recipient = insert(:user, local: false)
|
||||
|
||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
|
||||
assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
|
||||
end
|
||||
end
|
||||
|
||||
describe "EmojiReacts" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
|
||||
|
||||
{:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
|
||||
|
||||
%{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
|
||||
end
|
||||
|
||||
test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
|
||||
assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
|
||||
end
|
||||
|
||||
test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
|
||||
without_content =
|
||||
valid_emoji_react
|
||||
|> Map.delete("content")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", "x")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
|
||||
assert {:content, {"must be a single character emoji", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
|
||||
describe "Undos" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
{:ok, like} = CommonAPI.favorite(user, post_activity.id)
|
||||
{:ok, valid_like_undo, []} = Builder.undo(user, like)
|
||||
|
||||
%{user: user, like: like, valid_like_undo: valid_like_undo}
|
||||
end
|
||||
|
||||
test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
|
||||
assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
|
||||
end
|
||||
|
||||
test "it does not validate if the actor of the undo is not the actor of the object", %{
|
||||
valid_like_undo: valid_like_undo
|
||||
} do
|
||||
other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
bad_actor =
|
||||
valid_like_undo
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(bad_actor, [])
|
||||
|
||||
assert {:actor, {"not the same as object actor", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
|
||||
missing_object =
|
||||
valid_like_undo
|
||||
|> Map.put("object", "https://gensokyo.2hu/objects/1")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(missing_object, [])
|
||||
|
||||
assert {:object, {"can't find object", []}} in cng.errors
|
||||
assert length(cng.errors) == 1
|
||||
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
|
||||
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)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
valid_like = %{
|
||||
"to" => [user.ap_id],
|
||||
"cc" => [],
|
||||
"type" => "Like",
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"object" => post_activity.data["object"],
|
||||
"actor" => user.ap_id,
|
||||
"context" => "a context"
|
||||
}
|
||||
|
||||
%{valid_like: valid_like, user: user, post_activity: post_activity}
|
||||
end
|
||||
|
||||
test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
|
||||
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||
|
||||
assert "id" in Map.keys(object)
|
||||
end
|
||||
|
||||
test "is valid for a valid object", %{valid_like: valid_like} do
|
||||
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "sets the 'to' field to the object actor if no recipients are given", %{
|
||||
valid_like: valid_like,
|
||||
user: user
|
||||
} do
|
||||
without_recipients =
|
||||
valid_like
|
||||
|> Map.delete("to")
|
||||
|
||||
{:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
|
||||
|
||||
assert object["to"] == [user.ap_id]
|
||||
end
|
||||
|
||||
test "sets the context field to the context of the object if no context is given", %{
|
||||
valid_like: valid_like,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
without_context =
|
||||
valid_like
|
||||
|> Map.delete("context")
|
||||
|
||||
{:ok, object, _meta} = ObjectValidator.validate(without_context, [])
|
||||
|
||||
assert object["context"] == post_activity.data["context"]
|
||||
end
|
||||
|
||||
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
||||
without_actor = Map.delete(valid_like, "actor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_actor).valid?
|
||||
|
||||
with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
|
||||
end
|
||||
|
||||
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
||||
without_object = Map.delete(valid_like, "object")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_object).valid?
|
||||
|
||||
with_invalid_object = Map.put(valid_like, "object", "invalidobject")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_object).valid?
|
||||
end
|
||||
|
||||
test "it errors when the actor has already like the object", %{
|
||||
valid_like: valid_like,
|
||||
user: user,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
_like = CommonAPI.favorite(user, post_activity.id)
|
||||
|
||||
refute LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
|
||||
wrapped_like =
|
||||
valid_like
|
||||
|> Map.put("actor", %{"id" => valid_like["actor"]})
|
||||
|> Map.put("object", %{"id" => valid_like["object"]})
|
||||
|
||||
validated = LikeValidator.cast_and_validate(wrapped_like)
|
||||
|
||||
assert validated.valid?
|
||||
|
||||
assert {:actor, valid_like["actor"]} in validated.changes
|
||||
assert {:object, valid_like["object"]} in validated.changes
|
||||
end
|
||||
end
|
||||
|
||||
describe "announces" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
announcer = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
object = Object.normalize(post_activity, false)
|
||||
{:ok, valid_announce, []} = Builder.announce(announcer, object)
|
||||
|
||||
%{
|
||||
valid_announce: valid_announce,
|
||||
user: user,
|
||||
post_activity: post_activity,
|
||||
announcer: announcer
|
||||
}
|
||||
end
|
||||
|
||||
test "returns ok for a valid announce", %{valid_announce: valid_announce} do
|
||||
assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
|
||||
end
|
||||
|
||||
test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
|
||||
without_object =
|
||||
valid_announce
|
||||
|> Map.delete("object")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_object, [])
|
||||
|
||||
assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
|
||||
nonexisting_object =
|
||||
valid_announce
|
||||
|> Map.put("object", "https://gensokyo.2hu/objects/99999999")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(nonexisting_object, [])
|
||||
|
||||
assert {:object, {"can't find object", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
|
||||
nonexisting_actor =
|
||||
valid_announce
|
||||
|> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
|
||||
|
||||
assert {:actor, {"can't find user", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if the actor already announced the object", %{
|
||||
valid_announce: valid_announce,
|
||||
announcer: announcer,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
_announce = CommonAPI.repeat(post_activity.id, announcer)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(valid_announce, [])
|
||||
|
||||
assert {:actor, {"already announced this object", []}} in cng.errors
|
||||
assert {:object, {"already announced by this actor", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if the actor can't announce the object", %{
|
||||
announcer: announcer,
|
||||
user: user
|
||||
} do
|
||||
{:ok, post_activity} =
|
||||
CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
|
||||
|
||||
object = Object.normalize(post_activity, false)
|
||||
|
||||
# Another user can't announce it
|
||||
{:ok, announce, []} = Builder.announce(announcer, object, public: false)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(announce, [])
|
||||
|
||||
assert {:actor, {"can not announce this object", []}} in cng.errors
|
||||
|
||||
# The actor of the object can announce it
|
||||
{:ok, announce, []} = Builder.announce(user, object, public: false)
|
||||
|
||||
assert {:ok, _, _} = ObjectValidator.validate(announce, [])
|
||||
|
||||
# The actor of the object can not announce it publicly
|
||||
{:ok, announce, []} = Builder.announce(user, object, public: true)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(announce, [])
|
||||
|
||||
assert {:actor, {"can not announce this object publicly", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
|
||||
describe "updates" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
||||
object = %{
|
||||
"id" => user.ap_id,
|
||||
"name" => "A new name",
|
||||
"summary" => "A new bio"
|
||||
}
|
||||
|
||||
{:ok, valid_update, []} = Builder.update(user, object)
|
||||
|
||||
%{user: user, valid_update: valid_update}
|
||||
end
|
||||
|
||||
test "validates a basic object", %{valid_update: valid_update} do
|
||||
assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
|
||||
end
|
||||
|
||||
test "returns an error if the object can't be updated by the actor", %{
|
||||
valid_update: valid_update
|
||||
} do
|
||||
other_user = insert(:user)
|
||||
|
||||
update =
|
||||
valid_update
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|
||||
assert {:error, _cng} = ObjectValidator.validate(update, [])
|
||||
end
|
||||
end
|
||||
|
||||
describe "blocks" do
|
||||
setup do
|
||||
user = insert(:user, local: false)
|
||||
blocked = insert(:user)
|
||||
|
||||
{:ok, valid_block, []} = Builder.block(user, blocked)
|
||||
|
||||
%{user: user, valid_block: valid_block}
|
||||
end
|
||||
|
||||
test "validates a basic object", %{
|
||||
valid_block: valid_block
|
||||
} do
|
||||
assert {:ok, _block, []} = ObjectValidator.validate(valid_block, [])
|
||||
end
|
||||
|
||||
test "returns an error if we don't know the blocked user", %{
|
||||
valid_block: valid_block
|
||||
} do
|
||||
block =
|
||||
valid_block
|
||||
|> Map.put("object", "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
assert {:error, _cng} = ObjectValidator.validate(block, [])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,106 @@
|
|||
# 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.ObjectValidators.AnnouncValidationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "announces" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
announcer = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
object = Object.normalize(post_activity, false)
|
||||
{:ok, valid_announce, []} = Builder.announce(announcer, object)
|
||||
|
||||
%{
|
||||
valid_announce: valid_announce,
|
||||
user: user,
|
||||
post_activity: post_activity,
|
||||
announcer: announcer
|
||||
}
|
||||
end
|
||||
|
||||
test "returns ok for a valid announce", %{valid_announce: valid_announce} do
|
||||
assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
|
||||
end
|
||||
|
||||
test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
|
||||
without_object =
|
||||
valid_announce
|
||||
|> Map.delete("object")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_object, [])
|
||||
|
||||
assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
|
||||
nonexisting_object =
|
||||
valid_announce
|
||||
|> Map.put("object", "https://gensokyo.2hu/objects/99999999")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(nonexisting_object, [])
|
||||
|
||||
assert {:object, {"can't find object", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
|
||||
nonexisting_actor =
|
||||
valid_announce
|
||||
|> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
|
||||
|
||||
assert {:actor, {"can't find user", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if the actor already announced the object", %{
|
||||
valid_announce: valid_announce,
|
||||
announcer: announcer,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
_announce = CommonAPI.repeat(post_activity.id, announcer)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(valid_announce, [])
|
||||
|
||||
assert {:actor, {"already announced this object", []}} in cng.errors
|
||||
assert {:object, {"already announced by this actor", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "returns an error if the actor can't announce the object", %{
|
||||
announcer: announcer,
|
||||
user: user
|
||||
} do
|
||||
{:ok, post_activity} =
|
||||
CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
|
||||
|
||||
object = Object.normalize(post_activity, false)
|
||||
|
||||
# Another user can't announce it
|
||||
{:ok, announce, []} = Builder.announce(announcer, object, public: false)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(announce, [])
|
||||
|
||||
assert {:actor, {"can not announce this object", []}} in cng.errors
|
||||
|
||||
# The actor of the object can announce it
|
||||
{:ok, announce, []} = Builder.announce(user, object, public: false)
|
||||
|
||||
assert {:ok, _, _} = ObjectValidator.validate(announce, [])
|
||||
|
||||
# The actor of the object can not announce it publicly
|
||||
{:ok, announce, []} = Builder.announce(user, object, public: true)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(announce, [])
|
||||
|
||||
assert {:actor, {"can not announce this object publicly", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
# 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.ObjectValidators.AttachmentValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "attachments" do
|
||||
test "works with honkerific attachments" do
|
||||
attachment = %{
|
||||
"mediaType" => "",
|
||||
"name" => "",
|
||||
"summary" => "298p3RG7j27tfsZ9RQ.jpg",
|
||||
"type" => "Document",
|
||||
"url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
|
||||
}
|
||||
|
||||
assert {:ok, attachment} =
|
||||
AttachmentValidator.cast_and_validate(attachment)
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert attachment.mediaType == "application/octet-stream"
|
||||
end
|
||||
|
||||
test "it turns mastodon attachments into our attachments" do
|
||||
attachment = %{
|
||||
"url" =>
|
||||
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
"type" => "Document",
|
||||
"name" => nil,
|
||||
"mediaType" => "image/jpeg"
|
||||
}
|
||||
|
||||
{:ok, attachment} =
|
||||
AttachmentValidator.cast_and_validate(attachment)
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert [
|
||||
%{
|
||||
href:
|
||||
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
|
||||
type: "Link",
|
||||
mediaType: "image/jpeg"
|
||||
}
|
||||
] = attachment.url
|
||||
|
||||
assert attachment.mediaType == "image/jpeg"
|
||||
end
|
||||
|
||||
test "it handles our own uploads" do
|
||||
user = insert(:user)
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
{:ok, attachment} =
|
||||
attachment.data
|
||||
|> AttachmentValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert)
|
||||
|
||||
assert attachment.mediaType == "image/jpeg"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "blocks" do
|
||||
setup do
|
||||
user = insert(:user, local: false)
|
||||
blocked = insert(:user)
|
||||
|
||||
{:ok, valid_block, []} = Builder.block(user, blocked)
|
||||
|
||||
%{user: user, valid_block: valid_block}
|
||||
end
|
||||
|
||||
test "validates a basic object", %{
|
||||
valid_block: valid_block
|
||||
} do
|
||||
assert {:ok, _block, []} = ObjectValidator.validate(valid_block, [])
|
||||
end
|
||||
|
||||
test "returns an error if we don't know the blocked user", %{
|
||||
valid_block: valid_block
|
||||
} do
|
||||
block =
|
||||
valid_block
|
||||
|> Map.put("object", "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
assert {:error, _cng} = ObjectValidator.validate(block, [])
|
||||
end
|
||||
end
|
||||
end
|
200
test/web/activity_pub/object_validators/chat_validation_test.exs
Normal file
200
test/web/activity_pub/object_validators/chat_validation_test.exs
Normal file
|
@ -0,0 +1,200 @@
|
|||
# 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.ObjectValidators.ChatValidationTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "chat message create activities" do
|
||||
test "it is invalid if the object already exists" do
|
||||
user = insert(:user)
|
||||
recipient = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
|
||||
object = Object.normalize(activity, false)
|
||||
|
||||
{:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
||||
|
||||
assert {:object, {"The object to create already exists", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is invalid if the object data has a different `to` or `actor` field" do
|
||||
user = insert(:user)
|
||||
recipient = insert(:user)
|
||||
{:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
|
||||
|
||||
{:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
||||
|
||||
assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
|
||||
assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
|
||||
describe "chat messages" do
|
||||
setup do
|
||||
clear_config([:instance, :remote_limit])
|
||||
user = insert(:user)
|
||||
recipient = insert(:user, local: false)
|
||||
|
||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
|
||||
|
||||
%{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
|
||||
end
|
||||
|
||||
test "let's through some basic html", %{user: user, recipient: recipient} do
|
||||
{:ok, valid_chat_message, _} =
|
||||
Builder.chat_message(
|
||||
user,
|
||||
recipient.ap_id,
|
||||
"hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
|
||||
)
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["content"] ==
|
||||
"hey <a href=\"https://example.org\">example</a> alert('uguu')"
|
||||
end
|
||||
|
||||
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert Map.put(valid_chat_message, "attachment", nil) == object
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", attachment.data)
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment in an array", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", [attachment.data])
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "validates for a basic object with an attachment but without content", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user
|
||||
} do
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
||||
|
||||
valid_chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("attachment", attachment.data)
|
||||
|> Map.delete("content")
|
||||
|
||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
||||
|
||||
assert object["attachment"]
|
||||
end
|
||||
|
||||
test "does not validate if the message has no content", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
contentless =
|
||||
valid_chat_message
|
||||
|> Map.delete("content")
|
||||
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
|
||||
end
|
||||
|
||||
test "does not validate if the message is longer than the remote_limit", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
Pleroma.Config.put([:instance, :remote_limit], 2)
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
||||
end
|
||||
|
||||
test "does not validate if the recipient is blocking the actor", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user,
|
||||
recipient: recipient
|
||||
} do
|
||||
Pleroma.User.block(recipient, user)
|
||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
||||
end
|
||||
|
||||
test "does not validate if the actor or the recipient is not in our system", %{
|
||||
valid_chat_message: valid_chat_message
|
||||
} do
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("actor", "https://raymoo.com/raymoo")
|
||||
|
||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("to", ["https://raymoo.com/raymoo"])
|
||||
|
||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
end
|
||||
|
||||
test "does not validate for a message with multiple recipients", %{
|
||||
valid_chat_message: valid_chat_message,
|
||||
user: user,
|
||||
recipient: recipient
|
||||
} do
|
||||
chat_message =
|
||||
valid_chat_message
|
||||
|> Map.put("to", [user.ap_id, recipient.ap_id])
|
||||
|
||||
assert {:error, _} = ObjectValidator.validate(chat_message, [])
|
||||
end
|
||||
|
||||
test "does not validate if it doesn't concern local users" do
|
||||
user = insert(:user, local: false)
|
||||
recipient = insert(:user, local: false)
|
||||
|
||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
|
||||
assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,106 @@
|
|||
# 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.ObjectValidators.DeleteValidationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
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
|
||||
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
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# 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.ObjectValidators.EmojiReactHandlingTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "EmojiReacts" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
|
||||
|
||||
{:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
|
||||
|
||||
%{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
|
||||
end
|
||||
|
||||
test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
|
||||
assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
|
||||
end
|
||||
|
||||
test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
|
||||
without_content =
|
||||
valid_emoji_react
|
||||
|> Map.delete("content")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
|
||||
end
|
||||
|
||||
test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
|
||||
without_emoji_content =
|
||||
valid_emoji_react
|
||||
|> Map.put("content", "x")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(without_emoji_content, [])
|
||||
|
||||
refute cng.valid?
|
||||
|
||||
assert {:content, {"must be a single character emoji", []}} in cng.errors
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# 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.ObjectValidators.FollowValidationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "Follows" do
|
||||
setup do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
{:ok, valid_follow, []} = Builder.follow(follower, followed)
|
||||
%{follower: follower, followed: followed, valid_follow: valid_follow}
|
||||
end
|
||||
|
||||
test "validates a basic follow object", %{valid_follow: valid_follow} do
|
||||
assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow, [])
|
||||
end
|
||||
end
|
||||
end
|
113
test/web/activity_pub/object_validators/like_validation_test.exs
Normal file
113
test/web/activity_pub/object_validators/like_validation_test.exs
Normal file
|
@ -0,0 +1,113 @@
|
|||
# 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.ObjectValidators.LikeValidationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "likes" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
|
||||
valid_like = %{
|
||||
"to" => [user.ap_id],
|
||||
"cc" => [],
|
||||
"type" => "Like",
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"object" => post_activity.data["object"],
|
||||
"actor" => user.ap_id,
|
||||
"context" => "a context"
|
||||
}
|
||||
|
||||
%{valid_like: valid_like, user: user, post_activity: post_activity}
|
||||
end
|
||||
|
||||
test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
|
||||
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||
|
||||
assert "id" in Map.keys(object)
|
||||
end
|
||||
|
||||
test "is valid for a valid object", %{valid_like: valid_like} do
|
||||
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "sets the 'to' field to the object actor if no recipients are given", %{
|
||||
valid_like: valid_like,
|
||||
user: user
|
||||
} do
|
||||
without_recipients =
|
||||
valid_like
|
||||
|> Map.delete("to")
|
||||
|
||||
{:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
|
||||
|
||||
assert object["to"] == [user.ap_id]
|
||||
end
|
||||
|
||||
test "sets the context field to the context of the object if no context is given", %{
|
||||
valid_like: valid_like,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
without_context =
|
||||
valid_like
|
||||
|> Map.delete("context")
|
||||
|
||||
{:ok, object, _meta} = ObjectValidator.validate(without_context, [])
|
||||
|
||||
assert object["context"] == post_activity.data["context"]
|
||||
end
|
||||
|
||||
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
||||
without_actor = Map.delete(valid_like, "actor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_actor).valid?
|
||||
|
||||
with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
|
||||
end
|
||||
|
||||
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
||||
without_object = Map.delete(valid_like, "object")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_object).valid?
|
||||
|
||||
with_invalid_object = Map.put(valid_like, "object", "invalidobject")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_object).valid?
|
||||
end
|
||||
|
||||
test "it errors when the actor has already like the object", %{
|
||||
valid_like: valid_like,
|
||||
user: user,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
_like = CommonAPI.favorite(user, post_activity.id)
|
||||
|
||||
refute LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
|
||||
wrapped_like =
|
||||
valid_like
|
||||
|> Map.put("actor", %{"id" => valid_like["actor"]})
|
||||
|> Map.put("object", %{"id" => valid_like["object"]})
|
||||
|
||||
validated = LikeValidator.cast_and_validate(wrapped_like)
|
||||
|
||||
assert validated.valid?
|
||||
|
||||
assert {:actor, valid_like["actor"]} in validated.changes
|
||||
assert {:object, valid_like["object"]} in validated.changes
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# 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.ObjectValidators.UndoHandlingTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "Undos" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
|
||||
{:ok, like} = CommonAPI.favorite(user, post_activity.id)
|
||||
{:ok, valid_like_undo, []} = Builder.undo(user, like)
|
||||
|
||||
%{user: user, like: like, valid_like_undo: valid_like_undo}
|
||||
end
|
||||
|
||||
test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
|
||||
assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
|
||||
end
|
||||
|
||||
test "it does not validate if the actor of the undo is not the actor of the object", %{
|
||||
valid_like_undo: valid_like_undo
|
||||
} do
|
||||
other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||
|
||||
bad_actor =
|
||||
valid_like_undo
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(bad_actor, [])
|
||||
|
||||
assert {:actor, {"not the same as object actor", []}} in cng.errors
|
||||
end
|
||||
|
||||
test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
|
||||
missing_object =
|
||||
valid_like_undo
|
||||
|> Map.put("object", "https://gensokyo.2hu/objects/1")
|
||||
|
||||
{:error, cng} = ObjectValidator.validate(missing_object, [])
|
||||
|
||||
assert {:object, {"can't find object", []}} in cng.errors
|
||||
assert length(cng.errors) == 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# 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.ObjectValidators.UpdateHandlingTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "updates" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
||||
object = %{
|
||||
"id" => user.ap_id,
|
||||
"name" => "A new name",
|
||||
"summary" => "A new bio"
|
||||
}
|
||||
|
||||
{:ok, valid_update, []} = Builder.update(user, object)
|
||||
|
||||
%{user: user, valid_update: valid_update}
|
||||
end
|
||||
|
||||
test "validates a basic object", %{valid_update: valid_update} do
|
||||
assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
|
||||
end
|
||||
|
||||
test "returns an error if the object can't be updated by the actor", %{
|
||||
valid_update: valid_update
|
||||
} do
|
||||
other_user = insert(:user)
|
||||
|
||||
update =
|
||||
valid_update
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|
||||
assert {:error, _cng} = ObjectValidator.validate(update, [])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
|
@ -53,8 +53,7 @@ test "returns errors when user not found" do
|
|||
test "returns activity" do
|
||||
user = insert(:user)
|
||||
service_actor = Relay.get_actor()
|
||||
ActivityPub.follow(service_actor, user)
|
||||
Pleroma.User.follow(service_actor, user)
|
||||
CommonAPI.follow(service_actor, user)
|
||||
assert "#{user.ap_id}/followers" in User.following(service_actor)
|
||||
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
|
||||
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
|
@ -74,6 +73,7 @@ test "returns error when activity not `Create` type" do
|
|||
assert Relay.publish(activity) == {:error, "Not implemented"}
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "returns error when activity not public" do
|
||||
activity = insert(:direct_note_activity)
|
||||
assert Relay.publish(activity) == {:error, false}
|
||||
|
|
|
@ -160,7 +160,7 @@ test "it rejects incoming follow requests if the following errors for some reaso
|
|||
|> Poison.decode!()
|
||||
|> Map.put("object", user.ap_id)
|
||||
|
||||
with_mock Pleroma.User, [:passthrough], follow: fn _, _ -> {:error, :testing} end do
|
||||
with_mock Pleroma.User, [:passthrough], follow: fn _, _, _ -> {:error, :testing} end do
|
||||
{:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
%Activity{} = activity = Activity.get_by_ap_id(id)
|
||||
|
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
@ -452,7 +451,7 @@ test "it works for incoming accepts which were pre-accepted" do
|
|||
{:ok, follower} = User.follow(follower, followed)
|
||||
assert User.following?(follower, followed) == true
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
accept_data =
|
||||
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||
|
@ -482,7 +481,7 @@ test "it works for incoming accepts which were orphaned" do
|
|||
follower = insert(:user)
|
||||
followed = insert(:user, locked: true)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
accept_data =
|
||||
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||
|
@ -504,7 +503,7 @@ test "it works for incoming accepts which are referenced by IRI only" do
|
|||
follower = insert(:user)
|
||||
followed = insert(:user, locked: true)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
accept_data =
|
||||
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||
|
@ -569,7 +568,7 @@ test "it works for incoming rejects which are orphaned" do
|
|||
followed = insert(:user, locked: true)
|
||||
|
||||
{:ok, follower} = User.follow(follower, followed)
|
||||
{:ok, _follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
assert User.following?(follower, followed) == true
|
||||
|
||||
|
@ -595,7 +594,7 @@ test "it works for incoming rejects which are referenced by IRI only" do
|
|||
followed = insert(:user, locked: true)
|
||||
|
||||
{:ok, follower} = User.follow(follower, followed)
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
|
||||
|
||||
assert User.following?(follower, followed) == true
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
@ -197,8 +196,8 @@ test "updates the state of all Follow activities with the same actor and object"
|
|||
user = insert(:user, locked: true)
|
||||
follower = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
|
||||
|
||||
data =
|
||||
follow_activity_two.data
|
||||
|
@ -221,8 +220,8 @@ test "updates the state of the given follow activity" do
|
|||
user = insert(:user, locked: true)
|
||||
follower = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
|
||||
|
||||
data =
|
||||
follow_activity_two.data
|
||||
|
|
|
@ -934,6 +934,15 @@ test "remove a reblog mute", %{muter: muter, muted: muted} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "follow/2" do
|
||||
test "directly follows a non-locked local user" do
|
||||
[follower, followed] = insert_pair(:user)
|
||||
{:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
|
||||
|
||||
assert User.following?(follower, followed)
|
||||
end
|
||||
end
|
||||
|
||||
describe "unfollow/2" do
|
||||
test "also unsubscribes a user" do
|
||||
[follower, followed] = insert_pair(:user)
|
||||
|
@ -998,9 +1007,9 @@ test "after acceptance, it sets all existing pending follow request states to 'a
|
|||
follower = insert(:user)
|
||||
follower_two = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
|
||||
|
||||
assert follow_activity.data["state"] == "pending"
|
||||
assert follow_activity_two.data["state"] == "pending"
|
||||
|
@ -1018,9 +1027,9 @@ test "after rejection, it sets all existing pending follow request states to 're
|
|||
follower = insert(:user)
|
||||
follower_two = insert(:user)
|
||||
|
||||
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
||||
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
|
||||
{:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
|
||||
|
||||
assert follow_activity.data["state"] == "pending"
|
||||
assert follow_activity_two.data["state"] == "pending"
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
|
|||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
|
|||
test "/api/v1/follow_requests works", %{user: user, conn: conn} do
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
{:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
|
||||
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
@ -34,7 +34,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do
|
|||
test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
{:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
|
||||
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
|
||||
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
@ -56,7 +56,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
|
|||
test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
{:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
|
||||
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ test "returns error when followed user is deactivated" do
|
|||
follower = insert(:user)
|
||||
user = insert(:user, local: true, deactivated: true)
|
||||
{:error, error} = MastodonAPI.follow(follower, user)
|
||||
assert error == "Could not follow user: #{user.nickname} is deactivated."
|
||||
assert error == :rejected
|
||||
end
|
||||
|
||||
test "following for user" do
|
||||
|
|
|
@ -372,6 +372,9 @@ test "shows actual follower/following count to the account owner" do
|
|||
user = insert(:user, hide_followers: true, hide_follows: true)
|
||||
other_user = insert(:user)
|
||||
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||
|
||||
assert User.following?(user, other_user)
|
||||
assert Pleroma.FollowingRelationship.follower_count(other_user) == 1
|
||||
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||
|
||||
assert %{
|
||||
|
|
Loading…
Reference in a new issue