Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into pleroma-feature/akkoma-prune-old-posts

This commit is contained in:
Lain Soykaf 2024-05-28 16:51:19 +04:00
commit cc42b50c5b
173 changed files with 4361 additions and 745 deletions

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" template="https://gleasonator.com/.well-known/webfinger?resource={uri}" type="application/xrd+xml" />
</XRD>

View file

@ -0,0 +1,28 @@
{
"aliases": [
"https://gleasonator.com/users/alex",
"https://mitra.social/users/alex"
],
"links": [
{
"href": "https://gleasonator.com/users/alex",
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html"
},
{
"href": "https://gleasonator.com/users/alex",
"rel": "self",
"type": "application/activity+json"
},
{
"href": "https://gleasonator.com/users/alex",
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://gleasonator.com/ostatus_subscribe?acct={uri}"
}
],
"subject": "acct:trump@whitehouse.gov"
}

View file

@ -0,0 +1,41 @@
{
"subject": "acct:graf@poa.st",
"aliases": [
"https://fba.ryona.agenc/webfingertest"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://fba.ryona.agenc/webfingertest"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://fba.ryona.agenc/webfingertest"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://fba.ryona.agenc/contact/follow?url={uri}"
},
{
"rel": "http://schemas.google.com/g/2010#updates-from",
"type": "application/atom+xml",
"href": ""
},
{
"rel": "salmon",
"href": "https://fba.ryona.agenc/salmon/friendica"
},
{
"rel": "http://microformats.org/profile/hcard",
"type": "text/html",
"href": "https://fba.ryona.agenc/hcard/friendica"
},
{
"rel": "http://joindiaspora.com/seed_location",
"type": "text/html",
"href": "https://fba.ryona.agenc"
}
]
}

View file

@ -6,7 +6,6 @@ defmodule Pleroma.NotificationTest do
use Pleroma.DataCase, async: false
import Pleroma.Factory
import Mock
alias Pleroma.FollowingRelationship
alias Pleroma.Notification
@ -18,8 +17,6 @@ defmodule Pleroma.NotificationTest do
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
setup do
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Config)
@ -115,6 +112,7 @@ defmodule Pleroma.NotificationTest do
{:ok, [notification]} = Notification.create_notifications(status)
assert notification.user_id == subscriber.id
assert notification.type == "status"
end
test "does not create a notification for subscribed users if status is a reply" do
@ -139,6 +137,21 @@ defmodule Pleroma.NotificationTest do
assert Enum.empty?(subscriber_notifications)
end
test "does not create subscriber notification if mentioned" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, status} = CommonAPI.post(user, %{status: "mentioning @#{subscriber.nickname}"})
{:ok, [notification] = notifications} = Notification.create_notifications(status)
assert length(notifications) == 1
assert notification.user_id == subscriber.id
assert notification.type == "mention"
end
test "it sends edited notifications to those who repeated a status" do
user = insert(:user)
repeated_user = insert(:user)
@ -175,158 +188,7 @@ defmodule Pleroma.NotificationTest do
assert [user2.id, user3.id, user1.id] == Enum.map(notifications, & &1.user_id)
end
describe "CommonApi.post/2 notification-related functionality" do
test_with_mock "creates but does NOT send notification to blocker user",
Push,
[:passthrough],
[] do
user = insert(:user)
blocker = insert(:user)
{:ok, _user_relationship} = User.block(blocker, user)
{:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{blocker.nickname}!"})
blocker_id = blocker.id
assert [%Notification{user_id: ^blocker_id}] = Repo.all(Notification)
refute called(Push.send(:_))
end
test_with_mock "creates but does NOT send notification to notification-muter user",
Push,
[:passthrough],
[] do
user = insert(:user)
muter = insert(:user)
{:ok, _user_relationships} = User.mute(muter, user)
{:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{muter.nickname}!"})
muter_id = muter.id
assert [%Notification{user_id: ^muter_id}] = Repo.all(Notification)
refute called(Push.send(:_))
end
test_with_mock "creates but does NOT send notification to thread-muter user",
Push,
[:passthrough],
[] do
user = insert(:user)
thread_muter = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{thread_muter.nickname}!"})
{:ok, _} = CommonAPI.add_mute(thread_muter, activity)
{:ok, _same_context_activity} =
CommonAPI.post(user, %{
status: "hey-hey-hey @#{thread_muter.nickname}!",
in_reply_to_status_id: activity.id
})
[pre_mute_notification, post_mute_notification] =
Repo.all(from(n in Notification, where: n.user_id == ^thread_muter.id, order_by: n.id))
pre_mute_notification_id = pre_mute_notification.id
post_mute_notification_id = post_mute_notification.id
assert called(
Push.send(
:meck.is(fn
%Notification{id: ^pre_mute_notification_id} -> true
_ -> false
end)
)
)
refute called(
Push.send(
:meck.is(fn
%Notification{id: ^post_mute_notification_id} -> true
_ -> false
end)
)
)
end
end
describe "create_notification" do
@tag needs_streamer: true
test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do
%{user: user, token: oauth_token} = oauth_access(["read"])
task =
Task.async(fn ->
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
assert_receive {:render_with_user, _, _, _, _}, 4_000
end)
task_user_notification =
Task.async(fn ->
{:ok, _topic} =
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
assert_receive {:render_with_user, _, _, _, _}, 4_000
end)
activity = insert(:note_activity)
notify = Notification.create_notification(activity, user)
assert notify.user_id == user.id
Task.await(task)
Task.await(task_user_notification)
end
test "it creates a notification for user if the user blocks the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user)
{:ok, _user_relationship} = User.block(user, author)
assert Notification.create_notification(activity, user)
end
test "it creates a notification for the user if the user mutes the activity author" do
muter = insert(:user)
muted = insert(:user)
{:ok, _} = User.mute(muter, muted)
muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
notification = Notification.create_notification(activity, muter)
assert notification.id
assert notification.seen
end
test "notification created if user is muted without notifications" do
muter = insert(:user)
muted = insert(:user)
{:ok, _user_relationships} = User.mute(muter, muted, %{notifications: false})
{:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
assert Notification.create_notification(activity, muter)
end
test "it creates a notification for an activity from a muted thread" do
muter = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(muter, %{status: "hey"})
CommonAPI.add_mute(muter, activity)
{:ok, activity} =
CommonAPI.post(other_user, %{
status: "Hi @#{muter.nickname}",
in_reply_to_status_id: activity.id
})
notification = Notification.create_notification(activity, muter)
assert notification.id
assert notification.seen
end
test "it disables notifications from strangers" do
follower = insert(:user)
@ -603,9 +465,7 @@ defmodule Pleroma.NotificationTest do
status: "hey yet again @#{other_user.nickname}!"
})
[_, read_notification] = Notification.set_read_up_to(other_user, n2.id)
assert read_notification.activity.object
Notification.set_read_up_to(other_user, n2.id)
[n3, n2, n1] = Notification.for_user(other_user)
@ -680,7 +540,7 @@ defmodule Pleroma.NotificationTest do
status: "hey @#{other_user.nickname}!"
})
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert other_user in enabled_receivers
end
@ -712,7 +572,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert other_user in enabled_receivers
end
@ -739,7 +599,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert other_user not in enabled_receivers
end
@ -756,8 +616,7 @@ defmodule Pleroma.NotificationTest do
{:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id)
{enabled_receivers, _disabled_receivers} =
Notification.get_notified_from_activity(activity_two)
enabled_receivers = Notification.get_notified_from_activity(activity_two)
assert other_user not in enabled_receivers
end
@ -779,7 +638,7 @@ defmodule Pleroma.NotificationTest do
|> Map.put("to", [other_user.ap_id | like_data["to"]])
|> ActivityPub.persist(local: true)
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(like)
enabled_receivers = Notification.get_notified_from_activity(like)
assert other_user not in enabled_receivers
end
@ -796,39 +655,36 @@ defmodule Pleroma.NotificationTest do
{:ok, activity_two} = CommonAPI.repeat(activity_one.id, third_user)
{enabled_receivers, _disabled_receivers} =
Notification.get_notified_from_activity(activity_two)
enabled_receivers = Notification.get_notified_from_activity(activity_two)
assert other_user not in enabled_receivers
end
test "it returns blocking recipient in disabled recipients list" do
test "it does not return blocking recipient in recipients list" do
user = insert(:user)
other_user = insert(:user)
{:ok, _user_relationship} = User.block(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert [] == enabled_receivers
assert [other_user] == disabled_receivers
end
test "it returns notification-muting recipient in disabled recipients list" do
test "it does not return notification-muting recipient in recipients list" do
user = insert(:user)
other_user = insert(:user)
{:ok, _user_relationships} = User.mute(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert [] == enabled_receivers
assert [other_user] == disabled_receivers
end
test "it returns thread-muting recipient in disabled recipients list" do
test "it does not return thread-muting recipient in recipients list" do
user = insert(:user)
other_user = insert(:user)
@ -842,14 +698,12 @@ defmodule Pleroma.NotificationTest do
in_reply_to_status_id: activity.id
})
{enabled_receivers, disabled_receivers} =
Notification.get_notified_from_activity(same_context_activity)
enabled_receivers = Notification.get_notified_from_activity(same_context_activity)
assert [other_user] == disabled_receivers
refute other_user in enabled_receivers
end
test "it returns non-following domain-blocking recipient in disabled recipients list" do
test "it does not return non-following domain-blocking recipient in recipients list" do
blocked_domain = "blocked.domain"
user = insert(:user, %{ap_id: "https://#{blocked_domain}/@actor"})
other_user = insert(:user)
@ -858,10 +712,9 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert [] == enabled_receivers
assert [other_user] == disabled_receivers
end
test "it returns following domain-blocking recipient in enabled recipients list" do
@ -874,10 +727,9 @@ defmodule Pleroma.NotificationTest do
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
enabled_receivers = Notification.get_notified_from_activity(activity)
assert [other_user] == enabled_receivers
assert [] == disabled_receivers
end
test "it sends edited notifications to those who repeated a status" do
@ -897,11 +749,10 @@ defmodule Pleroma.NotificationTest do
status: "hey @#{other_user.nickname}! mew mew"
})
{enabled_receivers, _disabled_receivers} =
Notification.get_notified_from_activity(edit_activity)
enabled_receivers = Notification.get_notified_from_activity(edit_activity)
assert repeated_user in enabled_receivers
assert other_user not in enabled_receivers
refute other_user in enabled_receivers
end
end
@ -1008,22 +859,6 @@ defmodule Pleroma.NotificationTest do
assert Enum.empty?(Notification.for_user(user))
end
test "replying to a deleted post without tagging does not generate a notification" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "test post"})
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
{:ok, _reply_activity} =
CommonAPI.post(other_user, %{
status: "test reply",
in_reply_to_status_id: activity.id
})
assert Enum.empty?(Notification.for_user(user))
end
test "notifications are deleted if a local user is deleted" do
user = insert(:user)
other_user = insert(:user)
@ -1189,13 +1024,13 @@ defmodule Pleroma.NotificationTest do
assert Notification.for_user(user) == []
end
test "it returns notifications from a muted user when with_muted is set", %{user: user} do
test "it doesn't return notifications from a muted user when with_muted is set", %{user: user} do
muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted)
{:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
assert length(Notification.for_user(user, %{with_muted: true})) == 1
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
end
test "it doesn't return notifications from a blocked user when with_muted is set", %{

View file

@ -0,0 +1,57 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.RuleTest do
use Pleroma.DataCase, async: true
alias Pleroma.Repo
alias Pleroma.Rule
test "getting a list of rules sorted by priority" do
%{id: id1} = Rule.create(%{text: "Example rule"})
%{id: id2} = Rule.create(%{text: "Second rule", priority: 2})
%{id: id3} = Rule.create(%{text: "Third rule", priority: 1})
rules =
Rule.query()
|> Repo.all()
assert [%{id: ^id1}, %{id: ^id3}, %{id: ^id2}] = rules
end
test "creating rules" do
%{id: id} = Rule.create(%{text: "Example rule"})
assert %{text: "Example rule"} = Rule.get(id)
end
test "editing rules" do
%{id: id} = Rule.create(%{text: "Example rule"})
Rule.update(%{text: "There are no rules", priority: 2}, id)
assert %{text: "There are no rules", priority: 2} = Rule.get(id)
end
test "deleting rules" do
%{id: id} = Rule.create(%{text: "Example rule"})
Rule.delete(id)
assert [] =
Rule.query()
|> Pleroma.Repo.all()
end
test "getting rules by ids" do
%{id: id1} = Rule.create(%{text: "Example rule"})
%{id: id2} = Rule.create(%{text: "Second rule"})
%{id: _id3} = Rule.create(%{text: "Third rule"})
rules = Rule.get([id1, id2])
assert Enum.all?(rules, &(&1.id in [id1, id2]))
assert length(rules) == 2
end
end

View file

@ -31,8 +31,7 @@ defmodule Pleroma.ScheduledActivityTest do
{:ok, sa1} = ScheduledActivity.create(user, attrs)
{:ok, sa2} = ScheduledActivity.create(user, attrs)
jobs =
Repo.all(from(j in Oban.Job, where: j.queue == "scheduled_activities", select: j.args))
jobs = Repo.all(from(j in Oban.Job, where: j.queue == "federator_outgoing", select: j.args))
assert jobs == [%{"activity_id" => sa1.id}, %{"activity_id" => sa2.id}]
end

View file

@ -0,0 +1,49 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Search.HealthcheckTest do
use Pleroma.DataCase
import Tesla.Mock
alias Pleroma.Search.Healthcheck
@good1 "http://good1.example.com/healthz"
@good2 "http://good2.example.com/health"
@bad "http://bad.example.com/healthy"
setup do
mock(fn
%{method: :get, url: @good1} ->
%Tesla.Env{
status: 200,
body: ""
}
%{method: :get, url: @good2} ->
%Tesla.Env{
status: 200,
body: ""
}
%{method: :get, url: @bad} ->
%Tesla.Env{
status: 503,
body: ""
}
end)
:ok
end
test "true for 200 responses" do
assert Healthcheck.check([@good1])
assert Healthcheck.check([@good1, @good2])
end
test "false if any response is not a 200" do
refute Healthcheck.check([@bad])
refute Healthcheck.check([@good1, @bad])
end
end

View file

@ -0,0 +1,199 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Search.QdrantSearchTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
import Mox
alias Pleroma.Search.QdrantSearch
alias Pleroma.UnstubbedConfigMock, as: Config
alias Pleroma.Web.CommonAPI
alias Pleroma.Workers.SearchIndexingWorker
describe "Qdrant search" do
test "returns the correct healthcheck endpoints" do
# No openai healthcheck URL
Config
|> expect(:get, 2, fn
[Pleroma.Search.QdrantSearch, key], nil ->
%{qdrant_url: "https://qdrant.url"}[key]
end)
[health_endpoint] = QdrantSearch.healthcheck_endpoints()
assert "https://qdrant.url/healthz" == health_endpoint
# Set openai healthcheck URL
Config
|> expect(:get, 2, fn
[Pleroma.Search.QdrantSearch, key], nil ->
%{qdrant_url: "https://qdrant.url", openai_healthcheck_url: "https://openai.url/health"}[
key
]
end)
[_, health_endpoint] = QdrantSearch.healthcheck_endpoints()
assert "https://openai.url/health" == health_endpoint
end
test "searches for a term by encoding it and sending it to qdrant" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: "public"
})
Config
|> expect(:get, 3, fn
[Pleroma.Search, :module], nil ->
QdrantSearch
[Pleroma.Search.QdrantSearch, key], nil ->
%{
openai_model: "a_model",
openai_url: "https://openai.url",
qdrant_url: "https://qdrant.url"
}[key]
end)
Tesla.Mock.mock(fn
%{url: "https://openai.url/v1/embeddings", method: :post} ->
Tesla.Mock.json(%{
data: [%{embedding: [1, 2, 3]}]
})
%{url: "https://qdrant.url/collections/posts/points/search", method: :post, body: body} ->
data = Jason.decode!(body)
refute data["filter"]
Tesla.Mock.json(%{
result: [%{"id" => activity.id |> FlakeId.from_string() |> Ecto.UUID.cast!()}]
})
end)
results = QdrantSearch.search(nil, "guys i just don't wanna leave the swamp", %{})
assert results == [activity]
end
test "for a given actor, ask for only relevant matches" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: "public"
})
Config
|> expect(:get, 3, fn
[Pleroma.Search, :module], nil ->
QdrantSearch
[Pleroma.Search.QdrantSearch, key], nil ->
%{
openai_model: "a_model",
openai_url: "https://openai.url",
qdrant_url: "https://qdrant.url"
}[key]
end)
Tesla.Mock.mock(fn
%{url: "https://openai.url/v1/embeddings", method: :post} ->
Tesla.Mock.json(%{
data: [%{embedding: [1, 2, 3]}]
})
%{url: "https://qdrant.url/collections/posts/points/search", method: :post, body: body} ->
data = Jason.decode!(body)
assert data["filter"] == %{
"must" => [%{"key" => "actor", "match" => %{"value" => user.ap_id}}]
}
Tesla.Mock.json(%{
result: [%{"id" => activity.id |> FlakeId.from_string() |> Ecto.UUID.cast!()}]
})
end)
results =
QdrantSearch.search(nil, "guys i just don't wanna leave the swamp", %{author: user})
assert results == [activity]
end
test "indexes a public post on creation, deletes from the index on deletion" do
user = insert(:user)
Tesla.Mock.mock(fn
%{method: :post, url: "https://openai.url/v1/embeddings"} ->
send(self(), "posted_to_openai")
Tesla.Mock.json(%{
data: [%{embedding: [1, 2, 3]}]
})
%{method: :put, url: "https://qdrant.url/collections/posts/points", body: body} ->
send(self(), "posted_to_qdrant")
data = Jason.decode!(body)
%{"points" => [%{"vector" => vector, "payload" => payload}]} = data
assert vector == [1, 2, 3]
assert payload["actor"]
assert payload["published_at"]
Tesla.Mock.json("ok")
%{method: :post, url: "https://qdrant.url/collections/posts/points/delete"} ->
send(self(), "deleted_from_qdrant")
Tesla.Mock.json("ok")
end)
Config
|> expect(:get, 6, fn
[Pleroma.Search, :module], nil ->
QdrantSearch
[Pleroma.Search.QdrantSearch, key], nil ->
%{
openai_model: "a_model",
openai_url: "https://openai.url",
qdrant_url: "https://qdrant.url"
}[key]
end)
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: "public"
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(
worker: SearchIndexingWorker,
args: args
)
assert :ok = perform_job(SearchIndexingWorker, args)
assert_received("posted_to_openai")
assert_received("posted_to_qdrant")
{:ok, _} = CommonAPI.delete(activity.id, user)
delete_args = %{"op" => "remove_from_index", "object" => activity.object.id}
assert_enqueued(worker: SearchIndexingWorker, args: delete_args)
assert :ok = perform_job(SearchIndexingWorker, delete_args)
assert_received("deleted_from_qdrant")
end
end
end

View file

@ -67,6 +67,14 @@ defmodule Pleroma.SignatureTest do
end
end
describe "get_actor_id/1" do
test "it returns actor id" do
ap_id = "https://mastodon.social/users/lambadalambda"
assert Signature.get_actor_id(make_fake_conn(ap_id)) == {:ok, ap_id}
end
end
describe "sign/2" do
test "it returns signature headers" do
user =

View file

@ -0,0 +1,158 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.IPFSTest do
use Pleroma.DataCase
alias Pleroma.Uploaders.IPFS
alias Tesla.Multipart
import ExUnit.CaptureLog
import Mock
import Mox
alias Pleroma.UnstubbedConfigMock, as: Config
describe "get_final_url" do
setup do
Config
|> expect(:get, fn [Pleroma.Uploaders.IPFS] ->
[post_gateway_url: "http://localhost:5001"]
end)
:ok
end
test "it returns the final url for put_file" do
assert IPFS.put_file_endpoint() == "http://localhost:5001/api/v0/add"
end
test "it returns the final url for delete_file" do
assert IPFS.delete_file_endpoint() == "http://localhost:5001/api/v0/files/rm"
end
end
describe "get_file/1" do
setup do
Config
|> expect(:get, fn [Pleroma.Upload, :uploader] -> Pleroma.Uploaders.IPFS end)
|> expect(:get, fn [Pleroma.Upload, :base_url] -> nil end)
|> expect(:get, fn [Pleroma.Uploaders.IPFS, :public_endpoint] -> nil end)
:ok
end
test "it returns path to ipfs file with cid as subdomain" do
Config
|> expect(:get, fn [Pleroma.Uploaders.IPFS, :get_gateway_url] ->
"https://{CID}.ipfs.mydomain.com"
end)
assert IPFS.get_file("testcid") == {
:ok,
{:url, "https://testcid.ipfs.mydomain.com"}
}
end
test "it returns path to ipfs file with cid as path" do
Config
|> expect(:get, fn [Pleroma.Uploaders.IPFS, :get_gateway_url] ->
"https://ipfs.mydomain.com/ipfs/{CID}"
end)
assert IPFS.get_file("testcid") == {
:ok,
{:url, "https://ipfs.mydomain.com/ipfs/testcid"}
}
end
end
describe "put_file/1" do
setup do
Config
|> expect(:get, fn [Pleroma.Uploaders.IPFS] ->
[post_gateway_url: "http://localhost:5001"]
end)
file_upload = %Pleroma.Upload{
name: "image-tet.jpg",
content_type: "image/jpeg",
path: "test_folder/image-tet.jpg",
tempfile: Path.absname("test/instance_static/add/shortcode.png")
}
mp =
Multipart.new()
|> Multipart.add_content_type_param("charset=utf-8")
|> Multipart.add_file(file_upload.tempfile)
[file_upload: file_upload, mp: mp]
end
test "save file", %{file_upload: file_upload} do
with_mock Pleroma.HTTP,
post: fn "http://localhost:5001/api/v0/add", _mp, [], params: ["cid-version": "1"] ->
{:ok,
%Tesla.Env{
status: 200,
body:
"{\"Name\":\"image-tet.jpg\",\"Size\":\"5000\", \"Hash\":\"bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi\"}"
}}
end do
assert IPFS.put_file(file_upload) ==
{:ok, {:file, "bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi"}}
end
end
test "returns error", %{file_upload: file_upload} do
with_mock Pleroma.HTTP,
post: fn "http://localhost:5001/api/v0/add", _mp, [], params: ["cid-version": "1"] ->
{:error, "IPFS Gateway upload failed"}
end do
assert capture_log(fn ->
assert IPFS.put_file(file_upload) == {:error, "IPFS Gateway upload failed"}
end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"IPFS Gateway upload failed\"}"
end
end
test "returns error if JSON decode fails", %{file_upload: file_upload} do
with_mock Pleroma.HTTP, [],
post: fn "http://localhost:5001/api/v0/add", _mp, [], params: ["cid-version": "1"] ->
{:ok, %Tesla.Env{status: 200, body: "invalid"}}
end do
assert capture_log(fn ->
assert IPFS.put_file(file_upload) == {:error, "JSON decode failed"}
end) =~
"Elixir.Pleroma.Uploaders.IPFS: {:error, %Jason.DecodeError"
end
end
test "returns error if JSON body doesn't contain Hash key", %{file_upload: file_upload} do
with_mock Pleroma.HTTP, [],
post: fn "http://localhost:5001/api/v0/add", _mp, [], params: ["cid-version": "1"] ->
{:ok, %Tesla.Env{status: 200, body: "{\"key\": \"value\"}"}}
end do
assert IPFS.put_file(file_upload) == {:error, "JSON doesn't contain Hash key"}
end
end
end
describe "delete_file/1" do
setup do
Config
|> expect(:get, fn [Pleroma.Uploaders.IPFS] ->
[post_gateway_url: "http://localhost:5001"]
end)
:ok
end
test_with_mock "deletes file", Pleroma.HTTP,
post: fn "http://localhost:5001/api/v0/files/rm", "", [], params: [arg: "image.jpg"] ->
{:ok, %{status: 204}}
end do
assert :ok = IPFS.delete_file("image.jpg")
end
end
end

View file

@ -877,109 +877,19 @@ defmodule Pleroma.UserTest do
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
test "for mastodon" do
Tesla.Mock.mock(fn
%{url: "https://example.com/.well-known/host-meta"} ->
%Tesla.Env{
status: 302,
headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
}
%{url: "https://sub.example.com/.well-known/host-meta"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-host-meta.xml"
|> File.read!()
|> String.replace("{{domain}}", "sub.example.com")
}
%{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-webfinger.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "example.com")
|> String.replace("{{subdomain}}", "sub.example.com"),
headers: [{"content-type", "application/jrd+json"}]
}
%{url: "https://sub.example.com/users/a"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-user.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "sub.example.com"),
headers: [{"content-type", "application/activity+json"}]
}
%{url: "https://sub.example.com/users/a/collections/featured"} ->
%Tesla.Env{
status: 200,
body:
File.read!("test/fixtures/users_mock/masto_featured.json")
|> String.replace("{{domain}}", "sub.example.com")
|> String.replace("{{nickname}}", "a"),
headers: [{"content-type", "application/activity+json"}]
}
end)
ap_id = "a@example.com"
ap_id = "a@mastodon.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id)
assert fetched_user.ap_id == "https://sub.example.com/users/a"
assert fetched_user.nickname == "a@example.com"
assert fetched_user.ap_id == "https://sub.mastodon.example/users/a"
assert fetched_user.nickname == "a@mastodon.example"
end
test "for pleroma" do
Tesla.Mock.mock(fn
%{url: "https://example.com/.well-known/host-meta"} ->
%Tesla.Env{
status: 302,
headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
}
%{url: "https://sub.example.com/.well-known/host-meta"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-host-meta.xml"
|> File.read!()
|> String.replace("{{domain}}", "sub.example.com")
}
%{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-webfinger.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "example.com")
|> String.replace("{{subdomain}}", "sub.example.com"),
headers: [{"content-type", "application/jrd+json"}]
}
%{url: "https://sub.example.com/users/a"} ->
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-user.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "sub.example.com"),
headers: [{"content-type", "application/activity+json"}]
}
end)
ap_id = "a@example.com"
ap_id = "a@pleroma.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id)
assert fetched_user.ap_id == "https://sub.example.com/users/a"
assert fetched_user.nickname == "a@example.com"
assert fetched_user.ap_id == "https://sub.pleroma.example/users/a"
assert fetched_user.nickname == "a@pleroma.example"
end
end
@ -2894,6 +2804,20 @@ defmodule Pleroma.UserTest do
end
end
describe "get_familiar_followers/3" do
test "returns familiar followers for a pair of users" do
user1 = insert(:user)
%{id: id2} = user2 = insert(:user)
user3 = insert(:user)
_user4 = insert(:user)
User.follow(user1, user2)
User.follow(user2, user3)
assert [%{id: ^id2}] = User.get_familiar_followers(user3, user1)
end
end
describe "account endorsements" do
test "it pins people" do
user = insert(:user)

View file

@ -0,0 +1,65 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicyTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy
test "it allows posts without mentions" do
user = insert(:user, local: false)
assert user.note_count == 0
message = %{
"type" => "Create",
"actor" => user.ap_id
}
{:ok, _message} = AntiMentionSpamPolicy.filter(message)
end
test "it allows posts from users with followers, posts, and age" do
user =
insert(:user,
local: false,
follower_count: 1,
note_count: 1,
inserted_at: ~N[1970-01-01 00:00:00]
)
message = %{
"type" => "Create",
"actor" => user.ap_id
}
{:ok, _message} = AntiMentionSpamPolicy.filter(message)
end
test "it allows posts from local users" do
user = insert(:user, local: true)
message = %{
"type" => "Create",
"actor" => user.ap_id
}
{:ok, _message} = AntiMentionSpamPolicy.filter(message)
end
test "it rejects posts with mentions from users without followers" do
user = insert(:user, local: false, follower_count: 0)
message = %{
"type" => "Create",
"actor" => user.ap_id,
"object" => %{
"to" => ["https://pleroma.soykaf.com/users/1"],
"cc" => ["https://pleroma.soykaf.com/users/1"],
"actor" => user.ap_id
}
}
{:reject, _message} = AntiMentionSpamPolicy.filter(message)
end
end

View file

@ -0,0 +1,267 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicyTest do
use Pleroma.DataCase
import ExUnit.CaptureLog
import Pleroma.Factory
alias Pleroma.Constants
alias Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy
require Pleroma.Constants
@policy :mrf_nsfw_api
@sfw_url "https://kittens.co/kitty.gif"
@nsfw_url "https://b00bies.com/nsfw.jpg"
@timeout_url "http://time.out/i.jpg"
setup_all do
clear_config(@policy,
url: "http://127.0.0.1:5000/",
threshold: 0.7,
mark_sensitive: true,
unlist: false,
reject: false
)
end
setup do
Tesla.Mock.mock(fn
# NSFW URL
%{method: :get, url: "http://127.0.0.1:5000/?url=#{@nsfw_url}"} ->
%Tesla.Env{status: 200, body: ~s({"score":0.99772077798843384,"url":"#{@nsfw_url}"})}
# SFW URL
%{method: :get, url: "http://127.0.0.1:5000/?url=#{@sfw_url}"} ->
%Tesla.Env{status: 200, body: ~s({"score":0.00011714912398019806,"url":"#{@sfw_url}"})}
# Timeout URL
%{method: :get, url: "http://127.0.0.1:5000/?url=#{@timeout_url}"} ->
{:error, :timeout}
# Fallback URL
%{method: :get, url: "http://127.0.0.1:5000/?url=" <> url} ->
body =
~s({"error_code":500,"error_reason":"[Errno -2] Name or service not known","url":"#{url}"})
%Tesla.Env{status: 500, body: body}
end)
:ok
end
describe "build_request_url/1" do
test "it works" do
expected = "http://127.0.0.1:5000/?url=https://b00bies.com/nsfw.jpg"
assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected
end
test "it adds a trailing slash" do
clear_config([@policy, :url], "http://localhost:5000")
expected = "http://localhost:5000/?url=https://b00bies.com/nsfw.jpg"
assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected
end
test "it adds a trailing slash preserving the path" do
clear_config([@policy, :url], "http://localhost:5000/nsfw_api")
expected = "http://localhost:5000/nsfw_api/?url=https://b00bies.com/nsfw.jpg"
assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected
end
end
describe "parse_url/1" do
test "returns decoded JSON from the API server" do
expected = %{"score" => 0.99772077798843384, "url" => @nsfw_url}
assert NsfwApiPolicy.parse_url(@nsfw_url) == {:ok, expected}
end
test "warns when the API server fails" do
expected = "[NsfwApiPolicy]: The API server failed. Skipping."
assert capture_log(fn -> NsfwApiPolicy.parse_url(@timeout_url) end) =~ expected
end
test "returns {:error, _} tuple when the API server fails" do
capture_log(fn ->
assert {:error, _} = NsfwApiPolicy.parse_url(@timeout_url)
end)
end
end
describe "check_url_nsfw/1" do
test "returns {:nsfw, _} tuple" do
expected = {:nsfw, %{url: @nsfw_url, score: 0.99772077798843384, threshold: 0.7}}
assert NsfwApiPolicy.check_url_nsfw(@nsfw_url) == expected
end
test "returns {:sfw, _} tuple" do
expected = {:sfw, %{url: @sfw_url, score: 0.00011714912398019806, threshold: 0.7}}
assert NsfwApiPolicy.check_url_nsfw(@sfw_url) == expected
end
test "returns {:sfw, _} on failure" do
expected = {:sfw, %{url: @timeout_url, score: nil, threshold: 0.7}}
capture_log(fn ->
assert NsfwApiPolicy.check_url_nsfw(@timeout_url) == expected
end)
end
test "works with map URL" do
expected = {:nsfw, %{url: @nsfw_url, score: 0.99772077798843384, threshold: 0.7}}
assert NsfwApiPolicy.check_url_nsfw(%{"href" => @nsfw_url}) == expected
end
end
describe "check_attachment_nsfw/1" do
test "returns {:nsfw, _} if any items are NSFW" do
attachment = %{"url" => [%{"href" => @nsfw_url}, @nsfw_url, @sfw_url]}
assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:nsfw, attachment}
end
test "returns {:sfw, _} if all items are SFW" do
attachment = %{"url" => [%{"href" => @sfw_url}, @sfw_url, @sfw_url]}
assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:sfw, attachment}
end
test "works with binary URL" do
attachment = %{"url" => @nsfw_url}
assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:nsfw, attachment}
end
end
describe "check_object_nsfw/1" do
test "returns {:nsfw, _} if any items are NSFW" do
object = %{"attachment" => [%{"url" => [%{"href" => @nsfw_url}, @sfw_url]}]}
assert NsfwApiPolicy.check_object_nsfw(object) == {:nsfw, object}
end
test "returns {:sfw, _} if all items are SFW" do
object = %{"attachment" => [%{"url" => [%{"href" => @sfw_url}, @sfw_url]}]}
assert NsfwApiPolicy.check_object_nsfw(object) == {:sfw, object}
end
test "works with embedded object" do
object = %{"object" => %{"attachment" => [%{"url" => [%{"href" => @nsfw_url}, @sfw_url]}]}}
assert NsfwApiPolicy.check_object_nsfw(object) == {:nsfw, object}
end
end
describe "unlist/1" do
test "unlist addressing" do
user = insert(:user)
object = %{
"to" => [Constants.as_public()],
"cc" => [user.follower_address, "https://hello.world/users/alex"],
"actor" => user.ap_id
}
expected = %{
"to" => [user.follower_address],
"cc" => [Constants.as_public(), "https://hello.world/users/alex"],
"actor" => user.ap_id
}
assert NsfwApiPolicy.unlist(object) == expected
end
test "raise if user isn't found" do
object = %{
"to" => [Constants.as_public()],
"cc" => [],
"actor" => "https://hello.world/users/alex"
}
assert_raise(RuntimeError, fn ->
NsfwApiPolicy.unlist(object)
end)
end
end
describe "mark_sensitive/1" do
test "adds nsfw tag and marks sensitive" do
object = %{"tag" => ["yolo"]}
expected = %{"tag" => ["yolo", "nsfw"], "sensitive" => true}
assert NsfwApiPolicy.mark_sensitive(object) == expected
end
test "works with embedded object" do
object = %{"object" => %{"tag" => ["yolo"]}}
expected = %{"object" => %{"tag" => ["yolo", "nsfw"], "sensitive" => true}}
assert NsfwApiPolicy.mark_sensitive(object) == expected
end
end
describe "filter/1" do
setup do
user = insert(:user)
nsfw_object = %{
"to" => [Constants.as_public()],
"cc" => [user.follower_address],
"actor" => user.ap_id,
"attachment" => [%{"url" => @nsfw_url}]
}
sfw_object = %{
"to" => [Constants.as_public()],
"cc" => [user.follower_address],
"actor" => user.ap_id,
"attachment" => [%{"url" => @sfw_url}]
}
%{user: user, nsfw_object: nsfw_object, sfw_object: sfw_object}
end
test "passes SFW object through", %{sfw_object: object} do
{:ok, _} = NsfwApiPolicy.filter(object)
end
test "passes NSFW object through when actions are disabled", %{nsfw_object: object} do
clear_config([@policy, :mark_sensitive], false)
clear_config([@policy, :unlist], false)
clear_config([@policy, :reject], false)
{:ok, _} = NsfwApiPolicy.filter(object)
end
test "passes NSFW object through when :threshold is 1", %{nsfw_object: object} do
clear_config([@policy, :reject], true)
clear_config([@policy, :threshold], 1)
{:ok, _} = NsfwApiPolicy.filter(object)
end
test "rejects SFW object through when :threshold is 0", %{sfw_object: object} do
clear_config([@policy, :reject], true)
clear_config([@policy, :threshold], 0)
{:reject, _} = NsfwApiPolicy.filter(object)
end
test "rejects NSFW when :reject is enabled", %{nsfw_object: object} do
clear_config([@policy, :reject], true)
{:reject, _} = NsfwApiPolicy.filter(object)
end
test "passes NSFW through when :reject is disabled", %{nsfw_object: object} do
clear_config([@policy, :reject], false)
{:ok, _} = NsfwApiPolicy.filter(object)
end
test "unlists NSFW when :unlist is enabled", %{user: user, nsfw_object: object} do
clear_config([@policy, :unlist], true)
{:ok, object} = NsfwApiPolicy.filter(object)
assert object["to"] == [user.follower_address]
end
test "passes NSFW through when :unlist is disabled", %{nsfw_object: object} do
clear_config([@policy, :unlist], false)
{:ok, object} = NsfwApiPolicy.filter(object)
assert object["to"] == [Constants.as_public()]
end
end
end

View file

@ -27,19 +27,22 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
end
test "works with honkerific attachments" do
attachment = %{
honk = %{
"mediaType" => "",
"name" => "",
"summary" => "298p3RG7j27tfsZ9RQ.jpg",
"summary" => "Select your spirit chonk",
"name" => "298p3RG7j27tfsZ9RQ.jpg",
"type" => "Document",
"url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
}
assert {:ok, attachment} =
AttachmentValidator.cast_and_validate(attachment)
honk
|> AttachmentValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert)
assert attachment.mediaType == "application/octet-stream"
assert attachment.summary == "Select your spirit chonk"
assert attachment.name == "298p3RG7j27tfsZ9RQ.jpg"
end
test "works with an unknown but valid mime type" do

View file

@ -827,31 +827,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
{:ok, announce, _} = SideEffects.handle(announce)
assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
end
test "it streams out the announce", %{announce: announce} do
with_mocks([
{
Pleroma.Web.Streamer,
[],
[
stream: fn _, _ -> nil end
]
},
{
Pleroma.Web.Push,
[],
[
send: fn _ -> nil end
]
}
]) do
{:ok, announce, _} = SideEffects.handle(announce)
assert called(Pleroma.Web.Streamer.stream(["user", "list"], announce))
assert called(Pleroma.Web.Push.send(:_))
end
end
end
describe "removing a follower" do

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.ReportNote
alias Pleroma.Rule
alias Pleroma.Web.CommonAPI
setup do
@ -436,6 +437,34 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
"error" => "Invalid credentials."
}
end
test "returns reports with specified role_id", %{conn: conn} do
[reporter, target_user] = insert_pair(:user)
%{id: rule_id} = Rule.create(%{text: "Example rule"})
rule_id = to_string(rule_id)
{:ok, %{id: report_id}} =
CommonAPI.report(reporter, %{
account_id: target_user.id,
comment: "",
rule_ids: [rule_id]
})
{:ok, _report} =
CommonAPI.report(reporter, %{
account_id: target_user.id,
comment: ""
})
response =
conn
|> get("/api/pleroma/admin/reports?rule_id=#{rule_id}")
|> json_response_and_validate_schema(:ok)
assert %{"reports" => [%{"id" => ^report_id}]} = response
end
end
describe "POST /api/pleroma/admin/reports/:id/notes" do

View file

@ -0,0 +1,82 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.RuleControllerTest do
use Pleroma.Web.ConnCase, async: true
import Pleroma.Factory
alias Pleroma.Rule
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
describe "GET /api/pleroma/admin/rules" do
test "sorts rules by priority", %{conn: conn} do
%{id: id1} = Rule.create(%{text: "Example rule"})
%{id: id2} = Rule.create(%{text: "Second rule", priority: 2})
%{id: id3} = Rule.create(%{text: "Third rule", priority: 1})
id1 = to_string(id1)
id2 = to_string(id2)
id3 = to_string(id3)
response =
conn
|> get("/api/pleroma/admin/rules")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^id1}, %{"id" => ^id3}, %{"id" => ^id2}] = response
end
end
describe "POST /api/pleroma/admin/rules" do
test "creates a rule", %{conn: conn} do
%{"id" => id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/rules", %{text: "Example rule"})
|> json_response_and_validate_schema(:ok)
assert %{text: "Example rule"} = Rule.get(id)
end
end
describe "PATCH /api/pleroma/admin/rules" do
test "edits a rule", %{conn: conn} do
%{id: id} = Rule.create(%{text: "Example rule"})
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/pleroma/admin/rules/#{id}", %{text: "There are no rules", priority: 2})
|> json_response_and_validate_schema(:ok)
assert %{text: "There are no rules", priority: 2} = Rule.get(id)
end
end
describe "DELETE /api/pleroma/admin/rules" do
test "deletes a rule", %{conn: conn} do
%{id: id} = Rule.create(%{text: "Example rule"})
conn
|> put_req_header("content-type", "application/json")
|> delete("/api/pleroma/admin/rules/#{id}")
|> json_response_and_validate_schema(:ok)
assert [] =
Rule.query()
|> Pleroma.Repo.all()
end
end
end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
import Pleroma.Factory
alias Pleroma.Rule
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.AdminAPI.ReportView
@ -38,7 +39,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
statuses: [],
notes: [],
state: "open",
id: activity.id
id: activity.id,
rules: []
}
result =
@ -76,7 +78,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
statuses: [StatusView.render("show.json", %{activity: activity})],
state: "open",
notes: [],
id: report_activity.id
id: report_activity.id,
rules: []
}
result =
@ -168,4 +171,22 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
assert report2.id == rendered |> Enum.at(0) |> Map.get(:id)
assert report1.id == rendered |> Enum.at(1) |> Map.get(:id)
end
test "renders included rules" do
user = insert(:user)
other_user = insert(:user)
%{id: rule_id, text: text} = Rule.create(%{text: "Example rule"})
rule_id = to_string(rule_id)
{:ok, activity} =
CommonAPI.report(user, %{
account_id: other_user.id,
rule_ids: [rule_id]
})
assert %{rules: [%{id: ^rule_id, text: ^text}]} =
ReportView.render("show.json", Report.extract_report_info(activity))
end
end

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Web.CommonAPITest do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Rule
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -1363,6 +1364,33 @@ defmodule Pleroma.Web.CommonAPITest do
assert first_report.data["state"] == "resolved"
assert second_report.data["state"] == "resolved"
end
test "creates a report with provided rules" do
reporter = insert(:user)
target_user = insert(:user)
%{id: rule_id} = Rule.create(%{text: "There are no rules"})
reporter_ap_id = reporter.ap_id
target_ap_id = target_user.ap_id
report_data = %{
account_id: target_user.id,
rule_ids: [rule_id]
}
assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
assert %Activity{
actor: ^reporter_ap_id,
data: %{
"type" => "Flag",
"object" => [^target_ap_id],
"state" => "open",
"rules" => [^rule_id]
}
} = flag_activity
end
end
describe "reblog muting" do

View file

@ -2172,6 +2172,55 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
end
end
describe "familiar followers" do
setup do: oauth_access(["read:follows"])
test "fetch user familiar followers", %{user: user, conn: conn} do
%{id: id1} = other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [%{"id" => ^id1}], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "returns empty array if followers are hidden", %{user: user, conn: conn} do
other_user1 = insert(:user, hide_follows: true)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "it respects hide_followers", %{user: user, conn: conn} do
other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user, hide_followers: true)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
end
describe "remove from followers" do
setup do: oauth_access(["follow"])

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
# TODO: Should not need Cachex
use Pleroma.Web.ConnCase
alias Pleroma.Rule
alias Pleroma.User
import Pleroma.Factory
@ -40,7 +41,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
"banner_upload_limit" => _,
"background_image" => from_config_background,
"shout_limit" => _,
"description_limit" => _
"description_limit" => _,
"rules" => _
} = result
assert result["pleroma"]["metadata"]["account_activation_required"] != nil
@ -125,4 +127,29 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
assert get(conn, "/api/v2/instance")
|> json_response_and_validate_schema(200)
end
test "get instance rules", %{conn: conn} do
Rule.create(%{text: "Example rule", hint: "Rule description", priority: 1})
Rule.create(%{text: "Third rule", priority: 2})
Rule.create(%{text: "Second rule", priority: 1})
conn = get(conn, "/api/v1/instance")
assert result = json_response_and_validate_schema(conn, 200)
assert [
%{
"text" => "Example rule",
"hint" => "Rule description"
},
%{
"text" => "Second rule",
"hint" => ""
},
%{
"text" => "Third rule",
"hint" => ""
}
] = result["rules"]
end
end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.Rule
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@ -81,6 +82,44 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|> json_response_and_validate_schema(200)
end
test "submit a report with rule_ids", %{
conn: conn,
target_user: target_user
} do
%{id: rule_id} = Rule.create(%{text: "There are no rules"})
rule_id = to_string(rule_id)
assert %{"action_taken" => false, "id" => id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"forward" => "false",
"rule_ids" => [rule_id]
})
|> json_response_and_validate_schema(200)
assert %Activity{data: %{"rules" => [^rule_id]}} = Activity.get_report(id)
end
test "rules field is empty if provided wrong rule id", %{
conn: conn,
target_user: target_user
} do
assert %{"id" => id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"forward" => "false",
"rule_ids" => ["-1"]
})
|> json_response_and_validate_schema(200)
assert %Activity{data: %{"rules" => []}} = Activity.get_report(id)
end
test "account_id is required", %{
conn: conn,
activity: activity

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Repo
@ -78,7 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
}
)
job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities"))
job = Repo.one(from(j in Oban.Job, where: j.queue == "federator_outgoing"))
assert job.args == %{"activity_id" => scheduled_activity.id}
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at)
@ -124,9 +125,11 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
}
)
job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities"))
assert job.args == %{"activity_id" => scheduled_activity.id}
assert_enqueued(
worker: Pleroma.Workers.ScheduledActivityWorker,
args: %{"activity_id" => scheduled_activity.id},
queue: :federator_outgoing
)
res_conn =
conn
@ -135,7 +138,11 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
assert %{} = json_response_and_validate_schema(res_conn, 200)
refute Repo.get(ScheduledActivity, scheduled_activity.id)
refute Repo.get(Oban.Job, job.id)
refute_enqueued(
worker: Pleroma.Workers.ScheduledActivityWorker,
args: %{"activity_id" => scheduled_activity.id}
)
res_conn =
conn

View file

@ -235,6 +235,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
end
test "replying to a deleted status", %{user: user, conn: conn} do
{:ok, status} = CommonAPI.post(user, %{status: "cofe"})
{:ok, _deleted_status} = CommonAPI.delete(status.id, user)
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => status.id})
|> json_response_and_validate_schema(422)
end
test "replying to a direct message with visibility other than direct", %{
user: user,
conn: conn

View file

@ -331,4 +331,31 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
test_notifications_rendering([notification], user, [expected])
end
test "Subscribed status notification" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hi"})
{:ok, [notification]} = Notification.create_notifications(activity)
user = User.get_cached_by_id(user.id)
expected = %{
id: to_string(notification.id),
pleroma: %{is_seen: false, is_muted: false},
type: "status",
account:
AccountView.render("show.json", %{
user: user,
for: subscriber
}),
status: StatusView.render("show.json", %{activity: activity, for: subscriber}),
created_at: Utils.to_masto_date(notification.inserted_at)
}
test_notifications_rendering([notification], subscriber, [expected])
end
end

View file

@ -591,45 +591,78 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert mention.url == recipient.ap_id
end
test "attachments" do
object = %{
"type" => "Image",
"url" => [
%{
"mediaType" => "image/png",
"href" => "someurl",
"width" => 200,
"height" => 100
}
],
"blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
"uuid" => 6
}
describe "attachments" do
test "Complete Mastodon style" do
object = %{
"type" => "Image",
"url" => [
%{
"mediaType" => "image/png",
"href" => "someurl",
"width" => 200,
"height" => 100
}
],
"blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
"uuid" => 6
}
expected = %{
id: "1638338801",
type: "image",
url: "someurl",
remote_url: "someurl",
preview_url: "someurl",
text_url: "someurl",
description: nil,
pleroma: %{mime_type: "image/png"},
meta: %{original: %{width: 200, height: 100, aspect: 2}},
blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
}
expected = %{
id: "1638338801",
type: "image",
url: "someurl",
remote_url: "someurl",
preview_url: "someurl",
text_url: "someurl",
description: nil,
pleroma: %{mime_type: "image/png"},
meta: %{original: %{width: 200, height: 100, aspect: 2}},
blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
}
api_spec = Pleroma.Web.ApiSpec.spec()
api_spec = Pleroma.Web.ApiSpec.spec()
assert expected == StatusView.render("attachment.json", %{attachment: object})
assert_schema(expected, "Attachment", api_spec)
assert expected == StatusView.render("attachment.json", %{attachment: object})
assert_schema(expected, "Attachment", api_spec)
# If theres a "id", use that instead of the generated one
object = Map.put(object, "id", 2)
result = StatusView.render("attachment.json", %{attachment: object})
# If theres a "id", use that instead of the generated one
object = Map.put(object, "id", 2)
result = StatusView.render("attachment.json", %{attachment: object})
assert %{id: "2"} = result
assert_schema(result, "Attachment", api_spec)
assert %{id: "2"} = result
assert_schema(result, "Attachment", api_spec)
end
test "Honkerific" do
object = %{
"type" => "Image",
"url" => [
%{
"mediaType" => "image/png",
"href" => "someurl"
}
],
"name" => "fool.jpeg",
"summary" => "they have played us for absolute fools."
}
expected = %{
blurhash: nil,
description: "they have played us for absolute fools.",
id: "1638338801",
pleroma: %{mime_type: "image/png", name: "fool.jpeg"},
preview_url: "someurl",
remote_url: "someurl",
text_url: "someurl",
type: "image",
url: "someurl"
}
api_spec = Pleroma.Web.ApiSpec.spec()
assert expected == StatusView.render("attachment.json", %{attachment: object})
assert_schema(expected, "Attachment", api_spec)
end
end
test "put the url advertised in the Activity in to the url attribute" do

View file

@ -21,13 +21,11 @@ defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do
{:ok, [notification1]} = Notification.create_notifications(activity1)
{:ok, [notification2]} = Notification.create_notifications(activity2)
response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{id: notification1.id})
|> json_response_and_validate_schema(:ok)
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{id: notification1.id})
|> json_response_and_validate_schema(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response
assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
end
@ -40,14 +38,17 @@ defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
[response1, response2] =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id})
|> json_response_and_validate_schema(:ok)
refute Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
refute Repo.get(Notification, notification3.id).seen
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id})
|> json_response_and_validate_schema(:ok)
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
assert %{"pleroma" => %{"is_seen" => true}} = response1
assert %{"pleroma" => %{"is_seen" => true}} = response2
assert Repo.get(Notification, notification1.id).seen
assert Repo.get(Notification, notification2.id).seen
refute Repo.get(Notification, notification3.id).seen

View file

@ -3,14 +3,52 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
use Pleroma.Web.ConnCase
use Pleroma.Web.ConnCase, async: true
alias Plug.Conn
describe "http security enabled" do
setup do: clear_config([:http_security, :enabled], true)
import Mox
test "it sends CSP headers when enabled", %{conn: conn} do
setup do
base_config = Pleroma.Config.get([:http_security])
%{base_config: base_config}
end
defp mock_config(config, additional \\ %{}) do
Pleroma.StaticStubbedConfigMock
|> stub(:get, fn
[:http_security, key] -> config[key]
key -> additional[key]
end)
end
describe "http security enabled" do
setup %{base_config: base_config} do
%{base_config: Keyword.put(base_config, :enabled, true)}
end
test "it does not contain unsafe-eval", %{conn: conn, base_config: base_config} do
mock_config(base_config)
conn = get(conn, "/api/v1/instance")
[header] = Conn.get_resp_header(conn, "content-security-policy")
refute header =~ ~r/unsafe-eval/
end
test "with allow_unsafe_eval set, it does contain it", %{conn: conn, base_config: base_config} do
base_config =
base_config
|> Keyword.put(:allow_unsafe_eval, true)
mock_config(base_config)
conn = get(conn, "/api/v1/instance")
[header] = Conn.get_resp_header(conn, "content-security-policy")
assert header =~ ~r/unsafe-eval/
end
test "it sends CSP headers when enabled", %{conn: conn, base_config: base_config} do
mock_config(base_config)
conn = get(conn, "/api/v1/instance")
refute Conn.get_resp_header(conn, "x-xss-protection") == []
@ -22,8 +60,10 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
refute Conn.get_resp_header(conn, "content-security-policy") == []
end
test "it sends STS headers when enabled", %{conn: conn} do
clear_config([:http_security, :sts], true)
test "it sends STS headers when enabled", %{conn: conn, base_config: base_config} do
base_config
|> Keyword.put(:sts, true)
|> mock_config()
conn = get(conn, "/api/v1/instance")
@ -31,8 +71,10 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
refute Conn.get_resp_header(conn, "expect-ct") == []
end
test "it does not send STS headers when disabled", %{conn: conn} do
clear_config([:http_security, :sts], false)
test "it does not send STS headers when disabled", %{conn: conn, base_config: base_config} do
base_config
|> Keyword.put(:sts, false)
|> mock_config()
conn = get(conn, "/api/v1/instance")
@ -40,19 +82,30 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
assert Conn.get_resp_header(conn, "expect-ct") == []
end
test "referrer-policy header reflects configured value", %{conn: conn} do
resp = get(conn, "/api/v1/instance")
test "referrer-policy header reflects configured value", %{
conn: conn,
base_config: base_config
} do
mock_config(base_config)
resp = get(conn, "/api/v1/instance")
assert Conn.get_resp_header(resp, "referrer-policy") == ["same-origin"]
clear_config([:http_security, :referrer_policy], "no-referrer")
base_config
|> Keyword.put(:referrer_policy, "no-referrer")
|> mock_config
resp = get(conn, "/api/v1/instance")
assert Conn.get_resp_header(resp, "referrer-policy") == ["no-referrer"]
end
test "it sends `report-to` & `report-uri` CSP response headers", %{conn: conn} do
test "it sends `report-to` & `report-uri` CSP response headers", %{
conn: conn,
base_config: base_config
} do
mock_config(base_config)
conn = get(conn, "/api/v1/instance")
[csp] = Conn.get_resp_header(conn, "content-security-policy")
@ -65,7 +118,11 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
"{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
end
test "default values for img-src and media-src with disabled media proxy", %{conn: conn} do
test "default values for img-src and media-src with disabled media proxy", %{
conn: conn,
base_config: base_config
} do
mock_config(base_config)
conn = get(conn, "/api/v1/instance")
[csp] = Conn.get_resp_header(conn, "content-security-policy")
@ -73,60 +130,129 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
assert csp =~ "img-src 'self' data: blob: https:;"
end
test "it sets the Service-Worker-Allowed header", %{conn: conn} do
clear_config([:http_security, :enabled], true)
clear_config([:frontends, :primary], %{"name" => "fedi-fe", "ref" => "develop"})
test "it sets the Service-Worker-Allowed header", %{conn: conn, base_config: base_config} do
base_config
|> Keyword.put(:enabled, true)
clear_config([:frontends, :available], %{
"fedi-fe" => %{
"name" => "fedi-fe",
"custom-http-headers" => [{"service-worker-allowed", "/"}]
}
})
additional_config =
%{}
|> Map.put([:frontends, :primary], %{"name" => "fedi-fe", "ref" => "develop"})
|> Map.put(
[:frontends, :available],
%{
"fedi-fe" => %{
"name" => "fedi-fe",
"custom-http-headers" => [{"service-worker-allowed", "/"}]
}
}
)
mock_config(base_config, additional_config)
conn = get(conn, "/api/v1/instance")
assert Conn.get_resp_header(conn, "service-worker-allowed") == ["/"]
end
end
describe "img-src and media-src" do
setup do
clear_config([:http_security, :enabled], true)
clear_config([:media_proxy, :enabled], true)
clear_config([:media_proxy, :proxy_opts, :redirect_on_failure], false)
setup %{base_config: base_config} do
base_config =
base_config
|> Keyword.put(:enabled, true)
additional_config =
%{}
|> Map.put([:media_proxy, :enabled], true)
|> Map.put([:media_proxy, :proxy_opts, :redirect_on_failure], false)
|> Map.put([:media_proxy, :whitelist], [])
%{base_config: base_config, additional_config: additional_config}
end
test "media_proxy with base_url", %{conn: conn} do
test "media_proxy with base_url", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
url = "https://example.com"
clear_config([:media_proxy, :base_url], url)
additional_config =
additional_config
|> Map.put([:media_proxy, :base_url], url)
mock_config(base_config, additional_config)
assert_media_img_src(conn, url)
end
test "upload with base url", %{conn: conn} do
test "upload with base url", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
url = "https://example2.com"
clear_config([Pleroma.Upload, :base_url], url)
additional_config =
additional_config
|> Map.put([Pleroma.Upload, :base_url], url)
mock_config(base_config, additional_config)
assert_media_img_src(conn, url)
end
test "with S3 public endpoint", %{conn: conn} do
test "with S3 public endpoint", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
url = "https://example3.com"
clear_config([Pleroma.Uploaders.S3, :public_endpoint], url)
additional_config =
additional_config
|> Map.put([Pleroma.Uploaders.S3, :public_endpoint], url)
mock_config(base_config, additional_config)
assert_media_img_src(conn, url)
end
test "with captcha endpoint", %{conn: conn} do
clear_config([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com")
test "with captcha endpoint", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
additional_config =
additional_config
|> Map.put([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com")
|> Map.put([Pleroma.Captcha, :method], Pleroma.Captcha.Mock)
mock_config(base_config, additional_config)
assert_media_img_src(conn, "https://captcha.com")
end
test "with media_proxy whitelist", %{conn: conn} do
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
test "with media_proxy whitelist", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
additional_config =
additional_config
|> Map.put([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
mock_config(base_config, additional_config)
assert_media_img_src(conn, "https://example7.com https://example6.com")
end
# TODO: delete after removing support bare domains for media proxy whitelist
test "with media_proxy bare domains whitelist (deprecated)", %{conn: conn} do
clear_config([:media_proxy, :whitelist], ["example4.com", "example5.com"])
test "with media_proxy bare domains whitelist (deprecated)", %{
conn: conn,
base_config: base_config,
additional_config: additional_config
} do
additional_config =
additional_config
|> Map.put([:media_proxy, :whitelist], ["example4.com", "example5.com"])
mock_config(base_config, additional_config)
assert_media_img_src(conn, "example5.com example4.com")
end
end
@ -138,8 +264,10 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
assert csp =~ "img-src 'self' data: blob: #{url};"
end
test "it does not send CSP headers when disabled", %{conn: conn} do
clear_config([:http_security, :enabled], false)
test "it does not send CSP headers when disabled", %{conn: conn, base_config: base_config} do
base_config
|> Keyword.put(:enabled, false)
|> mock_config
conn = get(conn, "/api/v1/instance")

View file

@ -3,77 +3,89 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
use Pleroma.Web.ConnCase
use Pleroma.Web.ConnCase, async: true
alias Pleroma.StaticStubbedConfigMock, as: ConfigMock
alias Pleroma.StubbedHTTPSignaturesMock, as: HTTPSignaturesMock
alias Pleroma.Web.Plugs.HTTPSignaturePlug
import Plug.Conn
import Mox
import Phoenix.Controller, only: [put_format: 2]
import Mock
import Plug.Conn
test "it call HTTPSignatures to check validity if the actor sighed it" do
test "it calls HTTPSignatures to check validity if the actor signed it" do
params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params)
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> put_format("activity+json")
|> HTTPSignaturePlug.call(%{})
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
assert conn.assigns.valid_signature == true
assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> put_format("activity+json")
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert conn.halted == false
end
describe "requires a signature when `authorized_fetch_mode` is enabled" do
setup do
clear_config([:activitypub, :authorized_fetch_mode], true)
params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params) |> put_format("activity+json")
[conn: conn]
end
test "when signature header is present", %{conn: conn} do
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
test "when signature header is present", %{conn: orig_conn} do
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] -> [] end)
assert conn.assigns.valid_signature == false
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
assert called(HTTPSignatures.validate_conn(:_))
end
HTTPSignaturesMock
|> expect(:validate_conn, 2, fn _ -> false end)
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
conn =
orig_conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
assert conn.assigns.valid_signature == false
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
conn =
orig_conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert conn.halted == false
end
test "halts the connection when `signature` header is not present", %{conn: conn} do
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] -> [] end)
conn = HTTPSignaturePlug.call(conn, %{})
assert conn.assigns[:valid_signature] == nil
assert conn.halted == true
@ -81,5 +93,73 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
end
test "exempts specific IPs from `authorized_fetch_mode_exceptions`", %{conn: conn} do
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] ->
["192.168.0.0/24"]
end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
HTTPSignaturesMock
|> expect(:validate_conn, 2, fn _ -> false end)
conn =
conn
|> Map.put(:remote_ip, {192, 168, 0, 1})
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.remote_ip == {192, 168, 0, 1}
assert conn.halted == false
end
end
test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is enabled" do
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:instance, :rejected_instances] ->
[{"mastodon.example.org", "no reason"}]
end)
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
conn =
build_conn(:get, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> put_format("activity+json")
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert conn.halted == true
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:instance, :rejected_instances] ->
[{"mastodon.example.org", "no reason"}]
end)
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
conn =
build_conn(:get, "/doesntmattter", %{"actor" => "http://allowed.example.org/users/admin"})
|> put_req_header(
"signature",
"keyId=\"http://allowed.example.org/users/admin#main-key"
)
|> put_format("activity+json")
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true
assert conn.halted == false
end
end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
alias Pleroma.Web.RichMedia.Card
alias Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
setup do
ConfigMock
@ -82,6 +83,12 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do
assert DateTime.diff(scheduled_at, timestamp_dt) == valid_till
end
test "AWS URL for an image without expiration works" do
og_data = %{"image" => "https://amazonaws.com/image.png"}
assert is_nil(AwsSignedUrl.ttl(og_data, ""))
end
defp construct_s3_url(timestamp, valid_till) do
"https://pleroma.s3.ap-southeast-1.amazonaws.com/sachin%20%281%29%20_a%20-%25%2Aasdasd%20BNN%20bnnn%20.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIBLWWK6RGDQXDLJQ%2F20190716%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=#{timestamp}&X-Amz-Expires=#{valid_till}&X-Amz-Signature=04ffd6b98634f4b1bbabc62e0fac4879093cd54a6eed24fe8eb38e8369526bbf&X-Amz-SignedHeaders=host"
end

View file

@ -76,15 +76,6 @@ defmodule Pleroma.Web.WebFingerTest do
{:ok, _data} = WebFinger.finger(user)
end
test "returns the ActivityPub actor URI and subscribe address for an ActivityPub user with the ld+json mimetype" do
user = "kaniini@gerzilla.de"
{:ok, data} = WebFinger.finger(user)
assert data["ap_id"] == "https://gerzilla.de/channel/kaniini"
assert data["subscribe_address"] == "https://gerzilla.de/follow?f=&url={uri}"
end
test "it work for AP-only user" do
user = "kpherox@mstdn.jp"
@ -99,12 +90,6 @@ defmodule Pleroma.Web.WebFingerTest do
assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"
end
test "it works for friendica" do
user = "lain@squeet.me"
{:ok, _data} = WebFinger.finger(user)
end
test "it gets the xrd endpoint" do
{:ok, template} = WebFinger.find_lrdd_template("social.heldscal.la")
@ -203,5 +188,44 @@ defmodule Pleroma.Web.WebFingerTest do
assert :error = WebFinger.finger("pekorino@pawoo.net")
end
test "prevents spoofing" do
Tesla.Mock.mock(fn
%{
url: "https://gleasonator.com/.well-known/webfinger?resource=acct:alex@gleasonator.com"
} ->
{:ok,
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/webfinger_spoof.json"),
headers: [{"content-type", "application/jrd+json"}]
}}
%{url: "https://gleasonator.com/.well-known/host-meta"} ->
{:ok,
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/gleasonator.com_host_meta")
}}
end)
{:error, _data} = WebFinger.finger("alex@gleasonator.com")
end
end
@tag capture_log: true
test "prevents forgeries" do
Tesla.Mock.mock(fn
%{url: "https://fba.ryona.agency/.well-known/webfinger?resource=acct:graf@fba.ryona.agency"} ->
fake_webfinger =
File.read!("test/fixtures/webfinger/graf-imposter-webfinger.json") |> Jason.decode!()
Tesla.Mock.json(fake_webfinger)
%{url: "https://fba.ryona.agency/.well-known/host-meta"} ->
{:ok, %Tesla.Env{status: 404}}
end)
assert {:error, _} = WebFinger.finger("graf@fba.ryona.agency")
end
end

View file

@ -116,6 +116,7 @@ defmodule Pleroma.DataCase do
Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Test.StaticConfig)
Mox.stub_with(Pleroma.StubbedHTTPSignaturesMock, Pleroma.Test.HTTPSignaturesProxy)
end
def ensure_local_uploader(context) do

View file

@ -1521,6 +1521,120 @@ defmodule HttpRequestMock do
}}
end
def get("https://mastodon.example/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 302,
headers: [{"location", "https://sub.mastodon.example/.well-known/host-meta"}]
}}
end
def get("https://sub.mastodon.example/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-host-meta.xml"
|> File.read!()
|> String.replace("{{domain}}", "sub.mastodon.example")
}}
end
def get(
"https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example",
_,
_,
_
) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-webfinger.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "mastodon.example")
|> String.replace("{{subdomain}}", "sub.mastodon.example"),
headers: [{"content-type", "application/jrd+json"}]
}}
end
def get("https://sub.mastodon.example/users/a", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/masto-user.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "sub.mastodon.example"),
headers: [{"content-type", "application/activity+json"}]
}}
end
def get("https://sub.mastodon.example/users/a/collections/featured", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body:
File.read!("test/fixtures/users_mock/masto_featured.json")
|> String.replace("{{domain}}", "sub.mastodon.example")
|> String.replace("{{nickname}}", "a"),
headers: [{"content-type", "application/activity+json"}]
}}
end
def get("https://pleroma.example/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 302,
headers: [{"location", "https://sub.pleroma.example/.well-known/host-meta"}]
}}
end
def get("https://sub.pleroma.example/.well-known/host-meta", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-host-meta.xml"
|> File.read!()
|> String.replace("{{domain}}", "sub.pleroma.example")
}}
end
def get(
"https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example",
_,
_,
_
) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-webfinger.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "pleroma.example")
|> String.replace("{{subdomain}}", "sub.pleroma.example"),
headers: [{"content-type", "application/jrd+json"}]
}}
end
def get("https://sub.pleroma.example/users/a", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body:
"test/fixtures/webfinger/pleroma-user.json"
|> File.read!()
|> String.replace("{{nickname}}", "a")
|> String.replace("{{domain}}", "sub.pleroma.example"),
headers: [{"content-type", "application/activity+json"}]
}}
end
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}

View file

@ -0,0 +1,9 @@
defmodule Pleroma.Test.HTTPSignaturesProxy do
@behaviour Pleroma.HTTPSignaturesAPI
@impl true
defdelegate validate_conn(conn), to: HTTPSignatures
@impl true
defdelegate signature_for_conn(conn), to: HTTPSignatures
end

View file

@ -28,6 +28,7 @@ Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StaticStubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StubbedHTTPSignaturesMock, for: Pleroma.HTTPSignaturesAPI)
Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)