Merge remote-tracking branch 'origin/develop' into shigusegubu

This commit is contained in:
Henry Jameson 2024-08-29 00:10:59 +03:00
commit c775b4876d
143 changed files with 2064 additions and 1110 deletions

3
.gitignore vendored
View file

@ -63,3 +63,6 @@ pleroma.iml
archive-* archive-*
.gitlab-ci-local .gitlab-ci-local
# Test files should be named *.exs
test/pleroma/**/*.ex

View file

@ -9,6 +9,7 @@ variables: &global_variables
DB_HOST: postgres DB_HOST: postgres
DB_PORT: "5432" DB_PORT: "5432"
MIX_ENV: test MIX_ENV: test
GIT_STRATEGY: fetch
workflow: workflow:
rules: rules:
@ -133,7 +134,7 @@ unit-testing-1.13.4-otp-25:
script: &testing_script script: &testing_script
- mix ecto.create - mix ecto.create
- mix ecto.migrate - mix ecto.migrate
- mix pleroma.test_runner --cover --preload-modules - mix test --cover --preload-modules
coverage: '/^Line total: ([^ ]*%)$/' coverage: '/^Line total: ([^ ]*%)$/'
artifacts: artifacts:
reports: reports:

View file

@ -0,0 +1 @@
Truncate remote user fields, avoids them getting rejected

View file

View file

View file

@ -0,0 +1 @@
Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe`

View file

View file

@ -0,0 +1 @@
Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them.

View file

@ -0,0 +1 @@
Improve the FollowValidator to successfully incoming activities with an errant cc field.

View file

@ -0,0 +1 @@
Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0)

1
changelog.d/mailgun.fix Normal file
View file

@ -0,0 +1 @@
The Swoosh email adapter for Mailgun was missing a new dependency on :multipart

0
changelog.d/mogrify.skip Normal file
View file

View file

View file

@ -0,0 +1 @@
Added MRF.FODirectReply which changes replies to followers-only posts to be direct.

View file

@ -0,0 +1 @@
Added MRF.QuietReply which prevents replies to public posts from being published to the timelines

View file

@ -0,0 +1 @@
Fix 'Setting a marker should mark notifications as read'

View file

@ -0,0 +1 @@
Address case where instance reachability status couldn't be updated

View file

@ -0,0 +1 @@
Remote Fetcher Worker recognizes more permanent failure errors

View file

@ -0,0 +1 @@
StreamerView: Do not leak follows count if hidden

View file

View file

@ -0,0 +1 @@
Update Oban to 2.18

View file

View file

@ -0,0 +1 @@
Imports of blocks, mutes, and follows would retry repeatedly due to incorrect error handling and all work executed in a single job

View file

@ -0,0 +1 @@
Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues.

View file

@ -613,13 +613,6 @@ config :pleroma, Oban,
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
] ]
config :pleroma, :workers,
retries: [
federator_incoming: 5,
federator_outgoing: 5,
search_indexing: 2
]
config :pleroma, Pleroma.Formatter, config :pleroma, Pleroma.Formatter,
class: false, class: false,
rel: "ugc", rel: "ugc",

View file

@ -2013,23 +2013,6 @@ config :pleroma, :config_description, [
} }
] ]
}, },
%{
group: :pleroma,
key: :workers,
type: :group,
description: "Includes custom worker options not interpretable directly by `Oban`",
children: [
%{
key: :retries,
type: {:keyword, :integer},
description: "Max retry attempts for failed jobs, per `Oban` queue",
suggestions: [
federator_incoming: 5,
federator_outgoing: 5
]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: Pleroma.Web.Metadata, key: Pleroma.Web.Metadata,

View file

@ -510,12 +510,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat
- `GET /api/v1/trends`: Returns an empty array, `[]` - `GET /api/v1/trends`: Returns an empty array, `[]`
### Identity proofs
*Added in Mastodon 2.8.0*
- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
### Featured tags ### Featured tags
*Added in Mastodon 3.0.0* *Added in Mastodon 3.0.0*

View file

@ -145,6 +145,9 @@ See [Admin-API](admin_api.md)
## `/api/v1/pleroma/accounts/:id/subscribe` ## `/api/v1/pleroma/accounts/:id/subscribe`
### Subscribe to receive notifications for all statuses posted by a user ### Subscribe to receive notifications for all statuses posted by a user
Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead.
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params:
@ -171,6 +174,9 @@ See [Admin-API](admin_api.md)
## `/api/v1/pleroma/accounts/:id/unsubscribe` ## `/api/v1/pleroma/accounts/:id/unsubscribe`
### Unsubscribe to stop receiving notifications from user statuses ### Unsubscribe to stop receiving notifications from user statuses
Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead.
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params:

View file

@ -295,10 +295,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|> DateTime.from_naive!("Etc/UTC") |> DateTime.from_naive!("Etc/UTC")
|> Timex.shift(days: days) |> Timex.shift(days: days)
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ Pleroma.Workers.PurgeExpiredActivity.enqueue(
activity_id: activity.id, %{
expires_at: expires_at activity_id: activity.id
}) },
scheduled_at: expires_at
)
end) end)
end) end)
|> Stream.run() |> Stream.run()

View file

@ -1,25 +0,0 @@
defmodule Mix.Tasks.Pleroma.TestRunner do
@shortdoc "Retries tests once if they fail"
use Mix.Task
def run(args \\ []) do
case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do
{_, 0} ->
:ok
_ ->
retry(args)
end
end
def retry(args) do
case System.cmd("mix", ["test", "--failed"] ++ args, into: IO.stream(:stdio, :line)) do
{_, 0} ->
:ok
_ ->
exit(1)
end
end
end

View file

@ -25,7 +25,8 @@ defmodule Pleroma.Emails.Mailer do
|> :erlang.term_to_binary() |> :erlang.term_to_binary()
|> Base.encode64() |> Base.encode64()
MailerWorker.enqueue("email", %{"encoded_email" => encoded_email, "config" => config}) MailerWorker.new(%{"op" => "email", "encoded_email" => encoded_email, "config" => config})
|> Oban.insert()
end end
@doc "callback to perform send email from queue" @doc "callback to perform send email from queue"

View file

@ -133,10 +133,13 @@ defmodule Pleroma.Filter do
defp maybe_add_expires_at(changeset, _), do: changeset defp maybe_add_expires_at(changeset, _), do: changeset
defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do
Pleroma.Workers.PurgeExpiredFilter.enqueue(%{ Pleroma.Workers.PurgeExpiredFilter.new(
filter_id: filter.id, %{
expires_at: DateTime.from_naive!(expires_at, "Etc/UTC") filter_id: filter.id
}) },
scheduled_at: DateTime.from_naive!(expires_at, "Etc/UTC")
)
|> Oban.insert()
end end
defp maybe_add_expiration_job(_), do: {:ok, nil} defp maybe_add_expiration_job(_), do: {:ok, nil}

View file

@ -297,7 +297,8 @@ defmodule Pleroma.Instances.Instance do
all of those users' activities and notifications. all of those users' activities and notifications.
""" """
def delete_users_and_activities(host) when is_binary(host) do def delete_users_and_activities(host) when is_binary(host) do
DeleteWorker.enqueue("delete_instance", %{"host" => host}) DeleteWorker.new(%{"op" => "delete_instance", "host" => host})
|> Oban.insert()
end end
def perform(:delete_instance, host) when is_binary(host) do def perform(:delete_instance, host) when is_binary(host) do

View file

@ -52,11 +52,14 @@ defmodule Pleroma.MFA.Token do
@spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()} @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def create(user, authorization \\ nil) do def create(user, authorization \\ nil) do
with {:ok, token} <- do_create(user, authorization) do with {:ok, token} <- do_create(user, authorization) do
Pleroma.Workers.PurgeExpiredToken.enqueue(%{ Pleroma.Workers.PurgeExpiredToken.new(
token_id: token.id, %{
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), token_id: token.id,
mod: __MODULE__ mod: __MODULE__
}) },
scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC")
)
|> Oban.insert()
{:ok, token} {:ok, token}
end end

View file

@ -255,7 +255,8 @@ defmodule Pleroma.Object do
@spec cleanup_attachments(boolean(), Object.t()) :: @spec cleanup_attachments(boolean(), Object.t()) ::
{:ok, Oban.Job.t() | nil} {:ok, Oban.Job.t() | nil}
def cleanup_attachments(true, %Object{} = object) do def cleanup_attachments(true, %Object{} = object) do
AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{"object" => object}) AttachmentsCleanupWorker.new(%{"op" => "cleanup_attachments", "object" => object})
|> Oban.insert()
end end
def cleanup_attachments(_, _), do: {:ok, nil} def cleanup_attachments(_, _), do: {:ok, nil}

View file

@ -73,50 +73,22 @@ defmodule Pleroma.Object.Fetcher do
{:object, data, Object.normalize(activity, fetch: false)} do {:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object} {:ok, object}
else else
{:allowed_depth, false} = e ->
log_fetch_error(id, e)
{:error, :allowed_depth}
{:containment, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:transmogrifier, {:error, {:reject, reason}}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, {:reject, reason}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:object, data, nil} ->
reinject_object(%Object{}, data)
{:normalize, object = %Object{}} -> {:normalize, object = %Object{}} ->
{:ok, object} {:ok, object}
{:fetch_object, %Object{} = object} -> {:fetch_object, %Object{} = object} ->
{:ok, object} {:ok, object}
{:fetch, {:error, reason}} = e -> {:object, data, nil} ->
log_fetch_error(id, e) reinject_object(%Object{}, data)
{:error, reason}
e -> e ->
log_fetch_error(id, e) Logger.metadata(object: id)
{:error, e} Logger.error("Object rejected while fetching #{id} #{inspect(e)}")
e
end end
end end
defp log_fetch_error(id, error) do
Logger.metadata(object: id)
Logger.error("Object rejected while fetching #{id} #{inspect(error)}")
end
defp prepare_activity_params(data) do defp prepare_activity_params(data) do
%{ %{
"type" => "Create", "type" => "Create",

View file

@ -2,11 +2,13 @@ defmodule Pleroma.Search do
alias Pleroma.Workers.SearchIndexingWorker alias Pleroma.Workers.SearchIndexingWorker
def add_to_index(%Pleroma.Activity{id: activity_id}) do def add_to_index(%Pleroma.Activity{id: activity_id}) do
SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id}) SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id})
|> Oban.insert()
end end
def remove_from_index(%Pleroma.Object{id: object_id}) do def remove_from_index(%Pleroma.Object{id: object_id}) do
SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id}) SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id})
|> Oban.insert()
end end
def search(query, options) do def search(query, options) do

View file

@ -463,6 +463,7 @@ defmodule Pleroma.User do
def remote_user_changeset(struct \\ %User{local: false}, params) do def remote_user_changeset(struct \\ %User{local: false}, params) do
bio_limit = Config.get([:instance, :user_bio_length], 5000) bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100) name_limit = Config.get([:instance, :user_name_length], 100)
fields_limit = Config.get([:instance, :max_remote_account_fields], 0)
name = name =
case params[:name] do case params[:name] do
@ -476,6 +477,7 @@ defmodule Pleroma.User do
|> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now()) |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
|> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit) |> truncate_if_exists(:bio, bio_limit)
|> Map.update(:fields, [], &Enum.take(&1, fields_limit))
|> truncate_fields_param() |> truncate_fields_param()
|> fix_follower_address() |> fix_follower_address()
@ -736,7 +738,8 @@ defmodule Pleroma.User do
end end
def force_password_reset_async(user) do def force_password_reset_async(user) do
BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id}) BackgroundWorker.new(%{"op" => "force_password_reset", "user_id" => user.id})
|> Oban.insert()
end end
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@ -1218,7 +1221,8 @@ defmodule Pleroma.User do
def update_and_set_cache(changeset) do def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
if get_change(changeset, :raw_fields) do if get_change(changeset, :raw_fields) do
BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id}) BackgroundWorker.new(%{"op" => "verify_fields_links", "user_id" => user.id})
|> Oban.insert()
end end
set_cache(user) set_cache(user)
@ -1589,11 +1593,11 @@ defmodule Pleroma.User do
)) || )) ||
{:ok, nil} do {:ok, nil} do
if duration > 0 do if duration > 0 do
Pleroma.Workers.MuteExpireWorker.enqueue( Pleroma.Workers.MuteExpireWorker.new(
"unmute_user", %{"op" => "unmute_user", "muter_id" => muter.id, "mutee_id" => mutee.id},
%{"muter_id" => muter.id, "mutee_id" => mutee.id},
scheduled_at: expires_at scheduled_at: expires_at
) )
|> Oban.insert()
end end
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
@ -1836,7 +1840,8 @@ defmodule Pleroma.User do
defp maybe_filter_on_ap_id(query, _ap_ids), do: query defp maybe_filter_on_ap_id(query, _ap_ids), do: query
def set_activation_async(user, status \\ true) do def set_activation_async(user, status \\ true) do
BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status}) BackgroundWorker.new(%{"op" => "user_activation", "user_id" => user.id, "status" => status})
|> Oban.insert()
end end
@spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@ -1983,7 +1988,9 @@ defmodule Pleroma.User do
def delete(%User{} = user) do def delete(%User{} = user) do
# Purge the user immediately # Purge the user immediately
purge(user) purge(user)
DeleteWorker.enqueue("delete_user", %{"user_id" => user.id})
DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id})
|> Oban.insert()
end end
# *Actually* delete the user from the DB # *Actually* delete the user from the DB

View file

@ -5,6 +5,7 @@
defmodule Pleroma.User.Import do defmodule Pleroma.User.Import do
use Ecto.Schema use Ecto.Schema
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Workers.BackgroundWorker alias Pleroma.Workers.BackgroundWorker
@ -12,74 +13,99 @@ defmodule Pleroma.User.Import do
require Logger require Logger
@spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()}
def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do def perform(:mute_import, %User{} = user, actor) do
Enum.map( with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
identifiers, {_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},
fn identifier -> {:ok, _} <- User.mute(user, muted_user) do
with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), {:ok, muted_user}
{:ok, _} <- User.mute(user, muted_user) do else
muted_user {:existing_mute, true} -> :ok
else error -> handle_error(:mutes_import, actor, error)
error -> handle_error(:mutes_import, identifier, error) end
end
end
)
end end
def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do def perform(:block_import, %User{} = user, actor) do
Enum.map( with {:ok, %User{} = blocked} <- User.get_or_fetch(actor),
identifiers, {_, false} <- {:existing_block, User.blocks_user?(user, blocked)},
fn identifier -> {:ok, _block} <- CommonAPI.block(blocked, user) do
with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), {:ok, blocked}
{:ok, _block} <- CommonAPI.block(blocked, blocker) do else
blocked {:existing_block, true} -> :ok
else error -> handle_error(:blocks_import, actor, error)
error -> handle_error(:blocks_import, identifier, error) end
end
end
)
end end
def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do def perform(:follow_import, %User{} = user, actor) do
Enum.map( with {:ok, %User{} = followed} <- User.get_or_fetch(actor),
identifiers, {_, false} <- {:existing_follow, User.following?(user, followed)},
fn identifier -> {:ok, user, followed} <- User.maybe_direct_follow(user, followed),
with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), {:ok, _, _, _} <- CommonAPI.follow(followed, user) do
{:ok, follower, followed} <- User.maybe_direct_follow(follower, followed), {:ok, followed}
{:ok, _, _, _} <- CommonAPI.follow(followed, follower) do else
followed {:existing_follow, true} -> :ok
else error -> handle_error(:follow_import, actor, error)
error -> handle_error(:follow_import, identifier, error) end
end
end
)
end end
def perform(_, _, _), do: :ok
defp handle_error(op, user_id, error) do defp handle_error(op, user_id, error) do
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
error error
end end
def blocks_import(%User{} = blocker, [_ | _] = identifiers) do def blocks_import(%User{} = user, [_ | _] = actors) do
BackgroundWorker.enqueue( jobs =
"blocks_import", Repo.checkout(fn ->
%{"user_id" => blocker.id, "identifiers" => identifiers} Enum.reduce(actors, [], fn actor, acc ->
) {:ok, job} =
BackgroundWorker.new(%{
"op" => "block_import",
"user_id" => user.id,
"actor" => actor
})
|> Oban.insert()
acc ++ [job]
end)
end)
{:ok, jobs}
end end
def follow_import(%User{} = follower, [_ | _] = identifiers) do def follows_import(%User{} = user, [_ | _] = actors) do
BackgroundWorker.enqueue( jobs =
"follow_import", Repo.checkout(fn ->
%{"user_id" => follower.id, "identifiers" => identifiers} Enum.reduce(actors, [], fn actor, acc ->
) {:ok, job} =
BackgroundWorker.new(%{
"op" => "follow_import",
"user_id" => user.id,
"actor" => actor
})
|> Oban.insert()
acc ++ [job]
end)
end)
{:ok, jobs}
end end
def mutes_import(%User{} = user, [_ | _] = identifiers) do def mutes_import(%User{} = user, [_ | _] = actors) do
BackgroundWorker.enqueue( jobs =
"mutes_import", Repo.checkout(fn ->
%{"user_id" => user.id, "identifiers" => identifiers} Enum.reduce(actors, [], fn actor, acc ->
) {:ok, job} =
BackgroundWorker.new(%{
"op" => "mute_import",
"user_id" => user.id,
"actor" => actor
})
|> Oban.insert()
acc ++ [job]
end)
end)
{:ok, jobs}
end end
end end

View file

@ -222,10 +222,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%{data: %{"expires_at" => %DateTime{} = expires_at}} = activity %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
) do ) do
with {:ok, _job} <- with {:ok, _job} <-
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ Pleroma.Workers.PurgeExpiredActivity.enqueue(
activity_id: activity.id, %{
expires_at: expires_at activity_id: activity.id
}) do },
scheduled_at: expires_at
) do
{:ok, activity} {:ok, activity}
end end
end end
@ -446,10 +448,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
_ <- notify_and_stream(activity) do _ <- notify_and_stream(activity) do
maybe_federate(activity) maybe_federate(activity)
BackgroundWorker.enqueue("move_following", %{ BackgroundWorker.new(%{
"op" => "move_following",
"origin_id" => origin.id, "origin_id" => origin.id,
"target_id" => target.id "target_id" => target.id
}) })
|> Oban.insert()
{:ok, activity} {:ok, activity}
else else
@ -1797,10 +1801,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# enqueue a task to fetch all pinned objects # enqueue a task to fetch all pinned objects
Enum.each(pins, fn {ap_id, _} -> Enum.each(pins, fn {ap_id, _} ->
if is_nil(Object.get_cached_by_ap_id(ap_id)) do if is_nil(Object.get_cached_by_ap_id(ap_id)) do
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ Pleroma.Workers.RemoteFetcherWorker.new(%{
"op" => "fetch_remote",
"id" => ap_id, "id" => ap_id,
"depth" => 1 "depth" => 1
}) })
|> Oban.insert()
end end
end) end)
end end

View file

@ -63,20 +63,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
end end
@impl true @impl true
def filter(%{"type" => "Follow", "actor" => actor_id} = message) do def filter(%{"type" => "Follow", "actor" => actor_id} = activity) do
%User{} = actor = normalize_by_ap_id(actor_id) %User{} = actor = normalize_by_ap_id(actor_id)
score = determine_if_followbot(actor) score = determine_if_followbot(actor)
if score < 0.8 || bot_allowed?(message, actor) do if score < 0.8 || bot_allowed?(activity, actor) do
{:ok, message} {:ok, activity}
else else
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"} {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -29,17 +29,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
defp contains_links?(_), do: false defp contains_links?(_), do: false
@impl true @impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do def filter(%{"type" => "Create", "actor" => actor, "object" => object} = activity) do
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor), with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)}, {:contains_links, true} <- {:contains_links, contains_links?(object)},
{:old_user, true} <- {:old_user, old_user?(u)} do {:old_user, true} <- {:old_user, old_user?(u)} do
{:ok, message} {:ok, activity}
else else
{:ok, %User{local: true}} -> {:ok, %User{local: true}} ->
{:ok, message} {:ok, activity}
{:contains_links, false} -> {:contains_links, false} ->
{:ok, message} {:ok, activity}
{:old_user, false} -> {:old_user, false} ->
{:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"} {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
end end
# in all other cases, pass through # in all other cases, pass through
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -22,11 +22,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
end end
# copied from HellthreadPolicy # copied from HellthreadPolicy
defp get_recipient_count(message) do defp get_recipient_count(activity) do
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (activity["to"] || []) ++ (activity["cc"] || [])
follower_collection = follower_collection =
User.get_cached_by_ap_id(message["actor"] || message["attributedTo"]).follower_address User.get_cached_by_ap_id(activity["actor"] || activity["attributedTo"]).follower_address
if Enum.member?(recipients, Pleroma.Constants.as_public()) do if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients = recipients =
@ -80,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
end end
# in all other cases, pass through # in all other cases, pass through
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -38,18 +38,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
@query_timeout 500 @query_timeout 500
@impl true @impl true
def filter(%{"actor" => actor} = object) do def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_rbl(actor_info, object) do with {:ok, activity} <- check_rbl(actor_info, activity) do
{:ok, object} {:ok, activity}
else else
_ -> {:reject, "[DNSRBLPolicy]"} _ -> {:reject, "[DNSRBLPolicy]"}
end end
end end
@impl true @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do
@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
} }
end end
defp check_rbl(%{host: actor_host}, object) do defp check_rbl(%{host: actor_host}, activity) do
with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()), with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()),
zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do
query = query =
@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
rbl_response = rblquery(query) rbl_response = rblquery(query)
if Enum.empty?(rbl_response) do if Enum.empty?(rbl_response) do
{:ok, object} {:ok, activity}
else else
Task.start(fn -> Task.start(fn ->
reason = reason =
@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
:error :error
end end
else else
_ -> {:ok, object} _ -> {:ok, activity}
end end
end end

View file

@ -8,9 +8,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(object) do def filter(activity) do
Logger.debug("REJECTING #{inspect(object)}") Logger.debug("REJECTING #{inspect(activity)}")
{:reject, object} {:reject, activity}
end end
@impl true @impl true

View file

@ -28,11 +28,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], []) Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], [])
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def history_awareness, do: :manual def history_awareness, do: :manual
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message) def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = activity)
when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do
with {:ok, object} <- with {:ok, object} <-
Updater.do_with_history(object, fn object -> Updater.do_with_history(object, fn object ->
@ -42,13 +42,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
Updater.do_with_history(object, fn object -> Updater.do_with_history(object, fn object ->
{:ok, process_remove(object, :shortcode, config_remove_shortcode())} {:ok, process_remove(object, :shortcode, config_remove_shortcode())}
end), end),
activity <- Map.put(message, "object", object), activity <- Map.put(activity, "object", object),
activity <- maybe_delist(activity) do activity <- maybe_delist(activity) do
{:ok, activity} {:ok, activity}
end end
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
with object <- process_remove(object, :url, config_remove_url()), with object <- process_remove(object, :url, config_remove_url()),
object <- process_remove(object, :shortcode, config_remove_shortcode()) do object <- process_remove(object, :shortcode, config_remove_shortcode()) do
@ -56,7 +56,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
end end
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(%{"type" => "EmojiReact"} = object) do def filter(%{"type" => "EmojiReact"} = object) do
with {:ok, _} <- with {:ok, _} <-
matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
@ -67,9 +67,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
end end
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(message) do def filter(activity) do
{:ok, message} {:ok, activity}
end end
defp match_string?(string, pattern) when is_binary(pattern) do defp match_string?(string, pattern) when is_binary(pattern) do
@ -214,7 +214,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
) )
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def describe do def describe do
mrf_emoji = mrf_emoji =
Pleroma.Config.get(:mrf_emoji, []) Pleroma.Config.get(:mrf_emoji, [])
@ -226,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
{:ok, %{mrf_emoji: mrf_emoji}} {:ok, %{mrf_emoji: mrf_emoji}}
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def config_description do def config_description do
%{ %{
key: :mrf_emoji, key: :mrf_emoji,
@ -239,7 +239,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :remove_url, key: :remove_url,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. A list of patterns which result in emoji whose URL matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,
@ -249,7 +249,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :remove_shortcode, key: :remove_shortcode,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. A list of patterns which result in emoji whose shortcode matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,
@ -259,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :federated_timeline_removal_url, key: :federated_timeline_removal_url,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. A list of patterns which result in activity with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,
@ -269,7 +269,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :federated_timeline_removal_shortcode, key: :federated_timeline_removal_shortcode,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. A list of patterns which result in activities with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,

View file

@ -29,19 +29,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
def filter_by_summary(_in_reply_to, child), do: child def filter_by_summary(_in_reply_to, child), do: child
def filter(%{"type" => type, "object" => child_object} = object) def filter(%{"type" => type, "object" => object} = activity)
when type in ["Create", "Update"] and is_map(child_object) do when type in ["Create", "Update"] and is_map(object) do
child = child =
child_object["inReplyTo"] object["inReplyTo"]
|> Object.normalize(fetch: false) |> Object.normalize(fetch: false)
|> filter_by_summary(child_object) |> filter_by_summary(object)
object = Map.put(object, "object", child) activity = Map.put(activity, "object", child)
{:ok, object} {:ok, activity}
end end
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}
end end

View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.FODirectReply do
@moduledoc """
FODirectReply alters the scope of replies to activities which are Followers Only to be Direct. The purpose of this policy is to prevent broken threads for followers of the reply author because their response was to a user that they are not also following.
"""
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(
%{
"type" => "Create",
"to" => to,
"object" => %{
"actor" => actor,
"type" => "Note",
"inReplyTo" => in_reply_to
}
} = activity
) do
with true <- is_binary(in_reply_to),
%User{follower_address: followers_collection, local: true} <- User.get_by_ap_id(actor),
%Object{} = in_reply_to_object <- Object.get_by_ap_id(in_reply_to),
"private" <- Visibility.get_visibility(in_reply_to_object) do
direct_to = to -- [followers_collection]
updated_activity =
activity
|> Map.put("cc", [])
|> Map.put("to", direct_to)
|> Map.put("directMessage", true)
|> put_in(["object", "cc"], [])
|> put_in(["object", "to"], direct_to)
{:ok, updated_activity}
else
_ -> {:ok, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
end

View file

@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
require Logger require Logger
@impl true @impl true
def filter(message) do def filter(activity) do
with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]), with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
%User{actor_type: "Service"} = follower <- %User{actor_type: "Service"} = follower <-
User.get_cached_by_nickname(follower_nickname), User.get_cached_by_nickname(follower_nickname),
%{"type" => "Create", "object" => %{"type" => "Note"}} <- message do %{"type" => "Create", "object" => %{"type" => "Note"}} <- activity do
try_follow(follower, message) try_follow(follower, activity)
else else
nil -> nil ->
Logger.warning( Logger.warning(
@ -24,17 +24,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
account does not exist, or the account is not correctly configured as a bot." account does not exist, or the account is not correctly configured as a bot."
) )
{:ok, message} {:ok, activity}
_ -> _ ->
{:ok, message} {:ok, activity}
end end
end end
defp try_follow(follower, message) do defp try_follow(follower, activity) do
to = Map.get(message, "to", []) to = Map.get(activity, "to", [])
cc = Map.get(message, "cc", []) cc = Map.get(activity, "cc", [])
actor = [message["actor"]] actor = [activity["actor"]]
Enum.concat([to, cc, actor]) Enum.concat([to, cc, actor])
|> List.flatten() |> List.flatten()
@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
end end
end) end)
{:ok, message} {:ok, activity}
end end
@impl true @impl true

View file

@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
"cc" => cc, "cc" => cc,
"actor" => actor, "actor" => actor,
"object" => object "object" => object
} = message } = activity
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
isbot = check_if_bot(user) isbot = check_if_bot(user)
@ -36,20 +36,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Map.put("object", object) |> Map.put("object", object)
{:ok, message} {:ok, activity}
else else
{:ok, message} {:ok, activity}
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
end end
@impl true @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -79,18 +79,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
%{ %{
"type" => type, "type" => type,
"object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to} "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
} = object } = activity
) )
when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do
# image-only posts from pleroma apparently reach this MRF without the content field # image-only posts from pleroma apparently reach this MRF without the content field
content = object["object"]["content"] || "" content = activity["object"]["content"] || ""
# Get the replied-to user for sorting # Get the replied-to user for sorting
replied_to_user = get_replied_to_user(object["object"]) replied_to_user = get_replied_to_user(activity["object"])
mention_users = mention_users =
to to
|> clean_recipients(object) |> clean_recipients(activity)
|> Enum.map(&User.get_cached_by_ap_id/1) |> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
|> sort_replied_user(replied_to_user) |> sort_replied_user(replied_to_user)
@ -126,11 +126,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
content content
end end
{:ok, put_in(object["object"]["content"], content)} {:ok, put_in(activity["object"]["content"], content)}
end end
@impl true @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
alias Pleroma.Object alias Pleroma.Object
@moduledoc """ @moduledoc """
Reject, TWKN-remove or Set-Sensitive messages with specific hashtags (without the leading #) Reject, TWKN-remove or Set-Sensitive activities with specific hashtags (without the leading #)
Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists. Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
""" """
@ -19,40 +19,40 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
@impl true @impl true
def history_awareness, do: :manual def history_awareness, do: :manual
defp check_reject(message, hashtags) do defp check_reject(activity, hashtags) do
if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
{:reject, "[HashtagPolicy] Matches with rejected keyword"} {:reject, "[HashtagPolicy] Matches with rejected keyword"}
else else
{:ok, message} {:ok, activity}
end end
end end
defp check_ftl_removal(%{"to" => to} = message, hashtags) do defp check_ftl_removal(%{"to" => to} = activity, hashtags) do
if Pleroma.Constants.as_public() in to and if Pleroma.Constants.as_public() in to and
Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match -> Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
match in hashtags match in hashtags
end) do end) do
to = List.delete(to, Pleroma.Constants.as_public()) to = List.delete(to, Pleroma.Constants.as_public())
cc = [Pleroma.Constants.as_public() | message["cc"] || []] cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc) |> Kernel.put_in(["object", "cc"], cc)
{:ok, message} {:ok, activity}
else else
{:ok, message} {:ok, activity}
end end
end end
defp check_ftl_removal(message, _hashtags), do: {:ok, message} defp check_ftl_removal(activity, _hashtags), do: {:ok, activity}
defp check_sensitive(message) do defp check_sensitive(activity) do
{:ok, new_object} = {:ok, new_object} =
Object.Updater.do_with_history(message["object"], fn object -> Object.Updater.do_with_history(activity["object"], fn object ->
hashtags = Object.hashtags(%Object{data: object}) hashtags = Object.hashtags(%Object{data: object})
if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
@ -62,11 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
end end
end) end)
{:ok, Map.put(message, "object", new_object)} {:ok, Map.put(activity, "object", new_object)}
end end
@impl true @impl true
def filter(%{"type" => type, "object" => object} = message) when type in ["Create", "Update"] do def filter(%{"type" => type, "object" => object} = activity)
when type in ["Create", "Update"] do
history_items = history_items =
with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do
items items
@ -82,23 +83,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags
if hashtags != [] do if hashtags != [] do
with {:ok, message} <- check_reject(message, hashtags), with {:ok, activity} <- check_reject(activity, hashtags),
{:ok, message} <- {:ok, activity} <-
(if type == "Create" do (if type == "Create" do
check_ftl_removal(message, hashtags) check_ftl_removal(activity, hashtags)
else else
{:ok, message} {:ok, activity}
end), end),
{:ok, message} <- check_sensitive(message) do {:ok, activity} <- check_sensitive(activity) do
{:ok, message} {:ok, activity}
end end
else else
{:ok, message} {:ok, activity}
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do
@ -120,21 +121,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
%{ %{
key: :reject, key: :reject,
type: {:list, :string}, type: {:list, :string},
description: "A list of hashtags which result in message being rejected.", description: "A list of hashtags which result in the activity being rejected.",
suggestions: ["foo"] suggestions: ["foo"]
}, },
%{ %{
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).", "A list of hashtags which result in the activity being removed from federated timelines (a.k.a unlisted).",
suggestions: ["foo"] suggestions: ["foo"]
}, },
%{ %{
key: :sensitive, key: :sensitive,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)", "A list of hashtags which result in the activity being set as sensitive (a.k.a NSFW/R-18)",
suggestions: ["nsfw", "r18"] suggestions: ["nsfw", "r18"]
} }
] ]

View file

@ -7,54 +7,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
require Pleroma.Constants require Pleroma.Constants
@moduledoc "Block messages with too much mentions (configurable)" @moduledoc "Block activities with too much mentions (configurable)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp delist_message(message, threshold) when threshold > 0 do defp delist_activity(activity, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address
to = message["to"] || [] to = activity["to"] || []
cc = message["cc"] || [] cc = activity["cc"] || []
follower_collection? = Enum.member?(to ++ cc, follower_collection) follower_collection? = Enum.member?(to ++ cc, follower_collection)
message = activity =
case get_recipient_count(message) do case get_recipient_count(activity) do
{:public, recipients} {:public, recipients}
when follower_collection? and recipients > threshold -> when follower_collection? and recipients > threshold ->
message activity
|> Map.put("to", [follower_collection]) |> Map.put("to", [follower_collection])
|> Map.put("cc", [Pleroma.Constants.as_public()]) |> Map.put("cc", [Pleroma.Constants.as_public()])
{:public, recipients} when recipients > threshold -> {:public, recipients} when recipients > threshold ->
message activity
|> Map.put("to", []) |> Map.put("to", [])
|> Map.put("cc", [Pleroma.Constants.as_public()]) |> Map.put("cc", [Pleroma.Constants.as_public()])
_ -> _ ->
message activity
end end
{:ok, message} {:ok, activity}
end end
defp delist_message(message, _threshold), do: {:ok, message} defp delist_activity(activity, _threshold), do: {:ok, activity}
defp reject_message(message, threshold) when threshold > 0 do defp reject_activity(activity, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do with {_, recipients} <- get_recipient_count(activity) do
if recipients > threshold do if recipients > threshold do
{:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"} {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else else
{:ok, message} {:ok, activity}
end end
end end
end end
defp reject_message(message, _threshold), do: {:ok, message} defp reject_activity(activity, _threshold), do: {:ok, activity}
defp get_recipient_count(message) do defp get_recipient_count(activity) do
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (activity["to"] || []) ++ (activity["cc"] || [])
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address
if Enum.member?(recipients, Pleroma.Constants.as_public()) do if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients = recipients =
@ -73,7 +73,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
end end
@impl true @impl true
def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message) def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = activity)
when object_type in ~w{Note Article} do when object_type in ~w{Note Article} do
reject_threshold = reject_threshold =
Pleroma.Config.get( Pleroma.Config.get(
@ -83,16 +83,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
with {:ok, message} <- reject_message(message, reject_threshold), with {:ok, activity} <- reject_activity(activity, reject_threshold),
{:ok, message} <- delist_message(message, delist_threshold) do {:ok, activity} <- delist_activity(activity, delist_threshold) do
{:ok, message} {:ok, activity}
else else
e -> e e -> e
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, def describe,
@ -104,13 +104,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
key: :mrf_hellthread, key: :mrf_hellthread,
related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
label: "MRF Hellthread", label: "MRF Hellthread",
description: "Block messages with excessive user mentions", description: "Block activities with excessive user mentions",
children: [ children: [
%{ %{
key: :delist_threshold, key: :delist_threshold,
type: :integer, type: :integer,
description: description:
"Number of mentioned users after which the message gets removed from timelines and" <> "Number of mentioned users after which the activity gets removed from timelines and" <>
"disables notifications. Set to 0 to disable.", "disables notifications. Set to 0 to disable.",
suggestions: [10] suggestions: [10]
}, },
@ -118,7 +118,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
key: :reject_threshold, key: :reject_threshold,
type: :integer, type: :integer,
description: description:
"Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.", "Number of mentioned users after which the activity gets rejected. Set to 0 to disable.",
suggestions: [20] suggestions: [20]
} }
] ]

View file

@ -48,12 +48,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
end end
@impl true @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def history_awareness, do: :auto def history_awareness, do: :auto
@impl true @impl true

View file

@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
alias Pleroma.Web.ActivityPub.MRF.Utils alias Pleroma.Web.ActivityPub.MRF.Utils
@moduledoc "Reject or Word-Replace messages with a keyword or regex" @moduledoc "Reject or Word-Replace activities with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@ -25,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|> Enum.join("\n") |> Enum.join("\n")
end end
defp check_reject(%{"object" => %{} = object} = message) do defp check_reject(%{"object" => %{} = object} = activity) do
with {:ok, _new_object} <- with {:ok, _new_object} <-
Pleroma.Object.Updater.do_with_history(object, fn object -> Pleroma.Object.Updater.do_with_history(object, fn object ->
payload = object_payload(object) payload = object_payload(object)
@ -35,16 +35,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end) do end) do
{:reject, "[KeywordPolicy] Matches with rejected keyword"} {:reject, "[KeywordPolicy] Matches with rejected keyword"}
else else
{:ok, message} {:ok, activity}
end end
end) do end) do
{:ok, message} {:ok, activity}
else else
e -> e e -> e
end end
end end
defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = message) do defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = activity) do
check_keyword = fn object -> check_keyword = fn object ->
payload = object_payload(object) payload = object_payload(object)
@ -67,24 +67,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Pleroma.Constants.as_public() in to and should_delist?.(object) do if Pleroma.Constants.as_public() in to and should_delist?.(object) do
to = List.delete(to, Pleroma.Constants.as_public()) to = List.delete(to, Pleroma.Constants.as_public())
cc = [Pleroma.Constants.as_public() | message["cc"] || []] cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
{:ok, message} {:ok, activity}
else else
{:ok, message} {:ok, activity}
end end
end end
defp check_ftl_removal(message) do defp check_ftl_removal(activity) do
{:ok, message} {:ok, activity}
end end
defp check_replace(%{"object" => %{} = object} = message) do defp check_replace(%{"object" => %{} = object} = activity) do
replace_kw = fn object -> replace_kw = fn object ->
["content", "name", "summary"] ["content", "name", "summary"]
|> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end) |> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
@ -103,18 +103,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw) {:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw)
message = Map.put(message, "object", object) activity = Map.put(activity, "object", object)
{:ok, message} {:ok, activity}
end end
@impl true @impl true
def filter(%{"type" => type, "object" => %{"content" => _content}} = message) def filter(%{"type" => type, "object" => %{"content" => _content}} = activity)
when type in ["Create", "Update"] do when type in ["Create", "Update"] do
with {:ok, message} <- check_reject(message), with {:ok, activity} <- check_reject(activity),
{:ok, message} <- check_ftl_removal(message), {:ok, activity} <- check_ftl_removal(activity),
{:ok, message} <- check_replace(message) do {:ok, activity} <- check_replace(activity) do
{:ok, message} {:ok, activity}
else else
{:reject, nil} -> {:reject, "[KeywordPolicy] "} {:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, _} = e -> e {:reject, _} = e -> e
@ -123,7 +123,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do
@ -154,13 +154,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
label: "MRF Keyword", label: "MRF Keyword",
description: description:
"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).", "Reject or Word-Replace activities matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
children: [ children: [
%{ %{
key: :reject, key: :reject,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in message being rejected. A list of patterns which result in the activity being rejected.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,
@ -170,7 +170,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: {:list, :string}, type: {:list, :string},
description: """ description: """
A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). A list of patterns which result in the activity being removed from federated timelines (a.k.a unlisted).
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""", """,

View file

@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
HTTP.get(url, [], http_client_opts) HTTP.get(url, [], http_client_opts)
end end
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do defp preload(%{"object" => %{"attachment" => attachments}} = _activity) do
Enum.each(attachments, fn Enum.each(attachments, fn
%{"url" => url} when is_list(url) -> %{"url" => url} when is_list(url) ->
url url
@ -49,15 +49,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
end end
@impl true @impl true
def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = message) def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = activity)
when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do
preload(message) preload(activity)
{:ok, message} {:ok, activity}
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -3,25 +3,25 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@moduledoc "Block messages which mention a user" @moduledoc "Block activities which mention a user"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(%{"type" => "Create"} = message) do def filter(%{"type" => "Create"} = activity) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (activity["to"] || []) ++ (activity["cc"] || [])
if rejected_mention = if rejected_mention =
Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"} {:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else else
{:ok, message} {:ok, activity}
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}
@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
key: :mrf_mention, key: :mrf_mention,
related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
label: "MRF Mention", label: "MRF Mention",
description: "Block messages which mention a specific user", description: "Block activities which mention a specific user",
children: [ children: [
%{ %{
key: :actors, key: :actors,

View file

@ -9,20 +9,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
@impl true @impl true
def filter(%{"actor" => actor} = object) do def filter(%{"actor" => actor} = activity) do
with true <- local?(actor), with true <- local?(actor),
true <- eligible_type?(object), true <- eligible_type?(activity),
true <- note?(object), true <- note?(activity),
false <- has_attachment?(object), false <- has_attachment?(activity),
true <- only_mentions?(object) do true <- only_mentions?(activity) do
{:reject, "[NoEmptyPolicy]"} {:reject, "[NoEmptyPolicy]"}
else else
_ -> _ ->
{:ok, object} {:ok, activity}
end end
end end
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
defp local?(actor) do defp local?(actor) do
if actor |> String.starts_with?("#{Endpoint.url()}") do if actor |> String.starts_with?("#{Endpoint.url()}") do

View file

@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(object) do def filter(activity) do
{:ok, object} {:ok, activity}
end end
@impl true @impl true

View file

@ -13,15 +13,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
def filter( def filter(
%{ %{
"type" => type, "type" => type,
"object" => %{"content" => content, "attachment" => _} = _child_object "object" => %{"content" => content, "attachment" => _} = _object
} = object } = activity
) )
when type in ["Create", "Update"] and content in [".", "<p>.</p>"] do when type in ["Create", "Update"] and content in [".", "<p>.</p>"] do
{:ok, put_in(object, ["object", "content"], "")} {:ok, put_in(activity, ["object", "content"], "")}
end end
@impl true @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -12,20 +12,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
def history_awareness, do: :auto def history_awareness, do: :auto
@impl true @impl true
def filter(%{"type" => type, "object" => child_object} = object) def filter(%{"type" => type, "object" => object} = activity)
when type in ["Create", "Update"] do when type in ["Create", "Update"] do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
content = content =
child_object["content"] object["content"]
|> HTML.filter_tags(scrub_policy) |> HTML.filter_tags(scrub_policy)
object = put_in(object, ["object", "content"], content) activity = put_in(activity, ["object", "content"], content)
{:ok, object} {:ok, activity}
end end
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -122,52 +122,52 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
end end
end end
def check_object_nsfw(%{"object" => %{} = child_object} = object) do def check_object_nsfw(%{"object" => %{} = object} = activity) do
case check_object_nsfw(child_object) do case check_object_nsfw(object) do
{:sfw, _} -> {:sfw, object} {:sfw, _} -> {:sfw, activity}
{:nsfw, _} -> {:nsfw, object} {:nsfw, _} -> {:nsfw, activity}
end end
end end
def check_object_nsfw(object), do: {:sfw, object} def check_object_nsfw(object), do: {:sfw, object}
@impl true @impl true
def filter(object) do def filter(activity) do
with {:sfw, object} <- check_object_nsfw(object) do with {:sfw, activity} <- check_object_nsfw(activity) do
{:ok, object} {:ok, activity}
else else
{:nsfw, _data} -> handle_nsfw(object) {:nsfw, _data} -> handle_nsfw(activity)
end end
end end
defp handle_nsfw(object) do defp handle_nsfw(activity) do
if Config.get([@policy, :reject]) do if Config.get([@policy, :reject]) do
{:reject, object} {:reject, activity}
else else
{:ok, {:ok,
object activity
|> maybe_unlist() |> maybe_unlist()
|> maybe_mark_sensitive()} |> maybe_mark_sensitive()}
end end
end end
defp maybe_unlist(object) do defp maybe_unlist(activity) do
if Config.get([@policy, :unlist]) do if Config.get([@policy, :unlist]) do
unlist(object) unlist(activity)
else else
object activity
end end
end end
defp maybe_mark_sensitive(object) do defp maybe_mark_sensitive(activity) do
if Config.get([@policy, :mark_sensitive]) do if Config.get([@policy, :mark_sensitive]) do
mark_sensitive(object) mark_sensitive(activity)
else else
object activity
end end
end end
def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = activity) do
with %User{} = user <- User.get_cached_by_ap_id(actor) do with %User{} = user <- User.get_cached_by_ap_id(actor) do
to = to =
[user.follower_address | to] [user.follower_address | to]
@ -179,7 +179,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
|> List.delete(user.follower_address) |> List.delete(user.follower_address)
|> Enum.uniq() |> Enum.uniq()
object activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
else else
@ -187,14 +187,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
end end
end end
def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do def mark_sensitive(%{"object" => object} = activity) when is_map(object) do
Map.put(object, "object", mark_sensitive(child_object)) Map.put(activity, "object", mark_sensitive(object))
end end
def mark_sensitive(object) when is_map(object) do def mark_sensitive(activity) when is_map(activity) do
tags = (object["tag"] || []) ++ ["nsfw"] tags = (activity["tag"] || []) ++ ["nsfw"]
object activity
|> Map.put("tag", tags) |> Map.put("tag", tags)
|> Map.put("sensitive", true) |> Map.put("sensitive", true)
end end

View file

@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@moduledoc "Filter activities depending on their age" @moduledoc "Filter activities depending on their age"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp check_date(%{"object" => %{"published" => published}} = message) do defp check_date(%{"object" => %{"published" => published}} = activity) do
with %DateTime{} = now <- DateTime.utc_now(), with %DateTime{} = now <- DateTime.utc_now(),
{:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published), {:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published),
max_ttl <- Config.get([:mrf_object_age, :threshold]), max_ttl <- Config.get([:mrf_object_age, :threshold]),
{:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do {:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do
{:ok, message} {:ok, activity}
else else
{:ttl, true} -> {:ttl, true} ->
{:reject, nil} {:reject, nil}
@ -26,73 +26,73 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
end end
end end
defp check_reject(message, actions) do defp check_reject(activity, actions) do
if :reject in actions do if :reject in actions do
{:reject, "[ObjectAgePolicy]"} {:reject, "[ObjectAgePolicy]"}
else else
{:ok, message} {:ok, activity}
end end
end end
defp check_delist(message, actions) do defp check_delist(activity, actions) do
if :delist in actions do if :delist in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do
to = to =
List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++ List.delete(activity["to"] || [], Pleroma.Constants.as_public()) ++
[user.follower_address] [user.follower_address]
cc = cc =
List.delete(message["cc"] || [], user.follower_address) ++ List.delete(activity["cc"] || [], user.follower_address) ++
[Pleroma.Constants.as_public()] [Pleroma.Constants.as_public()]
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc) |> Kernel.put_in(["object", "cc"], cc)
{:ok, message} {:ok, activity}
else else
_e -> _e ->
{:reject, "[ObjectAgePolicy] Unhandled error"} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, activity}
end end
end end
defp check_strip_followers(message, actions) do defp check_strip_followers(activity, actions) do
if :strip_followers in actions do if :strip_followers in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do
to = List.delete(message["to"] || [], user.follower_address) to = List.delete(activity["to"] || [], user.follower_address)
cc = List.delete(message["cc"] || [], user.follower_address) cc = List.delete(activity["cc"] || [], user.follower_address)
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc) |> Kernel.put_in(["object", "cc"], cc)
{:ok, message} {:ok, activity}
else else
_e -> _e ->
{:reject, "[ObjectAgePolicy] Unhandled error"} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, activity}
end end
end end
@impl true @impl true
def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do def filter(%{"type" => "Create", "object" => %{"published" => _}} = activity) do
with actions <- Config.get([:mrf_object_age, :actions]), with actions <- Config.get([:mrf_object_age, :actions]),
{:reject, _} <- check_date(message), {:reject, _} <- check_date(activity),
{:ok, message} <- check_reject(message, actions), {:ok, activity} <- check_reject(activity, actions),
{:ok, message} <- check_delist(message, actions), {:ok, activity} <- check_delist(activity, actions),
{:ok, message} <- check_strip_followers(message, actions) do {:ok, activity} <- check_strip_followers(activity, actions) do
{:ok, message} {:ok, activity}
else else
# check_date() is allowed to short-circuit the pipeline # check_date() is allowed to short-circuit the pipeline
e -> e e -> e
@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do
@ -131,8 +131,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
type: {:list, :atom}, type: {:list, :atom},
description: description:
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <> "A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message; " <> "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct activity; " <>
"`:reject` rejects the message entirely", "`:reject` rejects the activity entirely",
suggestions: [:delist, :strip_followers, :reject] suggestions: [:delist, :strip_followers, :reject]
} }
] ]

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.Policy do defmodule Pleroma.Web.ActivityPub.MRF.Policy do
@callback filter(map()) :: {:ok | :reject, map()} @callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
@callback describe() :: {:ok | :error, map()} @callback describe() :: {:ok | :error, map()}
@callback config_description() :: %{ @callback config_description() :: %{
optional(:children) => [map()], optional(:children) => [map()],

View file

@ -0,0 +1,60 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
@moduledoc """
QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread.
"""
require Pleroma.Constants
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def history_awareness, do: :auto
@impl true
def filter(
%{
"type" => "Create",
"to" => to,
"cc" => cc,
"object" => %{
"actor" => actor,
"type" => "Note",
"inReplyTo" => in_reply_to
}
} = activity
) do
with true <- is_binary(in_reply_to),
false <- match?([], cc),
%User{follower_address: followers_collection, local: true} <-
User.get_by_ap_id(actor) do
updated_to =
to
|> Kernel.++([followers_collection])
|> Kernel.--([Pleroma.Constants.as_public()])
updated_cc = [Pleroma.Constants.as_public()]
updated_activity =
activity
|> Map.put("to", updated_to)
|> Map.put("cc", updated_cc)
|> put_in(["object", "to"], updated_to)
|> put_in(["object", "cc"], updated_cc)
{:ok, updated_activity}
else
_ -> {:ok, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
end

View file

@ -10,18 +10,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
require Pleroma.Constants require Pleroma.Constants
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
{:ok, Map.put(activity, "object", filter_object(object))} {:ok, Map.put(activity, "object", filter_object(object))}
end end
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}
@impl Pleroma.Web.ActivityPub.MRF.Policy @impl true
def history_awareness, do: :auto def history_awareness, do: :auto
defp filter_object(%{"quoteUrl" => quote_url} = object) do defp filter_object(%{"quoteUrl" => quote_url} = object) do

View file

@ -13,20 +13,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
require Pleroma.Constants require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info, activity) do
accepts = accepts =
instance_list(:accept) instance_list(:accept)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
cond do cond do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, activity}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, activity}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, activity}
true -> {:reject, "[SimplePolicy] host not in accept list"} true -> {:reject, "[SimplePolicy] host not in accept list"}
end end
end end
defp check_reject(%{host: actor_host} = _actor_info, object) do defp check_reject(%{host: actor_host} = _actor_info, activity) do
rejects = rejects =
instance_list(:reject) instance_list(:reject)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
@ -34,109 +34,109 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(rejects, actor_host) do if MRF.subdomain_match?(rejects, actor_host) do
{:reject, "[SimplePolicy] host in reject list"} {:reject, "[SimplePolicy] host in reject list"}
else else
{:ok, object} {:ok, activity}
end end
end end
defp check_media_removal( defp check_media_removal(
%{host: actor_host} = _actor_info, %{host: actor_host} = _actor_info,
%{"type" => type, "object" => %{"attachment" => child_attachment}} = object %{"type" => type, "object" => %{"attachment" => object_attachment}} = activity
) )
when length(child_attachment) > 0 and type in ["Create", "Update"] do when length(object_attachment) > 0 and type in ["Create", "Update"] do
media_removal = media_removal =
instance_list(:media_removal) instance_list(:media_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = activity =
if MRF.subdomain_match?(media_removal, actor_host) do if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment") object = Map.delete(activity["object"], "attachment")
Map.put(object, "object", child_object) Map.put(activity, "object", object)
else else
object activity
end end
{:ok, object} {:ok, activity}
end end
defp check_media_removal(_actor_info, object), do: {:ok, object} defp check_media_removal(_actor_info, activity), do: {:ok, activity}
defp check_media_nsfw( defp check_media_nsfw(
%{host: actor_host} = _actor_info, %{host: actor_host} = _actor_info,
%{ %{
"type" => type, "type" => type,
"object" => %{} = _child_object "object" => %{} = _object
} = object } = activity
) )
when type in ["Create", "Update"] do when type in ["Create", "Update"] do
media_nsfw = media_nsfw =
instance_list(:media_nsfw) instance_list(:media_nsfw)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = activity =
if MRF.subdomain_match?(media_nsfw, actor_host) do if MRF.subdomain_match?(media_nsfw, actor_host) do
Kernel.put_in(object, ["object", "sensitive"], true) Kernel.put_in(activity, ["object", "sensitive"], true)
else else
object activity
end end
{:ok, object} {:ok, activity}
end end
defp check_media_nsfw(_actor_info, object), do: {:ok, object} defp check_media_nsfw(_actor_info, activity), do: {:ok, activity}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do defp check_ftl_removal(%{host: actor_host} = _actor_info, activity) do
timeline_removal = timeline_removal =
instance_list(:federated_timeline_removal) instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = activity =
with true <- MRF.subdomain_match?(timeline_removal, actor_host), with true <- MRF.subdomain_match?(timeline_removal, actor_host),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(activity["actor"]),
true <- Pleroma.Constants.as_public() in object["to"] do true <- Pleroma.Constants.as_public() in activity["to"] do
to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] to = List.delete(activity["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] cc = List.delete(activity["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
object activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
else else
_ -> object _ -> activity
end end
{:ok, object} {:ok, activity}
end end
defp intersection(list1, list2) do defp intersection(list1, list2) do
list1 -- list1 -- list2 list1 -- list1 -- list2
end end
defp check_followers_only(%{host: actor_host} = _actor_info, object) do defp check_followers_only(%{host: actor_host} = _actor_info, activity) do
followers_only = followers_only =
instance_list(:followers_only) instance_list(:followers_only)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = activity =
with true <- MRF.subdomain_match?(followers_only, actor_host), with true <- MRF.subdomain_match?(followers_only, actor_host),
user <- User.get_cached_by_ap_id(object["actor"]) do user <- User.get_cached_by_ap_id(activity["actor"]) do
# Don't use Map.get/3 intentionally, these must not be nil # Don't use Map.get/3 intentionally, these must not be nil
fixed_to = object["to"] || [] fixed_to = activity["to"] || []
fixed_cc = object["cc"] || [] fixed_cc = activity["cc"] || []
to = FollowingRelationship.followers_ap_ids(user, fixed_to) to = FollowingRelationship.followers_ap_ids(user, fixed_to)
cc = FollowingRelationship.followers_ap_ids(user, fixed_cc) cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
object activity
|> Map.put("to", intersection([user.follower_address | to], fixed_to)) |> Map.put("to", intersection([user.follower_address | to], fixed_to))
|> Map.put("cc", intersection([user.follower_address | cc], fixed_cc)) |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
else else
_ -> object _ -> activity
end end
{:ok, object} {:ok, activity}
end end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = activity) do
report_removal = report_removal =
instance_list(:report_removal) instance_list(:report_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
@ -144,39 +144,39 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(report_removal, actor_host) do if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, "[SimplePolicy] host in report_removal list"} {:reject, "[SimplePolicy] host in report_removal list"}
else else
{:ok, object} {:ok, activity}
end end
end end
defp check_report_removal(_actor_info, object), do: {:ok, object} defp check_report_removal(_actor_info, activity), do: {:ok, activity}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = activity) do
avatar_removal = avatar_removal =
instance_list(:avatar_removal) instance_list(:avatar_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")} {:ok, Map.delete(activity, "icon")}
else else
{:ok, object} {:ok, activity}
end end
end end
defp check_avatar_removal(_actor_info, object), do: {:ok, object} defp check_avatar_removal(_actor_info, activity), do: {:ok, activity}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = activity) do
banner_removal = banner_removal =
instance_list(:banner_removal) instance_list(:banner_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")} {:ok, Map.delete(activity, "image")}
else else
{:ok, object} {:ok, activity}
end end
end end
defp check_banner_removal(_actor_info, object), do: {:ok, object} defp check_banner_removal(_actor_info, activity), do: {:ok, activity}
defp check_object(%{"object" => object} = activity) do defp check_object(%{"object" => object} = activity) do
with {:ok, _object} <- filter(object) do with {:ok, _object} <- filter(object) do
@ -184,7 +184,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end end
end end
defp check_object(object), do: {:ok, object} defp check_object(activity), do: {:ok, activity}
defp instance_list(config_key) do defp instance_list(config_key) do
Config.get([:mrf_simple, config_key]) Config.get([:mrf_simple, config_key])
@ -192,7 +192,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end end
@impl true @impl true
def filter(%{"type" => "Delete", "actor" => actor} = object) do def filter(%{"type" => "Delete", "actor" => actor} = activity) do
%{host: actor_host} = URI.parse(actor) %{host: actor_host} = URI.parse(actor)
reject_deletes = reject_deletes =
@ -202,54 +202,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(reject_deletes, actor_host) do if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, "[SimplePolicy] host in reject_deletes list"} {:reject, "[SimplePolicy] host in reject_deletes list"}
else else
{:ok, object} {:ok, activity}
end end
end end
@impl true @impl true
def filter(%{"actor" => actor} = object) do def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_accept(actor_info, object), with {:ok, activity} <- check_accept(actor_info, activity),
{:ok, object} <- check_reject(actor_info, object), {:ok, activity} <- check_reject(actor_info, activity),
{:ok, object} <- check_media_removal(actor_info, object), {:ok, activity} <- check_media_removal(actor_info, activity),
{:ok, object} <- check_media_nsfw(actor_info, object), {:ok, activity} <- check_media_nsfw(actor_info, activity),
{:ok, object} <- check_ftl_removal(actor_info, object), {:ok, activity} <- check_ftl_removal(actor_info, activity),
{:ok, object} <- check_followers_only(actor_info, object), {:ok, activity} <- check_followers_only(actor_info, activity),
{:ok, object} <- check_report_removal(actor_info, object), {:ok, activity} <- check_report_removal(actor_info, activity),
{:ok, object} <- check_object(object) do {:ok, activity} <- check_object(activity) do
{:ok, object} {:ok, activity}
else else
{:reject, _} = e -> e {:reject, _} = e -> e
end end
end end
def filter(%{"id" => actor, "type" => obj_type} = object) def filter(%{"id" => actor, "type" => actor_type} = activity)
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do when actor_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_accept(actor_info, object), with {:ok, activity} <- check_accept(actor_info, activity),
{:ok, object} <- check_reject(actor_info, object), {:ok, activity} <- check_reject(actor_info, activity),
{:ok, object} <- check_avatar_removal(actor_info, object), {:ok, activity} <- check_avatar_removal(actor_info, activity),
{:ok, object} <- check_banner_removal(actor_info, object) do {:ok, activity} <- check_banner_removal(actor_info, activity) do
{:ok, object} {:ok, activity}
else else
{:reject, _} = e -> e {:reject, _} = e -> e
end end
end end
def filter(object) when is_binary(object) do def filter(activity) when is_binary(activity) do
uri = URI.parse(object) uri = URI.parse(activity)
with {:ok, object} <- check_accept(uri, object), with {:ok, activity} <- check_accept(uri, activity),
{:ok, object} <- check_reject(uri, object) do {:ok, activity} <- check_reject(uri, activity) do
{:ok, object} {:ok, activity}
else else
{:reject, _} = e -> e {:reject, _} = e -> e
end end
end end
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do

View file

@ -62,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end end
@impl true @impl true
def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = activity) do
host = URI.parse(actor).host host = URI.parse(actor).host
if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
@ -97,10 +97,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end end
end end
{:ok, message} {:ok, activity}
end end
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
@spec config_description :: %{ @spec config_description :: %{

View file

@ -20,20 +20,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
end end
@impl true @impl true
def filter(%{"actor" => actor} = message) do def filter(%{"actor" => actor} = activity) do
with {:ok, match, subchain} <- lookup_subchain(actor) do with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug( Logger.debug(
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}" "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
) )
MRF.filter(subchain, message) MRF.filter(subchain, activity)
else else
_e -> {:ok, message} _e -> {:ok, activity}
end end
end end
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}
@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
label: "MRF Subchain", label: "MRF Subchain",
description: description:
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <> "This policy processes activities through an alternate pipeline when a given activity matches certain criteria." <>
" All criteria are configured as a map of regular expressions to lists of policy modules.", " All criteria are configured as a map of regular expressions to lists of policy modules.",
children: [ children: [
%{ %{

View file

@ -28,25 +28,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"mrf_tag:media-force-nsfw", "mrf_tag:media-force-nsfw",
%{ %{
"type" => type, "type" => type,
"object" => %{"attachment" => child_attachment} "object" => %{"attachment" => object_attachment}
} = message } = activity
) )
when length(child_attachment) > 0 and type in ["Create", "Update"] do when length(object_attachment) > 0 and type in ["Create", "Update"] do
{:ok, Kernel.put_in(message, ["object", "sensitive"], true)} {:ok, Kernel.put_in(activity, ["object", "sensitive"], true)}
end end
defp process_tag( defp process_tag(
"mrf_tag:media-strip", "mrf_tag:media-strip",
%{ %{
"type" => type, "type" => type,
"object" => %{"attachment" => child_attachment} = object "object" => %{"attachment" => object_attachment} = object
} = message } = activity
) )
when length(child_attachment) > 0 and type in ["Create", "Update"] do when length(object_attachment) > 0 and type in ["Create", "Update"] do
object = Map.delete(object, "attachment") object = Map.delete(object, "attachment")
message = Map.put(message, "object", object) activity = Map.put(activity, "object", object)
{:ok, message} {:ok, activity}
end end
defp process_tag( defp process_tag(
@ -57,7 +57,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"cc" => cc, "cc" => cc,
"actor" => actor, "actor" => actor,
"object" => object "object" => object
} = message } = activity
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
@ -70,15 +70,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Map.put("object", object) |> Map.put("object", object)
{:ok, message} {:ok, activity}
else else
{:ok, message} {:ok, activity}
end end
end end
@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"cc" => cc, "cc" => cc,
"actor" => actor, "actor" => actor,
"object" => object "object" => object
} = message } = activity
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
@ -104,26 +104,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
message = activity =
message activity
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Map.put("object", object) |> Map.put("object", object)
{:ok, message} {:ok, activity}
else else
{:ok, message} {:ok, activity}
end end
end end
defp process_tag( defp process_tag(
"mrf_tag:disable-remote-subscription", "mrf_tag:disable-remote-subscription",
%{"type" => "Follow", "actor" => actor} = message %{"type" => "Follow", "actor" => actor} = activity
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
if user.local == true do if user.local == true do
{:ok, message} {:ok, activity}
else else
{:reject, {:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"} "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
@ -133,14 +133,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}), defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"} do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message} defp process_tag(_, activity), do: {:ok, activity}
def filter_message(actor, message) do def filter_activity(actor, activity) do
User.get_cached_by_ap_id(actor) User.get_cached_by_ap_id(actor)
|> get_tags() |> get_tags()
|> Enum.reduce({:ok, message}, fn |> Enum.reduce({:ok, activity}, fn
tag, {:ok, message} -> tag, {:ok, activity} ->
process_tag(tag, message) process_tag(tag, activity)
_, error -> _, error ->
error error
@ -148,15 +148,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
end end
@impl true @impl true
def filter(%{"object" => target_actor, "type" => "Follow"} = message), def filter(%{"object" => target_actor, "type" => "Follow"} = activity),
do: filter_message(target_actor, message) do: filter_activity(target_actor, activity)
@impl true @impl true
def filter(%{"actor" => actor, "type" => type} = message) when type in ["Create", "Update"], def filter(%{"actor" => actor, "type" => type} = activity) when type in ["Create", "Update"],
do: filter_message(actor, message) do: filter_activity(actor, activity)
@impl true @impl true
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, do: {:ok, %{}} def describe, do: {:ok, %{}}

View file

@ -8,18 +8,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
@moduledoc "Accept-list of users from specified instances" @moduledoc "Accept-list of users from specified instances"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp filter_by_list(object, []), do: {:ok, object} defp filter_by_list(activity, []), do: {:ok, activity}
defp filter_by_list(%{"actor" => actor} = object, allow_list) do defp filter_by_list(%{"actor" => actor} = activity, allow_list) do
if actor in allow_list do if actor in allow_list do
{:ok, object} {:ok, activity}
else else
{:reject, "[UserAllowListPolicy] #{actor} not in the list"} {:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end end
end end
@impl true @impl true
def filter(%{"actor" => actor} = object) do def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
allow_list = allow_list =
@ -28,10 +28,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
[] []
) )
filter_by_list(object, allow_list) filter_by_list(activity, allow_list)
end end
def filter(object), do: {:ok, object} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe do def describe do

View file

@ -3,38 +3,38 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@moduledoc "Filter messages which belong to certain activity vocabularies" @moduledoc "Filter activities which belong to certain activity vocabularies"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(%{"type" => "Undo", "object" => child_message} = message) do def filter(%{"type" => "Undo", "object" => object} = activity) do
with {:ok, _} <- filter(child_message) do with {:ok, _} <- filter(object) do
{:ok, message} {:ok, activity}
else else
{:reject, _} = e -> e {:reject, _} = e -> e
end end
end end
def filter(%{"type" => message_type} = message) do def filter(%{"type" => activity_type} = activity) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
{_, true} <- {_, true} <-
{:accepted, {:accepted,
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)}, Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, activity_type)},
{_, false} <- {_, false} <-
{:rejected, {:rejected,
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)}, length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, activity_type)},
{:ok, _} <- filter(message["object"]) do {:ok, _} <- filter(activity["object"]) do
{:ok, message} {:ok, activity}
else else
{:reject, _} = e -> e {:reject, _} = e -> e
{:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"} {:accepted, _} -> {:reject, "[VocabularyPolicy] #{activity_type} not in accept list"}
{:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"} {:rejected, _} -> {:reject, "[VocabularyPolicy] #{activity_type} in reject list"}
end end
end end
def filter(message), do: {:ok, message} def filter(activity), do: {:ok, activity}
@impl true @impl true
def describe, def describe,
@ -46,20 +46,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
key: :mrf_vocabulary, key: :mrf_vocabulary,
related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
label: "MRF Vocabulary", label: "MRF Vocabulary",
description: "Filter messages which belong to certain activity vocabularies", description: "Filter activities which belong to certain activity vocabularies",
children: [ children: [
%{ %{
key: :accept, key: :accept,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.", "A list of ActivityStreams terms to accept. If empty, all supported activities are accepted.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"] suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
}, },
%{ %{
key: :reject, key: :reject,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.", "A list of ActivityStreams terms to reject. If empty, no activities are rejected.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"] suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
} }
] ]

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
defp validate_data(cng) do defp validate_data(cng) do
cng cng
|> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Accept", "Reject"]) |> validate_inclusion(:type, ["Accept", "Reject"])
|> validate_actor_presence() |> validate_actor_presence()
|> validate_object_presence(allowed_types: ["Follow"]) |> validate_object_presence(allowed_types: ["Follow"])

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
defp validate_data(cng) do defp validate_data(cng) do
cng cng
|> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Block"]) |> validate_inclusion(:type, ["Block"])
|> CommonValidations.validate_actor_presence() |> CommonValidations.validate_actor_presence()
|> CommonValidations.validate_actor_presence(field_name: :object) |> CommonValidations.validate_actor_presence(field_name: :object)

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
defp validate_data(cng) do defp validate_data(cng) do
cng cng
|> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Follow"]) |> validate_inclusion(:type, ["Follow"])
|> validate_inclusion(:state, ~w{pending reject accept}) |> validate_inclusion(:state, ~w{pending reject accept})
|> validate_actor_presence() |> validate_actor_presence()

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Publisher.Prepared
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Workers.PublisherWorker alias Pleroma.Workers.PublisherWorker
@ -30,11 +31,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
""" """
@spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}} @spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}}
def enqueue_one(%{} = params, worker_args \\ []) do def enqueue_one(%{} = params, worker_args \\ []) do
PublisherWorker.enqueue( PublisherWorker.new(
"publish_one", %{"op" => "publish_one", "params" => params},
%{"params" => params},
worker_args worker_args
) )
|> Oban.insert()
end end
@doc """ @doc """
@ -76,14 +77,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end end
@doc """ @doc """
Publish a single message to a peer. Takes a struct with the following Prepare an activity for publishing from an Oban job
parameters set:
* `inbox`: the inbox to publish to * `inbox`: the inbox to publish to
* `activity_id`: the internal activity id * `activity_id`: the internal activity id
* `cc`: the cc recipients relevant to this inbox (optional) * `cc`: the cc recipients relevant to this inbox (optional)
""" """
def publish_one(%{inbox: inbox, activity_id: activity_id} = params) do @spec prepare_one(map()) :: Prepared.t()
def prepare_one(%{inbox: inbox, activity_id: activity_id} = params) do
activity = Activity.get_by_id_with_user_actor(activity_id) activity = Activity.get_by_id_with_user_actor(activity_id)
actor = activity.user_actor actor = activity.user_actor
@ -93,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
cc = Map.get(params, :cc) cc = Map.get(params, :cc, [])
json = json =
data data
@ -113,27 +113,54 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
date: date date: date
}) })
%Prepared{
activity_id: activity_id,
json: json,
date: date,
signature: signature,
digest: digest,
inbox: inbox,
unreachable_since: params[:unreachable_since]
}
end
@doc """
Publish a single message to a peer. Takes a struct with the following
parameters set:
* `activity_id`: the activity id
* `json`: the json payload
* `date`: the signed date from Pleroma.Signature.signed_date()
* `signature`: the signature from Pleroma.Signature.sign/2
* `digest`: base64 encoded the hash of the json payload prefixed with "SHA-256="
* `inbox`: the inbox URI of this delivery
* `unreachable_since`: timestamp the instance was marked unreachable
"""
def publish_one(%Prepared{} = p) do
with {:ok, %{status: code}} = result when code in 200..299 <- with {:ok, %{status: code}} = result when code in 200..299 <-
HTTP.post( HTTP.post(
inbox, p.inbox,
json, p.json,
[ [
{"Content-Type", "application/activity+json"}, {"Content-Type", "application/activity+json"},
{"Date", date}, {"Date", p.date},
{"signature", signature}, {"signature", p.signature},
{"digest", digest} {"digest", p.digest}
] ]
) do ) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do if not is_nil(p.unreachable_since) do
Instances.set_reachable(inbox) Instances.set_reachable(p.inbox)
end end
result result
else else
{_post_result, %{status: code} = response} = e -> {_post_result, %{status: code} = response} = e ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox) if is_nil(p.unreachable_since) do
Logger.metadata(activity: activity_id, inbox: inbox, status: code) Instances.set_unreachable(p.inbox)
Logger.error("Publisher failed to inbox #{inbox} with status #{code}") end
Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
case response do case response do
%{status: 400} -> {:cancel, :bad_request} %{status: 400} -> {:cancel, :bad_request}
@ -152,9 +179,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
connection_pool_snooze() connection_pool_snooze()
e -> e ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox) if is_nil(p.unreachable_since) do
Logger.metadata(activity: activity_id, inbox: inbox) Instances.set_unreachable(p.inbox)
Logger.error("Publisher failed to inbox #{inbox} #{inspect(e)}") end
Logger.metadata(activity: p.activity_id, inbox: p.inbox)
Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
{:error, e} {:error, e}
end end
end end

View file

@ -0,0 +1,8 @@
# 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.Publisher.Prepared do
@type t :: %__MODULE__{}
defstruct [:activity_id, :json, :date, :signature, :digest, :inbox, :unreachable_since]
end

View file

@ -223,10 +223,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
object.data["replies"] != nil do object.data["replies"] != nil do
for reply_id <- object.data["replies"] do for reply_id <- object.data["replies"] do
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ Pleroma.Workers.RemoteFetcherWorker.new(%{
"op" => "fetch_remote",
"id" => reply_id, "id" => reply_id,
"depth" => reply_depth "depth" => reply_depth
}) })
|> Oban.insert()
end end
end end
@ -410,10 +412,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, expires_at} = {:ok, expires_at} =
Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at]) Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at])
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ Pleroma.Workers.PurgeExpiredActivity.enqueue(
activity_id: meta[:activity_id], %{
expires_at: expires_at activity_id: meta[:activity_id]
}) },
scheduled_at: expires_at
)
end end
{:ok, object, meta} {:ok, object, meta}

View file

@ -498,22 +498,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
} }
end end
def identity_proofs_operation do
%Operation{
tags: ["Retrieve account information"],
summary: "Identity proofs",
operationId: "AccountController.identity_proofs",
# Validators complains about unused path params otherwise
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
],
description: "Not implemented",
responses: %{
200 => empty_array_response()
}
}
end
def familiar_followers_operation do def familiar_followers_operation do
%Operation{ %Operation{
tags: ["Retrieve account information"], tags: ["Retrieve account information"],

View file

@ -85,9 +85,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def subscribe_operation do def subscribe_operation do
%Operation{ %Operation{
deprecated: true,
tags: ["Account actions"], tags: ["Account actions"],
summary: "Subscribe", summary: "Subscribe",
description: "Receive notifications for all statuses posted by the account.", description:
"Receive notifications for all statuses posted by the account. Deprecated, use `notify: true` in follow operation instead.",
operationId: "PleromaAPI.AccountController.subscribe", operationId: "PleromaAPI.AccountController.subscribe",
parameters: [id_param()], parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}], security: [%{"oAuth" => ["follow", "write:follows"]}],
@ -100,9 +102,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def unsubscribe_operation do def unsubscribe_operation do
%Operation{ %Operation{
deprecated: true,
tags: ["Account actions"], tags: ["Account actions"],
summary: "Unsubscribe", summary: "Unsubscribe",
description: "Stop receiving notifications for all statuses posted by the account.", description:
"Stop receiving notifications for all statuses posted by the account. Deprecated, use `notify: false` in follow operation instead.",
operationId: "PleromaAPI.AccountController.unsubscribe", operationId: "PleromaAPI.AccountController.unsubscribe",
parameters: [id_param()], parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}], security: [%{"oAuth" => ["follow", "write:follows"]}],

View file

@ -130,7 +130,7 @@ defmodule Pleroma.Web.CommonAPI do
if activity.data["state"] == "reject" do if activity.data["state"] == "reject" do
{:error, :rejected} {:error, :rejected}
else else
{:ok, follower, followed, activity} {:ok, followed, follower, activity}
end end
end end
end end
@ -559,11 +559,11 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
if expires_in > 0 do if expires_in > 0 do
Pleroma.Workers.MuteExpireWorker.enqueue( Pleroma.Workers.MuteExpireWorker.new(
"unmute_conversation", %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id},
%{"user_id" => user.id, "activity_id" => activity.id},
schedule_in: expires_in schedule_in: expires_in
) )
|> Oban.insert()
end end
{:ok, activity} {:ok, activity}

View file

@ -35,22 +35,30 @@ defmodule Pleroma.Web.Federator do
end end
# Client API # Client API
def incoming_ap_doc(%{params: _params, req_headers: _req_headers} = args) do def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
job_args = Enum.into(args, %{}, fn {k, v} -> {Atom.to_string(k), v} end) ReceiverWorker.new(
%{
ReceiverWorker.enqueue( "op" => "incoming_ap_doc",
"incoming_ap_doc", "req_headers" => req_headers,
Map.put(job_args, "timeout", :timer.seconds(20)), "params" => params,
"timeout" => :timer.seconds(20)
},
priority: 2 priority: 2
) )
|> Oban.insert()
end end
def incoming_ap_doc(%{"type" => "Delete"} = params) do def incoming_ap_doc(%{"type" => "Delete"} = params) do
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3, queue: :slow) ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params},
priority: 3,
queue: :slow
)
|> Oban.insert()
end end
def incoming_ap_doc(params) do def incoming_ap_doc(params) do
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}) ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params})
|> Oban.insert()
end end
@impl true @impl true
@ -60,9 +68,10 @@ defmodule Pleroma.Web.Federator do
@impl true @impl true
def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do
PublisherWorker.enqueue("publish", %{"activity_id" => activity.id}, PublisherWorker.new(%{"op" => "publish", "activity_id" => activity.id},
priority: publish_priority(type) priority: publish_priority(type)
) )
|> Oban.insert()
end end
defp publish_priority("Delete"), do: 3 defp publish_priority("Delete"), do: 3
@ -71,7 +80,10 @@ defmodule Pleroma.Web.Federator do
# Job Worker Callbacks # Job Worker Callbacks
@spec perform(atom(), any()) :: {:ok, any()} | {:error, any()} @spec perform(atom(), any()) :: {:ok, any()} | {:error, any()}
def perform(:publish_one, params), do: Publisher.publish_one(params) def perform(:publish_one, params) do
Publisher.prepare_one(params)
|> Publisher.publish_one()
end
def perform(:publish, activity) do def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)

View file

@ -22,7 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
@ -51,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:accounts"]} %{scopes: ["read:accounts"]}
when action in [:verify_credentials, :endorsements, :identity_proofs] when action in [:verify_credentials, :endorsements]
) )
plug( plug(
@ -660,7 +659,4 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
defp get_familiar_followers(user, current_user) do defp get_familiar_followers(user, current_user) do
User.get_familiar_followers(user, current_user) User.get_familiar_followers(user, current_user)
end end
@doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
end end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.MastodonAPI.MarkerController do defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
@ -30,9 +31,16 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do
params = Map.new(params, fn {key, value} -> {to_string(key), value} end) params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
with {:ok, result} <- Pleroma.Marker.upsert(user, params), with {:ok, _} <- mark_notifications_read(user, params),
{:ok, result} <- Pleroma.Marker.upsert(user, params),
markers <- Map.values(result) do markers <- Map.values(result) do
render(conn, "markers.json", %{markers: markers}) render(conn, "markers.json", %{markers: markers})
end end
end end
defp mark_notifications_read(user, %{"notifications" => %{last_read_id: last_read_id}}) do
Pleroma.Notification.set_read_up_to(user, last_read_id)
end
defp mark_notifications_read(_, _), do: {:ok, :noop}
end end

View file

@ -18,10 +18,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
if not User.following?(follower, followed) do if not User.following?(follower, followed) do
CommonAPI.follow(followed, follower) CommonAPI.follow(followed, follower)
else else
{:ok, follower, followed, nil} {:ok, followed, follower, nil}
end end
with {:ok, follower, _followed, _} <- result do with {:ok, _followed, follower, _} <- result do
options = cast_params(params) options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result) set_reblogs_visibility(options[:reblogs], result)
set_subscription(options[:notify], result) set_subscription(options[:notify], result)
@ -29,19 +29,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
end end
end end
defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do defp set_reblogs_visibility(false, {:ok, followed, follower, _}) do
CommonAPI.hide_reblogs(followed, follower) CommonAPI.hide_reblogs(followed, follower)
end end
defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do defp set_reblogs_visibility(_, {:ok, followed, follower, _}) do
CommonAPI.show_reblogs(followed, follower) CommonAPI.show_reblogs(followed, follower)
end end
defp set_subscription(true, {:ok, follower, followed, _}) do defp set_subscription(true, {:ok, followed, follower, _}) do
User.subscribe(follower, followed) User.subscribe(follower, followed)
end end
defp set_subscription(false, {:ok, follower, followed, _}) do defp set_subscription(false, {:ok, followed, follower, _}) do
User.unsubscribe(follower, followed) User.unsubscribe(follower, followed)
end end

View file

@ -100,11 +100,10 @@ defmodule Pleroma.Web.OAuth.Token do
def create(%App{} = app, %User{} = user, attrs \\ %{}) do def create(%App{} = app, %User{} = user, attrs \\ %{}) do
with {:ok, token} <- do_create(app, user, attrs) do with {:ok, token} <- do_create(app, user, attrs) do
if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
Pleroma.Workers.PurgeExpiredToken.enqueue(%{ Pleroma.Workers.PurgeExpiredToken.new(%{token_id: token.id, mod: __MODULE__},
token_id: token.id, scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC")
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), )
mod: __MODULE__ |> Oban.insert()
})
end end
{:ok, token} {:ok, token}

View file

@ -38,8 +38,8 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
|> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@")))
|> Enum.reject(&(&1 == "")) |> Enum.reject(&(&1 == ""))
User.Import.follow_import(follower, identifiers) User.Import.follows_import(follower, identifiers)
json(conn, "job started") json(conn, "jobs started")
end end
def blocks( def blocks(
@ -55,7 +55,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
defp do_block(%{assigns: %{user: blocker}} = conn, list) do defp do_block(%{assigns: %{user: blocker}} = conn, list) do
User.Import.blocks_import(blocker, prepare_user_identifiers(list)) User.Import.blocks_import(blocker, prepare_user_identifiers(list))
json(conn, "job started") json(conn, "jobs started")
end end
def mutes( def mutes(
@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
defp do_mute(%{assigns: %{user: user}} = conn, list) do defp do_mute(%{assigns: %{user: user}} = conn, list) do
User.Import.mutes_import(user, prepare_user_identifiers(list)) User.Import.mutes_import(user, prepare_user_identifiers(list))
json(conn, "job started") json(conn, "jobs started")
end end
defp prepare_user_identifiers(list) do defp prepare_user_identifiers(list) do

View file

@ -28,6 +28,7 @@ defmodule Pleroma.Web.Push do
@spec send(Pleroma.Notification.t()) :: @spec send(Pleroma.Notification.t()) ::
{:ok, Oban.Job.t()} | {:error, Oban.Job.changeset() | term()} {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset() | term()}
def send(notification) do def send(notification) do
WebPusherWorker.enqueue("web_push", %{"notification_id" => notification.id}) WebPusherWorker.new(%{"op" => "web_push", "notification_id" => notification.id})
|> Oban.insert()
end end
end end

View file

@ -648,7 +648,6 @@ defmodule Pleroma.Web.Router do
get("/accounts/relationships", AccountController, :relationships) get("/accounts/relationships", AccountController, :relationships)
get("/accounts/familiar_followers", AccountController, :familiar_followers) get("/accounts/familiar_followers", AccountController, :familiar_followers)
get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/endorsements", AccountController, :endorsements) get("/endorsements", AccountController, :endorsements)
get("/blocks", AccountController, :blocks) get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes) get("/mutes", AccountController, :mutes)

View file

@ -109,7 +109,25 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!() |> Jason.encode!()
end end
def render("follow_relationships_update.json", item, topic) do def render(
"follow_relationships_update.json",
%{follower: follower, following: following} = item,
topic
) do
following_follower_count =
if Enum.any?([following.hide_followers_count, following.hide_followers]) do
0
else
following.follower_count
end
following_following_count =
if Enum.any?([following.hide_follows_count, following.hide_follows]) do
0
else
following.following_count
end
%{ %{
stream: render("stream.json", %{topic: topic}), stream: render("stream.json", %{topic: topic}),
event: "pleroma:follow_relationships_update", event: "pleroma:follow_relationships_update",
@ -117,14 +135,14 @@ defmodule Pleroma.Web.StreamerView do
%{ %{
state: item.state, state: item.state,
follower: %{ follower: %{
id: item.follower.id, id: follower.id,
follower_count: item.follower.follower_count, follower_count: follower.follower_count,
following_count: item.follower.following_count following_count: follower.following_count
}, },
following: %{ following: %{
id: item.following.id, id: following.id,
follower_count: item.following.follower_count, follower_count: following_follower_count,
following_count: item.following.following_count following_count: following_following_count
} }
} }
|> Jason.encode!() |> Jason.encode!()

View file

@ -8,9 +8,9 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
use Pleroma.Workers.WorkerHelper, queue: "slow" use Oban.Worker, queue: :slow
@impl Oban.Worker @impl true
def perform(%Job{ def perform(%Job{
args: %{ args: %{
"op" => "cleanup_attachments", "op" => "cleanup_attachments",
@ -31,7 +31,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip} def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(900) def timeout(_job), do: :timer.seconds(900)
defp do_clean({object_ids, attachment_urls}) do defp do_clean({object_ids, attachment_urls}) do

View file

@ -5,9 +5,9 @@
defmodule Pleroma.Workers.BackgroundWorker do defmodule Pleroma.Workers.BackgroundWorker do
alias Pleroma.User alias Pleroma.User
use Pleroma.Workers.WorkerHelper, queue: "background" use Oban.Worker, queue: :background
@impl Oban.Worker @impl true
def perform(%Job{args: %{"op" => "user_activation", "user_id" => user_id, "status" => status}}) do def perform(%Job{args: %{"op" => "user_activation", "user_id" => user_id, "status" => status}}) do
user = User.get_cached_by_id(user_id) user = User.get_cached_by_id(user_id)
@ -19,10 +19,10 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:force_password_reset, user) User.perform(:force_password_reset, user)
end end
def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) def perform(%Job{args: %{"op" => op, "user_id" => user_id, "actor" => actor}})
when op in ["blocks_import", "follow_import", "mutes_import"] do when op in ["block_import", "follow_import", "mute_import"] do
user = User.get_cached_by_id(user_id) user = User.get_cached_by_id(user_id)
{:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} User.Import.perform(String.to_existing_atom(op), user, actor)
end end
def perform(%Job{ def perform(%Job{
@ -39,6 +39,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:verify_fields_links, user) User.perform(:verify_fields_links, user)
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(15) def timeout(_job), do: :timer.seconds(900)
end end

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Workers.BackupWorker do
alias Pleroma.Config.Getting, as: Config alias Pleroma.Config.Getting, as: Config
alias Pleroma.User.Backup alias Pleroma.User.Backup
@impl Oban.Worker @impl true
def perform(%Job{ def perform(%Job{
args: %{"op" => "process", "backup_id" => backup_id} args: %{"op" => "process", "backup_id" => backup_id}
}) do }) do
@ -32,7 +32,7 @@ defmodule Pleroma.Workers.BackupWorker do
end end
end end
@impl Oban.Worker @impl true
def timeout(_job), do: Config.get([Backup, :timeout], :timer.minutes(30)) def timeout(_job), do: Config.get([Backup, :timeout], :timer.minutes(30))
defp has_email?(user) do defp has_email?(user) do

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
require Logger require Logger
@impl Oban.Worker @impl true
def perform(_job) do def perform(_job) do
config = Config.get([:email_notifications, :digest]) config = Config.get([:email_notifications, :digest])
@ -59,6 +59,6 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
User.touch_last_digest_emailed_at(user) User.touch_last_digest_emailed_at(user)
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
end end

View file

@ -9,9 +9,9 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
import Ecto.Query import Ecto.Query
use Pleroma.Workers.WorkerHelper, queue: "background" use Oban.Worker, queue: :background
@impl Oban.Worker @impl true
def perform(_job) do def perform(_job) do
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day() today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
@ -61,6 +61,6 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
:ok :ok
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
end end

View file

@ -6,10 +6,9 @@ defmodule Pleroma.Workers.DeleteWorker do
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
alias Pleroma.User alias Pleroma.User
use Pleroma.Workers.WorkerHelper, queue: "slow" use Oban.Worker, queue: :slow
@impl Oban.Worker
@impl true
def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id) user = User.get_cached_by_id(user_id)
User.perform(:delete, user) User.perform(:delete, user)
@ -19,6 +18,6 @@ defmodule Pleroma.Workers.DeleteWorker do
Instance.perform(:delete_instance, host) Instance.perform(:delete_instance, host)
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(900) def timeout(_job), do: :timer.seconds(900)
end end

View file

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MailerWorker do defmodule Pleroma.Workers.MailerWorker do
use Pleroma.Workers.WorkerHelper, queue: "background" use Oban.Worker, queue: :background
@impl Oban.Worker @impl true
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
encoded_email encoded_email
|> Base.decode64!() |> Base.decode64!()
@ -13,6 +13,6 @@ defmodule Pleroma.Workers.MailerWorker do
|> Pleroma.Emails.Mailer.deliver(config) |> Pleroma.Emails.Mailer.deliver(config)
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
end end

View file

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MuteExpireWorker do defmodule Pleroma.Workers.MuteExpireWorker do
use Pleroma.Workers.WorkerHelper, queue: "background" use Oban.Worker, queue: :background
@impl Oban.Worker @impl true
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
Pleroma.User.unmute(muter_id, mutee_id) Pleroma.User.unmute(muter_id, mutee_id)
:ok :ok
@ -18,6 +18,6 @@ defmodule Pleroma.Workers.MuteExpireWorker do
:ok :ok
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
end end

View file

@ -6,13 +6,13 @@ defmodule Pleroma.Workers.PollWorker do
@moduledoc """ @moduledoc """
Generates notifications when a poll ends. Generates notifications when a poll ends.
""" """
use Pleroma.Workers.WorkerHelper, queue: "background" use Oban.Worker, queue: :background
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
@impl Oban.Worker @impl true
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
with %Activity{} = activity <- find_poll_activity(activity_id), with %Activity{} = activity <- find_poll_activity(activity_id),
{:ok, notifications} <- Notification.create_poll_notifications(activity) do {:ok, notifications} <- Notification.create_poll_notifications(activity) do
@ -23,7 +23,7 @@ defmodule Pleroma.Workers.PollWorker do
end end
end end
@impl Oban.Worker @impl true
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
defp find_poll_activity(activity_id) do defp find_poll_activity(activity_id) do

Some files were not shown because too many files have changed in this diff Show more