aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/pleroma.ex1
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex11
-rw-r--r--lib/mix/tasks/pleroma/database.ex21
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex12
-rw-r--r--lib/mix/tasks/pleroma/frontend.ex4
-rw-r--r--lib/mix/tasks/pleroma/user.ex4
-rw-r--r--lib/pleroma/activity.ex7
-rw-r--r--lib/pleroma/activity_expiration.ex74
-rw-r--r--lib/pleroma/application.ex1
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex62
-rw-r--r--lib/pleroma/config/oban.ex34
-rw-r--r--lib/pleroma/gun/conn.ex14
-rw-r--r--lib/pleroma/gun/connection_pool/worker.ex45
-rw-r--r--lib/pleroma/html.ex34
-rw-r--r--lib/pleroma/http/adapter_helper.ex2
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex14
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex18
-rw-r--r--lib/pleroma/http/ex_aws.ex2
-rw-r--r--lib/pleroma/http/http.ex2
-rw-r--r--lib/pleroma/http/tzdata.ex4
-rw-r--r--lib/pleroma/instances/instance.ex25
-rw-r--r--lib/pleroma/mfa/token.ex71
-rw-r--r--lib/pleroma/migration_helper/notification_backfill.ex15
-rw-r--r--lib/pleroma/object/containment.ex7
-rw-r--r--lib/pleroma/object/fetcher.ex4
-rw-r--r--lib/pleroma/repo.ex23
-rw-r--r--lib/pleroma/repo_streamer.ex34
-rw-r--r--lib/pleroma/reverse_proxy/client/hackney.ex1
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex2
-rw-r--r--lib/pleroma/stats.ex77
-rw-r--r--lib/pleroma/telemetry/logger.ex18
-rw-r--r--lib/pleroma/upload/filter.ex13
-rw-r--r--lib/pleroma/upload/filter/anonymize_filename.ex4
-rw-r--r--lib/pleroma/upload/filter/dedupe.ex4
-rw-r--r--lib/pleroma/upload/filter/exiftool.ex10
-rw-r--r--lib/pleroma/upload/filter/mogrifun.ex6
-rw-r--r--lib/pleroma/upload/filter/mogrify.ex6
-rw-r--r--lib/pleroma/user.ex44
-rw-r--r--lib/pleroma/user/search.ex4
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex69
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex37
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex56
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex31
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex (renamed from lib/pleroma/web/activity_pub/object_validators/audio_validator.ex)37
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex21
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex134
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex9
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex8
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex73
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex24
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex7
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex45
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex3
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex2
-rw-r--r--lib/pleroma/web/common_api/common_api.ex5
-rw-r--r--lib/pleroma/web/feed/tag_controller.ex10
-rw-r--r--lib/pleroma/web/feed/user_controller.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/websocket_handler.ex12
-rw-r--r--lib/pleroma/web/metadata.ex11
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex9
-rw-r--r--lib/pleroma/web/oauth/token.ex25
-rw-r--r--lib/pleroma/web/oauth/token/clean_worker.ex38
-rw-r--r--lib/pleroma/web/oauth/token/query.ex6
-rw-r--r--lib/pleroma/web/oauth/token/strategy/refresh_token.ex2
-rw-r--r--lib/pleroma/web/rel_me.ex15
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex61
-rw-r--r--lib/pleroma/web/rich_media/parser.ex19
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex2
-rw-r--r--lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex2
-rw-r--r--lib/pleroma/workers/cron/clear_oauth_token_worker.ex23
-rw-r--r--lib/pleroma/workers/cron/purge_expired_activities_worker.ex48
-rw-r--r--lib/pleroma/workers/cron/stats_worker.ex17
-rw-r--r--lib/pleroma/workers/purge_expired_activity.ex72
-rw-r--r--lib/pleroma/workers/purge_expired_token.ex29
80 files changed, 1001 insertions, 735 deletions
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index fe9b0d16c..49ba2aae4 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -18,6 +18,7 @@ defmodule Mix.Pleroma do
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
+ Pleroma.Config.Oban.warn()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index dd2b9c8f2..a607d5d4f 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -91,20 +91,17 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
"Without conn and without pool" => fn ->
{:ok, %Tesla.Env{}} =
Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
- adapter: [pool: :no_pool, receive_conn: false]
+ pool: :no_pool,
+ receive_conn: false
)
end,
"Without conn and with pool" => fn ->
{:ok, %Tesla.Env{}} =
- Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
- adapter: [receive_conn: false]
- )
+ Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], receive_conn: false)
end,
"With reused conn and without pool" => fn ->
{:ok, %Tesla.Env{}} =
- Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
- adapter: [pool: :no_pool]
- )
+ Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], pool: :no_pool)
end,
"With reused conn and with pool" => fn ->
{:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500")
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 7d8f00b08..a01c36ece 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -99,7 +99,7 @@ defmodule Mix.Tasks.Pleroma.Database do
where: fragment("(?)->>'likes' is not null", object.data),
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
)
- |> Pleroma.RepoStreamer.chunk_stream(100)
+ |> Pleroma.Repo.chunk_stream(100, :batches)
|> Stream.each(fn objects ->
ids =
objects
@@ -133,8 +133,7 @@ defmodule Mix.Tasks.Pleroma.Database do
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
Pleroma.Activity
- |> join(:left, [a], u in assoc(a, :expiration))
- |> join(:inner, [a, _u], o in Object,
+ |> join(:inner, [a], o in Object,
on:
fragment(
"(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
@@ -144,14 +143,20 @@ defmodule Mix.Tasks.Pleroma.Database do
)
)
|> where(local: true)
- |> where([a, u], is_nil(u))
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
- |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data))
- |> Pleroma.RepoStreamer.chunk_stream(100)
+ |> where([_a, o], fragment("?->>'type' = 'Note'", o.data))
+ |> Pleroma.Repo.chunk_stream(100, :batches)
|> Stream.each(fn activities ->
Enum.each(activities, fn activity ->
- expires_at = Timex.shift(activity.inserted_at, days: days)
- Pleroma.ActivityExpiration.create(activity, expires_at, false)
+ expires_at =
+ activity.inserted_at
+ |> DateTime.from_naive!("Etc/UTC")
+ |> Timex.shift(days: days)
+
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: activity.id,
+ expires_at: expires_at
+ })
end)
end)
|> Stream.run()
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 8f52ee98d..1750373f9 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -183,7 +183,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
IO.puts("Downloading the pack and generating SHA256")
- binary_archive = Tesla.get!(client(), src).body
+ {:ok, %{body: binary_archive}} = Pleroma.HTTP.get(src)
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
IO.puts("SHA256 is #{archive_sha}")
@@ -252,7 +252,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
end
defp fetch("http" <> _ = from) do
- with {:ok, %{body: body}} <- Tesla.get(client(), from) do
+ with {:ok, %{body: body}} <- Pleroma.HTTP.get(from) do
{:ok, body}
end
end
@@ -271,13 +271,5 @@ defmodule Mix.Tasks.Pleroma.Emoji do
)
end
- defp client do
- middleware = [
- {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
- ]
-
- Tesla.client(middleware)
- end
-
defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
end
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex
index 1957b1d84..cbce81ab9 100644
--- a/lib/mix/tasks/pleroma/frontend.ex
+++ b/lib/mix/tasks/pleroma/frontend.ex
@@ -124,9 +124,7 @@ defmodule Mix.Tasks.Pleroma.Frontend do
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
with {:ok, %{status: 200, body: zip_body}} <-
- Pleroma.HTTP.get(url, [],
- adapter: [pool: :media, timeout: 120_000, recv_timeout: 120_000]
- ) do
+ Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
unzip(zip_body, dest)
else
e -> {:error, e}
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 01824aa18..b20c49d89 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -179,7 +179,7 @@ defmodule Mix.Tasks.Pleroma.User do
start_pleroma()
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
- |> Pleroma.RepoStreamer.chunk_stream(500)
+ |> Pleroma.Repo.chunk_stream(500, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
@@ -370,7 +370,7 @@ defmodule Mix.Tasks.Pleroma.User do
start_pleroma()
Pleroma.User.Query.build(%{local: true})
- |> Pleroma.RepoStreamer.chunk_stream(500)
+ |> Pleroma.Repo.chunk_stream(500, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 97feebeaa..17af04257 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Activity do
alias Pleroma.Activity
alias Pleroma.Activity.Queries
- alias Pleroma.ActivityExpiration
alias Pleroma.Bookmark
alias Pleroma.Notification
alias Pleroma.Object
@@ -60,8 +59,6 @@ defmodule Pleroma.Activity do
# typical case.
has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
- has_one(:expiration, ActivityExpiration, on_delete: :delete_all)
-
timestamps()
end
@@ -304,14 +301,14 @@ defmodule Pleroma.Activity do
|> Repo.all()
end
- def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
+ def follow_requests_for_actor(%User{ap_id: ap_id}) do
ap_id
|> Queries.by_object_id()
|> Queries.by_type("Follow")
|> where([a], fragment("? ->> 'state' = 'pending'", a.data))
end
- def following_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
+ def following_requests_for_actor(%User{ap_id: ap_id}) do
Queries.by_type("Follow")
|> where([a], fragment("?->>'state' = 'pending'", a.data))
|> where([a], a.actor == ^ap_id)
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
deleted file mode 100644
index 955f0578e..000000000
--- a/lib/pleroma/activity_expiration.ex
+++ /dev/null
@@ -1,74 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.ActivityExpiration do
- use Ecto.Schema
-
- alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
- alias Pleroma.Repo
-
- import Ecto.Changeset
- import Ecto.Query
-
- @type t :: %__MODULE__{}
- @min_activity_lifetime :timer.hours(1)
-
- schema "activity_expirations" do
- belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
- field(:scheduled_at, :naive_datetime)
- end
-
- def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do
- expiration
- |> cast(attrs, [:scheduled_at])
- |> validate_required([:scheduled_at])
- |> validate_scheduled_at(validate_scheduled_at)
- end
-
- def get_by_activity_id(activity_id) do
- ActivityExpiration
- |> where([exp], exp.activity_id == ^activity_id)
- |> Repo.one()
- end
-
- def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do
- %ActivityExpiration{activity_id: activity.id}
- |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at)
- |> Repo.insert()
- end
-
- def due_expirations(offset \\ 0) do
- naive_datetime =
- NaiveDateTime.utc_now()
- |> NaiveDateTime.add(offset, :millisecond)
-
- ActivityExpiration
- |> where([exp], exp.scheduled_at < ^naive_datetime)
- |> limit(50)
- |> preload(:activity)
- |> Repo.all()
- |> Enum.reject(fn %{activity: activity} ->
- Activity.pinned_by_actor?(activity)
- end)
- end
-
- def validate_scheduled_at(changeset, false), do: changeset
-
- def validate_scheduled_at(changeset, true) do
- validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
- if not expires_late_enough?(scheduled_at) do
- [scheduled_at: "an ephemeral activity must live for at least one hour"]
- else
- []
- end
- end)
- end
-
- def expires_late_enough?(scheduled_at) do
- now = NaiveDateTime.utc_now()
- diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
- diff > @min_activity_lifetime
- end
-end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 33b1e3872..c39e24919 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -50,6 +50,7 @@ defmodule Pleroma.Application do
Pleroma.Telemetry.Logger.attach()
Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers()
+ Pleroma.Config.Oban.warn()
Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.ApplicationRequirements.verify!()
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 0f52eb210..412d55a77 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
require Logger
alias Pleroma.Config
- @type config_namespace() :: [atom()]
+ @type config_namespace() :: atom() | [atom()]
@type config_map() :: {config_namespace(), config_namespace(), String.t()}
@mrf_config_map [
@@ -56,6 +56,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_old_mrf_config()
check_media_proxy_whitelist_config()
check_welcome_message_config()
+ check_gun_pool_options()
+ check_activity_expiration_config()
end
def check_welcome_message_config do
@@ -115,4 +117,62 @@ defmodule Pleroma.Config.DeprecationWarnings do
""")
end
end
+
+ def check_gun_pool_options do
+ pool_config = Config.get(:connections_pool)
+
+ if timeout = pool_config[:await_up_timeout] do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
+ """)
+
+ Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout))
+ end
+
+ pools_configs = Config.get(:pools)
+
+ warning_preface = """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
+ """
+
+ updated_config =
+ Enum.reduce(pools_configs, [], fn {pool_name, config}, acc ->
+ if timeout = config[:timeout] do
+ Keyword.put(acc, pool_name, Keyword.put_new(config, :recv_timeout, timeout))
+ else
+ acc
+ end
+ end)
+
+ if updated_config != [] do
+ pool_warnings =
+ updated_config
+ |> Keyword.keys()
+ |> Enum.map(fn pool_name ->
+ "\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`"
+ end)
+
+ Logger.warn(Enum.join([warning_preface | pool_warnings]))
+
+ Config.put(:pools, updated_config)
+ end
+ end
+
+ @spec check_activity_expiration_config() :: :ok | nil
+ def check_activity_expiration_config do
+ warning_preface = """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespace for activity expiration configuration. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later:
+ """
+
+ move_namespace_and_warn(
+ [
+ {Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity,
+ "\n* `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"}
+ ],
+ warning_preface
+ )
+ end
end
diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex
new file mode 100644
index 000000000..9f601b1a3
--- /dev/null
+++ b/lib/pleroma/config/oban.ex
@@ -0,0 +1,34 @@
+defmodule Pleroma.Config.Oban do
+ require Logger
+
+ def warn do
+ oban_config = Pleroma.Config.get(Oban)
+
+ crontab =
+ [
+ Pleroma.Workers.Cron.StatsWorker,
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
+ Pleroma.Workers.Cron.ClearOauthTokenWorker
+ ]
+ |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc ->
+ with acc when is_list(acc) <- acc,
+ setting when is_tuple(setting) <-
+ Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
+ """
+ !!!OBAN CONFIG WARNING!!!
+ You are using old workers in Oban crontab settings, which were removed.
+ Please, remove setting from crontab in your config file (prod.secret.exs): #{
+ inspect(setting)
+ }
+ """
+ |> Logger.warn()
+
+ List.delete(acc, setting)
+ else
+ _ -> acc
+ end
+ end)
+
+ Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
+ end
+end
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
index a3f75a4bb..477e19c6e 100644
--- a/lib/pleroma/gun/conn.ex
+++ b/lib/pleroma/gun/conn.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Gun.Conn do
opts =
opts
|> Enum.into(%{})
- |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
+ |> Map.put_new(:connect_timeout, pool_opts[:connect_timeout] || 5_000)
|> Map.put_new(:supervise, false)
|> maybe_add_tls_opts(uri)
@@ -50,10 +50,10 @@ defmodule Pleroma.Gun.Conn do
with open_opts <- Map.delete(opts, :tls_opts),
{:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
- {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]),
+ {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]),
stream <- Gun.connect(conn, connect_opts),
{:response, :fin, 200, _} <- Gun.await(conn, stream) do
- {:ok, conn}
+ {:ok, conn, protocol}
else
error ->
Logger.warn(
@@ -88,8 +88,8 @@ defmodule Pleroma.Gun.Conn do
|> Map.put(:socks_opts, socks_opts)
with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
- {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
- {:ok, conn}
+ {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
+ {:ok, conn, protocol}
else
error ->
Logger.warn(
@@ -106,8 +106,8 @@ defmodule Pleroma.Gun.Conn do
host = Pleroma.HTTP.AdapterHelper.parse_host(host)
with {:ok, conn} <- Gun.open(host, port, opts),
- {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
- {:ok, conn}
+ {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
+ {:ok, conn, protocol}
else
error ->
Logger.warn(
diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex
index c36332817..bf57e9e5f 100644
--- a/lib/pleroma/gun/connection_pool/worker.ex
+++ b/lib/pleroma/gun/connection_pool/worker.ex
@@ -15,7 +15,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
@impl true
def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
- with {:ok, conn_pid} <- Gun.Conn.open(uri, opts),
+ with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts),
Process.link(conn_pid) do
time = :erlang.monotonic_time(:millisecond)
@@ -27,8 +27,12 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
send(client_pid, {:conn_pid, conn_pid})
{:noreply,
- %{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}},
- :hibernate}
+ %{
+ key: key,
+ timer: nil,
+ client_monitors: %{client_pid => Process.monitor(client_pid)},
+ protocol: protocol
+ }, :hibernate}
else
err ->
{:stop, {:shutdown, err}, nil}
@@ -53,14 +57,20 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
end
@impl true
- def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do
+ def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do
time = :erlang.monotonic_time(:millisecond)
- {{conn_pid, _, _, _}, _} =
+ {{conn_pid, used_by, _, _}, _} =
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
end)
+ :telemetry.execute(
+ [:pleroma, :connection_pool, :client, :add],
+ %{client_pid: client_pid, clients: used_by},
+ %{key: state.key, protocol: protocol}
+ )
+
state =
if state.timer != nil do
Process.cancel_timer(state[:timer])
@@ -83,25 +93,18 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
end)
{ref, state} = pop_in(state.client_monitors[client_pid])
- # DOWN message can receive right after `remove_client` call and cause worker to terminate
- state =
- if is_nil(ref) do
- state
- else
- Process.demonitor(ref)
- timer =
- if used_by == [] do
- max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
- Process.send_after(self(), :idle_close, max_idle)
- else
- nil
- end
+ Process.demonitor(ref, [:flush])
- %{state | timer: timer}
+ timer =
+ if used_by == [] do
+ max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
+ Process.send_after(self(), :idle_close, max_idle)
+ else
+ nil
end
- {:reply, :ok, state, :hibernate}
+ {:reply, :ok, %{state | timer: timer}, :hibernate}
end
@impl true
@@ -131,7 +134,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
@impl true
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
:telemetry.execute(
- [:pleroma, :connection_pool, :client_death],
+ [:pleroma, :connection_pool, :client, :dead],
%{client_pid: pid, reason: reason},
%{key: state.key}
)
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 20b02f091..43e9145be 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -100,21 +100,27 @@ defmodule Pleroma.HTML do
end)
end
- def extract_first_external_url(_, nil), do: {:error, "No content"}
+ def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
+ when is_binary(content) do
+ unless object.data["fake"] do
+ key = "URL|#{object.id}"
+
+ Cachex.fetch!(:scrubber_cache, key, fn _key ->
+ {:commit, {:ok, extract_first_external_url(content)}}
+ end)
+ else
+ {:ok, extract_first_external_url(content)}
+ end
+ end
- def extract_first_external_url(object, content) do
- key = "URL|#{object.id}"
+ def extract_first_external_url_from_object(_), do: {:error, :no_content}
- Cachex.fetch!(:scrubber_cache, key, fn _key ->
- result =
- content
- |> Floki.parse_fragment!()
- |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])")
- |> Enum.take(1)
- |> Floki.attribute("href")
- |> Enum.at(0)
-
- {:commit, {:ok, result}}
- end)
+ def extract_first_external_url(content) do
+ content
+ |> Floki.parse_fragment!()
+ |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])")
+ |> Enum.take(1)
+ |> Floki.attribute("href")
+ |> Enum.at(0)
end
end
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index d72297323..08b51578a 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
@moduledoc """
Configure Tesla.Client with default and customized adapter options.
"""
- @defaults [pool: :federation]
+ @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000]
@type proxy_type() :: :socks4 | :socks5
@type host() :: charlist() | :inet.ip_address()
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
index 4a967d8f2..1dbb71362 100644
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -11,12 +11,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
require Logger
@defaults [
- connect_timeout: 5_000,
- domain_lookup_timeout: 5_000,
- tls_handshake_timeout: 5_000,
retry: 1,
- retry_timeout: 1000,
- await_up_timeout: 5_000
+ retry_timeout: 1_000
]
@type pool() :: :federation | :upload | :media | :default
@@ -45,15 +41,17 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
end
defp put_timeout(opts) do
+ {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool]))
# this is the timeout to receive a message from Gun
- Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool]))
+ # `:timeout` key is used in Tesla
+ Keyword.put(opts, :timeout, recv_timeout)
end
@spec pool_timeout(pool()) :: non_neg_integer()
def pool_timeout(pool) do
- default = Config.get([:pools, :default, :timeout], 5_000)
+ default = Config.get([:pools, :default, :recv_timeout], 5_000)
- Config.get([:pools, pool, :timeout], default)
+ Config.get([:pools, pool, :recv_timeout], default)
end
@prefix Pleroma.Gun.ConnectionPool
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
index f47a671ad..ef84553c1 100644
--- a/lib/pleroma/http/adapter_helper/hackney.ex
+++ b/lib/pleroma/http/adapter_helper/hackney.ex
@@ -2,11 +2,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
@behaviour Pleroma.HTTP.AdapterHelper
@defaults [
- connect_timeout: 10_000,
- recv_timeout: 20_000,
follow_redirect: true,
- force_redirect: true,
- pool: :federation
+ force_redirect: true
]
@spec options(keyword(), URI.t()) :: keyword()
@@ -19,8 +16,21 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|> Keyword.merge(config_opts)
|> Keyword.merge(connection_opts)
|> add_scheme_opts(uri)
+ |> maybe_add_with_body()
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
end
+ defp add_scheme_opts(opts, %URI{scheme: "https"}) do
+ Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
+ end
+
defp add_scheme_opts(opts, _), do: opts
+
+ defp maybe_add_with_body(opts) do
+ if opts[:max_body] do
+ Keyword.put(opts, :with_body, true)
+ else
+ opts
+ end
+ end
end
diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex
index c3f335c73..5cac3532f 100644
--- a/lib/pleroma/http/ex_aws.ex
+++ b/lib/pleroma/http/ex_aws.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.ExAws do
@impl true
def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do
- http_opts = Keyword.put_new(http_opts, :adapter, pool: :upload)
+ http_opts = Keyword.put_new(http_opts, :pool, :upload)
case HTTP.request(method, url, body, headers, http_opts) do
{:ok, env} ->
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index 7bc73f4a0..052597191 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -60,7 +60,7 @@ defmodule Pleroma.HTTP do
{:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)
- adapter_opts = AdapterHelper.options(uri, options[:adapter] || [])
+ adapter_opts = AdapterHelper.options(uri, options || [])
options = put_in(options[:adapter], adapter_opts)
params = options[:params] || []
diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex
index 4539ac359..09cfdadf7 100644
--- a/lib/pleroma/http/tzdata.ex
+++ b/lib/pleroma/http/tzdata.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do
@impl true
def get(url, headers, options) do
- options = Keyword.put_new(options, :adapter, pool: :default)
+ options = Keyword.put_new(options, :pool, :default)
with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
{:ok, {env.status, env.headers, env.body}}
@@ -20,7 +20,7 @@ defmodule Pleroma.HTTP.Tzdata do
@impl true
def head(url, headers, options) do
- options = Keyword.put_new(options, :adapter, pool: :default)
+ options = Keyword.put_new(options, :pool, :default)
with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
{:ok, {env.status, env.headers}}
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 711c42158..6948651c7 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -14,6 +14,8 @@ defmodule Pleroma.Instances.Instance do
import Ecto.Query
import Ecto.Changeset
+ require Logger
+
schema "instances" do
field(:host, :string)
field(:unreachable_since, :naive_datetime_usec)
@@ -145,6 +147,10 @@ defmodule Pleroma.Instances.Instance do
favicon
end
+ rescue
+ e ->
+ Logger.warn("Instance.get_or_update_favicon(\"#{host}\") error: #{inspect(e)}")
+ nil
end
defp scrape_favicon(%URI{} = instance_uri) do
@@ -153,19 +159,22 @@ defmodule Pleroma.Instances.Instance do
Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}],
adapter: [pool: :media]
),
- favicon_rel <-
- html
- |> Floki.parse_document!()
- |> Floki.attribute("link[rel=icon]", "href")
- |> List.first(),
- favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(),
- true <- is_binary(favicon) do
+ {_, [favicon_rel | _]} when is_binary(favicon_rel) <-
+ {:parse,
+ html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")},
+ {_, favicon} when is_binary(favicon) <-
+ {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do
favicon
else
_ -> nil
end
rescue
- _ -> nil
+ e ->
+ Logger.warn(
+ "Instance.scrape_favicon(\"#{to_string(instance_uri)}\") error: #{inspect(e)}"
+ )
+
+ nil
end
end
end
diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex
index 0b2449971..69b64c0e8 100644
--- a/lib/pleroma/mfa/token.ex
+++ b/lib/pleroma/mfa/token.ex
@@ -10,10 +10,11 @@ defmodule Pleroma.MFA.Token do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Authorization
- alias Pleroma.Web.OAuth.Token, as: OAuthToken
@expires 300
+ @type t() :: %__MODULE__{}
+
schema "mfa_tokens" do
field(:token, :string)
field(:valid_until, :naive_datetime_usec)
@@ -24,6 +25,7 @@ defmodule Pleroma.MFA.Token do
timestamps()
end
+ @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(token) do
from(
t in __MODULE__,
@@ -33,33 +35,40 @@ defmodule Pleroma.MFA.Token do
|> Repo.find_resource()
end
- def validate(token) do
- with {:fetch_token, {:ok, token}} <- {:fetch_token, get_by_token(token)},
- {:expired, false} <- {:expired, is_expired?(token)} do
+ @spec validate(String.t()) :: {:ok, t()} | {:error, :not_found} | {:error, :expired_token}
+ def validate(token_str) do
+ with {:ok, token} <- get_by_token(token_str),
+ false <- expired?(token) do
{:ok, token}
- else
- {:expired, _} -> {:error, :expired_token}
- {:fetch_token, _} -> {:error, :not_found}
- error -> {:error, error}
end
end
- def create_token(%User{} = user) do
- %__MODULE__{}
- |> change
- |> assign_user(user)
- |> put_token
- |> put_valid_until
- |> Repo.insert()
+ defp expired?(%__MODULE__{valid_until: valid_until}) do
+ with true <- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 do
+ {:error, :expired_token}
+ end
+ end
+
+ @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def create(user, authorization \\ nil) do
+ with {:ok, token} <- do_create(user, authorization) do
+ Pleroma.Workers.PurgeExpiredToken.enqueue(%{
+ token_id: token.id,
+ valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
+ mod: __MODULE__
+ })
+
+ {:ok, token}
+ end
end
- def create_token(user, authorization) do
+ defp do_create(user, authorization) do
%__MODULE__{}
- |> change
+ |> change()
|> assign_user(user)
- |> assign_authorization(authorization)
- |> put_token
- |> put_valid_until
+ |> maybe_assign_authorization(authorization)
+ |> put_token()
+ |> put_valid_until()
|> Repo.insert()
end
@@ -69,15 +78,19 @@ defmodule Pleroma.MFA.Token do
|> validate_required([:user])
end
- defp assign_authorization(changeset, authorization) do
+ defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do
changeset
|> put_assoc(:authorization, authorization)
|> validate_required([:authorization])
end
+ defp maybe_assign_authorization(changeset, _), do: changeset
+
defp put_token(changeset) do
+ token = Pleroma.Web.OAuth.Token.Utils.generate_token()
+
changeset
- |> change(%{token: OAuthToken.Utils.generate_token()})
+ |> change(%{token: token})
|> validate_required([:token])
|> unique_constraint(:token)
end
@@ -89,18 +102,4 @@ defmodule Pleroma.MFA.Token do
|> change(%{valid_until: expires_in})
|> validate_required([:valid_until])
end
-
- def is_expired?(%__MODULE__{valid_until: valid_until}) do
- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0
- end
-
- def is_expired?(_), do: false
-
- def delete_expired_tokens do
- from(
- q in __MODULE__,
- where: fragment("?", q.valid_until) < ^Timex.now()
- )
- |> Repo.delete_all()
- end
end
diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex
index d260e62ca..24f4733fe 100644
--- a/lib/pleroma/migration_helper/notification_backfill.ex
+++ b/lib/pleroma/migration_helper/notification_backfill.ex
@@ -19,13 +19,13 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
query
|> Repo.chunk_stream(100)
|> Enum.each(fn notification ->
- type =
- notification.activity
- |> type_from_activity()
+ if notification.activity do
+ type = type_from_activity(notification.activity)
- notification
- |> Ecto.Changeset.change(%{type: type})
- |> Repo.update()
+ notification
+ |> Ecto.Changeset.change(%{type: type})
+ |> Repo.update()
+ end
end)
end
@@ -72,8 +72,7 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
"pleroma:emoji_reaction"
"Create" ->
- activity
- |> type_from_activity_object()
+ type_from_activity_object(activity)
t ->
raise "No notification type for activity type #{t}"
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index bc88e8a0c..29cb3d672 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -44,13 +44,6 @@ defmodule Pleroma.Object.Containment do
nil
end
- # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus
- # objects being present in the test suite environment. Once these objects are
- # removed, please also remove this.
- if Mix.env() == :test do
- defp compare_uris(_, %URI{scheme: "tag"}), do: :ok
- end
-
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
defp compare_uris(_id_uri, _other_uri), do: :error
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 1de2ce6c3..24dc7cb95 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -98,8 +98,8 @@ defmodule Pleroma.Object.Fetcher do
{:containment, _} ->
{:error, "Object containment failed."}
- {:transmogrifier, {:error, {:reject, nil}}} ->
- {:reject, nil}
+ {:transmogrifier, {:error, {:reject, e}}} ->
+ {:reject, e}
{:transmogrifier, _} = e ->
{:error, e}
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index f317e4d58..4524bd5e2 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -49,7 +49,21 @@ defmodule Pleroma.Repo do
end
end
- def chunk_stream(query, chunk_size) do
+ @doc """
+ Returns a lazy enumerable that emits all entries from the data store matching the given query.
+
+ `returns_as` use to group records. use the `batches` option to fetch records in bulk.
+
+ ## Examples
+
+ # fetch records one-by-one
+ iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500)
+
+ # fetch records in bulk
+ iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
+ """
+ @spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
+ def chunk_stream(query, chunk_size, returns_as \\ :one) do
# We don't actually need start and end funcitons of resource streaming,
# but it seems to be the only way to not fetch records one-by-one and
# have individual records be the elements of the stream, instead of
@@ -69,7 +83,12 @@ defmodule Pleroma.Repo do
records ->
last_id = List.last(records).id
- {records, last_id}
+
+ if returns_as == :one do
+ {records, last_id}
+ else
+ {[records], last_id}
+ end
end
end,
fn _ -> :ok end
diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex
deleted file mode 100644
index cb4d7bb7a..000000000
--- a/lib/pleroma/repo_streamer.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.RepoStreamer do
- alias Pleroma.Repo
- import Ecto.Query
-
- def chunk_stream(query, chunk_size) do
- Stream.unfold(0, fn
- :halt ->
- {[], :halt}
-
- last_id ->
- query
- |> order_by(asc: :id)
- |> where([r], r.id > ^last_id)
- |> limit(^chunk_size)
- |> Repo.all()
- |> case do
- [] ->
- {[], :halt}
-
- records ->
- last_id = List.last(records).id
- {records, last_id}
- end
- end)
- |> Stream.take_while(fn
- [] -> false
- _ -> true
- end)
- end
-end
diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex
index e84118a90..ad988fac3 100644
--- a/lib/pleroma/reverse_proxy/client/hackney.ex
+++ b/lib/pleroma/reverse_proxy/client/hackney.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
@impl true
def request(method, url, headers, body, opts \\ []) do
+ opts = Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
:hackney.request(method, url, headers, body, opts)
end
diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex
index d5a339681..4b118eec2 100644
--- a/lib/pleroma/reverse_proxy/client/tesla.ex
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
url,
body,
headers,
- Keyword.put(opts, :adapter, opts)
+ opts
) do
if is_map(response.body) and method != :head do
{:ok, response.status, response.headers, response.body}
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 9a03f01db..e5c9c668b 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -3,12 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Stats do
+ use GenServer
+
import Ecto.Query
+
alias Pleroma.CounterCache
alias Pleroma.Repo
alias Pleroma.User
- use GenServer
+ @interval :timer.seconds(60)
def start_link(_) do
GenServer.start_link(
@@ -18,6 +21,12 @@ defmodule Pleroma.Stats do
)
end
+ @impl true
+ def init(_args) do
+ if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
+ {:ok, nil, {:continue, :calculate_stats}}
+ end
+
@doc "Performs update stats"
def force_update do
GenServer.call(__MODULE__, :force_update)
@@ -29,7 +38,11 @@ defmodule Pleroma.Stats do
end
@doc "Returns stats data"
- @spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()}
+ @spec get_stats() :: %{
+ domain_count: non_neg_integer(),
+ status_count: non_neg_integer(),
+ user_count: non_neg_integer()
+ }
def get_stats do
%{stats: stats} = GenServer.call(__MODULE__, :get_state)
@@ -44,25 +57,14 @@ defmodule Pleroma.Stats do
peers
end
- def init(_args) do
- {:ok, calculate_stat_data()}
- end
-
- def handle_call(:force_update, _from, _state) do
- new_stats = calculate_stat_data()
- {:reply, new_stats, new_stats}
- end
-
- def handle_call(:get_state, _from, state) do
- {:reply, state, state}
- end
-
- def handle_cast(:run_update, _state) do
- new_stats = calculate_stat_data()
-
- {:noreply, new_stats}
- end
-
+ @spec calculate_stat_data() :: %{
+ peers: list(),
+ stats: %{
+ domain_count: non_neg_integer(),
+ status_count: non_neg_integer(),
+ user_count: non_neg_integer()
+ }
+ }
def calculate_stat_data do
peers =
from(
@@ -97,6 +99,7 @@ defmodule Pleroma.Stats do
}
end
+ @spec get_status_visibility_count(String.t() | nil) :: map()
def get_status_visibility_count(instance \\ nil) do
if is_nil(instance) do
CounterCache.get_sum()
@@ -104,4 +107,36 @@ defmodule Pleroma.Stats do
CounterCache.get_by_instance(instance)
end
end
+
+ @impl true
+ def handle_continue(:calculate_stats, _) do
+ stats = calculate_stat_data()
+ Process.send_after(self(), :run_update, @interval)
+ {:noreply, stats}
+ end
+
+ @impl true
+ def handle_call(:force_update, _from, _state) do
+ new_stats = calculate_stat_data()
+ {:reply, new_stats, new_stats}
+ end
+
+ @impl true
+ def handle_call(:get_state, _from, state) do
+ {:reply, state, state}
+ end
+
+ @impl true
+ def handle_cast(:run_update, _state) do
+ new_stats = calculate_stat_data()
+
+ {:noreply, new_stats}
+ end
+
+ @impl true
+ def handle_info(:run_update, _) do
+ new_stats = calculate_stat_data()
+ Process.send_after(self(), :run_update, @interval)
+ {:noreply, new_stats}
+ end
end
diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex
index 4cacae02f..197b1d091 100644
--- a/lib/pleroma/telemetry/logger.ex
+++ b/lib/pleroma/telemetry/logger.ex
@@ -7,7 +7,8 @@ defmodule Pleroma.Telemetry.Logger do
[:pleroma, :connection_pool, :reclaim, :start],
[:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure],
- [:pleroma, :connection_pool, :client_death]
+ [:pleroma, :connection_pool, :client, :dead],
+ [:pleroma, :connection_pool, :client, :add]
]
def attach do
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, [])
@@ -62,7 +63,7 @@ defmodule Pleroma.Telemetry.Logger do
end
def handle_event(
- [:pleroma, :connection_pool, :client_death],
+ [:pleroma, :connection_pool, :client, :dead],
%{client_pid: client_pid, reason: reason},
%{key: key},
_
@@ -73,4 +74,17 @@ defmodule Pleroma.Telemetry.Logger do
}"
end)
end
+
+ def handle_event(
+ [:pleroma, :connection_pool, :client, :add],
+ %{clients: [_, _ | _] = clients},
+ %{key: key, protocol: :http},
+ _
+ ) do
+ Logger.info(fn ->
+ "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur."
+ end)
+ end
+
+ def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
end
diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex
index dbdadc97f..661135634 100644
--- a/lib/pleroma/upload/filter.ex
+++ b/lib/pleroma/upload/filter.ex
@@ -15,7 +15,11 @@ defmodule Pleroma.Upload.Filter do
require Logger
- @callback filter(Pleroma.Upload.t()) :: :ok | {:ok, Pleroma.Upload.t()} | {:error, any()}
+ @callback filter(Pleroma.Upload.t()) ::
+ {:ok, :filtered}
+ | {:ok, :noop}
+ | {:ok, :filtered, Pleroma.Upload.t()}
+ | {:error, any()}
@spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()}
@@ -25,10 +29,13 @@ defmodule Pleroma.Upload.Filter do
def filter([filter | rest], upload) do
case filter.filter(upload) do
- :ok ->
+ {:ok, :filtered} ->
filter(rest, upload)
- {:ok, upload} ->
+ {:ok, :filtered, upload} ->
+ filter(rest, upload)
+
+ {:ok, :noop} ->
filter(rest, upload)
error ->
diff --git a/lib/pleroma/upload/filter/anonymize_filename.ex b/lib/pleroma/upload/filter/anonymize_filename.ex
index 07ead8203..fc456e4f4 100644
--- a/lib/pleroma/upload/filter/anonymize_filename.ex
+++ b/lib/pleroma/upload/filter/anonymize_filename.ex
@@ -16,9 +16,11 @@ defmodule Pleroma.Upload.Filter.AnonymizeFilename do
def filter(%Upload{name: name} = upload) do
extension = List.last(String.split(name, "."))
name = predefined_name(extension) || random(extension)
- {:ok, %Upload{upload | name: name}}
+ {:ok, :filtered, %Upload{upload | name: name}}
end
+ def filter(_), do: {:ok, :noop}
+
@spec predefined_name(String.t()) :: String.t() | nil
defp predefined_name(extension) do
with name when not is_nil(name) <- Config.get([__MODULE__, :text]),
diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex
index 41218a918..86cbc8996 100644
--- a/lib/pleroma/upload/filter/dedupe.ex
+++ b/lib/pleroma/upload/filter/dedupe.ex
@@ -17,8 +17,8 @@ defmodule Pleroma.Upload.Filter.Dedupe do
|> Base.encode16(case: :lower)
filename = shasum <> "." <> extension
- {:ok, %Upload{upload | id: shasum, path: filename}}
+ {:ok, :filtered, %Upload{upload | id: shasum, path: filename}}
end
- def filter(_), do: :ok
+ def filter(_), do: {:ok, :noop}
end
diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex
index ea8798fe3..1fd0cfdaa 100644
--- a/lib/pleroma/upload/filter/exiftool.ex
+++ b/lib/pleroma/upload/filter/exiftool.ex
@@ -9,11 +9,15 @@ defmodule Pleroma.Upload.Filter.Exiftool do
"""
@behaviour Pleroma.Upload.Filter
- @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()}
+ @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
+
+ # webp is not compatible with exiftool at this time
+ def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
+
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
try do
case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do
- {_response, 0} -> :ok
+ {_response, 0} -> {:ok, :filtered}
{error, 1} -> {:error, error}
end
rescue
@@ -22,5 +26,5 @@ defmodule Pleroma.Upload.Filter.Exiftool do
end
end
- def filter(_), do: :ok
+ def filter(_), do: {:ok, :noop}
end
diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex
index c8fa7b190..363e5cf0f 100644
--- a/lib/pleroma/upload/filter/mogrifun.ex
+++ b/lib/pleroma/upload/filter/mogrifun.ex
@@ -38,16 +38,16 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
[{"fill", "yellow"}, {"tint", "40"}]
]
- @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()}
+ @spec filter(Pleroma.Upload.t()) :: {:ok, atom()} | {:error, String.t()}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
try do
Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
- :ok
+ {:ok, :filtered}
rescue
_e in ErlangError ->
{:error, "mogrify command not found"}
end
end
- def filter(_), do: :ok
+ def filter(_), do: {:ok, :noop}
end
diff --git a/lib/pleroma/upload/filter/mogrify.ex b/lib/pleroma/upload/filter/mogrify.ex
index 7a45add5a..71968fd9c 100644
--- a/lib/pleroma/upload/filter/mogrify.ex
+++ b/lib/pleroma/upload/filter/mogrify.ex
@@ -8,18 +8,18 @@ defmodule Pleroma.Upload.Filter.Mogrify do
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
@type conversions :: conversion() | [conversion()]
- @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()}
+ @spec filter(Pleroma.Upload.t()) :: {:ok, :atom} | {:error, String.t()}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
try do
do_filter(file, Pleroma.Config.get!([__MODULE__, :args]))
- :ok
+ {:ok, :filtered}
rescue
_e in ErlangError ->
{:error, "mogrify command not found"}
end
end
- def filter(_), do: :ok
+ def filter(_), do: {:ok, :noop}
def do_filter(file, filters) do
file
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index be2ef0d1b..cf72985fe 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -25,7 +25,6 @@ defmodule Pleroma.User do
alias Pleroma.Object
alias Pleroma.Registration
alias Pleroma.Repo
- alias Pleroma.RepoStreamer
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web
@@ -276,9 +275,9 @@ defmodule Pleroma.User do
@spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
- def account_status(%User{approval_pending: true}), do: :approval_pending
+ def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
- def account_status(%User{confirmation_pending: true}) do
+ def account_status(%User{local: true, confirmation_pending: true}) do
if Config.get([:instance, :account_activation_required]) do
:confirmation_pending
else
@@ -1125,31 +1124,31 @@ defmodule Pleroma.User do
User.Query.build(%{followers: user, deactivated: false})
end
- def get_followers_query(user, page) do
+ def get_followers_query(%User{} = user, page) do
user
|> get_followers_query(nil)
|> User.Query.paginate(page, 20)
end
@spec get_followers_query(User.t()) :: Ecto.Query.t()
- def get_followers_query(user), do: get_followers_query(user, nil)
+ def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
- def get_followers(user, page \\ nil) do
+ def get_followers(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> Repo.all()
end
@spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
- def get_external_followers(user, page \\ nil) do
+ def get_external_followers(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> User.Query.build(%{external: true})
|> Repo.all()
end
- def get_followers_ids(user, page \\ nil) do
+ def get_followers_ids(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> select([u], u.id)
@@ -1161,29 +1160,29 @@ defmodule Pleroma.User do
User.Query.build(%{friends: user, deactivated: false})
end
- def get_friends_query(user, page) do
+ def get_friends_query(%User{} = user, page) do
user
|> get_friends_query(nil)
|> User.Query.paginate(page, 20)
end
@spec get_friends_query(User.t()) :: Ecto.Query.t()
- def get_friends_query(user), do: get_friends_query(user, nil)
+ def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
- def get_friends(user, page \\ nil) do
+ def get_friends(%User{} = user, page \\ nil) do
user
|> get_friends_query(page)
|> Repo.all()
end
- def get_friends_ap_ids(user) do
+ def get_friends_ap_ids(%User{} = user) do
user
|> get_friends_query(nil)
|> select([u], u.ap_id)
|> Repo.all()
end
- def get_friends_ids(user, page \\ nil) do
+ def get_friends_ids(%User{} = user, page \\ nil) do
user
|> get_friends_query(page)
|> select([u], u.id)
@@ -1724,7 +1723,7 @@ defmodule Pleroma.User do
def delete_user_activities(%User{ap_id: ap_id} = user) do
ap_id
|> Activity.Queries.by_actor()
- |> RepoStreamer.chunk_stream(50)
+ |> Repo.chunk_stream(50, :batches)
|> Stream.each(fn activities ->
Enum.each(activities, fn activity -> delete_activity(activity, user) end)
end)
@@ -2264,6 +2263,11 @@ defmodule Pleroma.User do
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
params = %{pinned_activities: user.pinned_activities ++ [id]}
+ # if pinned activity was scheduled for deletion, we remove job
+ if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
+ Oban.cancel_job(expiration.id)
+ end
+
user
|> cast(params, [:pinned_activities])
|> validate_length(:pinned_activities,
@@ -2276,9 +2280,19 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+ def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
params = %{pinned_activities: List.delete(user.pinned_activities, id)}
+ # if pinned activity was scheduled for deletion, we reschedule it for deletion
+ if data["expires_at"] do
+ {:ok, expires_at, _} = DateTime.from_iso8601(data["expires_at"])
+
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: id,
+ expires_at: expires_at
+ })
+ end
+
user
|> cast(params, [:pinned_activities])
|> update_and_set_cache()
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index adbef7fb8..7babd47ea 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -115,8 +115,8 @@ defmodule Pleroma.User.Search do
)
end
- defp base_query(_user, false), do: User
- defp base_query(user, true), do: User.get_friends_query(user)
+ defp base_query(%User{} = user, true), do: User.get_friends_query(user)
+ defp base_query(_user, _following), do: User
defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 333621413..92fc1e422 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
- alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.Constants
alias Pleroma.Conversation
@@ -85,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
- @object_types ~w[ChatMessage Question Answer Audio Event]
+ @object_types ~w[ChatMessage Question Answer Audio Video Event Article]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -102,7 +101,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
local: local,
recipients: recipients,
actor: object["actor"]
- }) do
+ }),
+ # TODO: add tests for expired activities, when Note type will be supported in new pipeline
+ {:ok, _} <- maybe_create_activity_expiration(activity) do
{:ok, activity, meta}
end
end
@@ -111,23 +112,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
- true <- bypass_actor_check || check_actor_is_active(map["actor"]),
- {_, true} <- {:remote_limit_error, check_remote_limit(map)},
+ {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+ {_, true} <- {:remote_limit_pass, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
{:containment, :ok} <- {:containment, Containment.contain_child(map)},
- {:ok, map, object} <- insert_full_object(map) do
- {:ok, activity} =
- %Activity{
- data: map,
- local: local,
- actor: map["actor"],
- recipients: recipients
- }
- |> Repo.insert()
- |> maybe_create_activity_expiration()
-
+ {:ok, map, object} <- insert_full_object(map),
+ {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
@@ -138,6 +130,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%Activity{} = activity ->
{:ok, activity}
+ {:actor_check, _} ->
+ {:error, false}
+
+ {:containment, _} = error ->
+ error
+
+ {:error, _} = error ->
+ error
+
{:fake, true, map, recipients} ->
activity = %Activity{
data: map,
@@ -150,8 +151,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
{:ok, activity}
- error ->
- {:error, error}
+ {:remote_limit_pass, _} ->
+ {:error, :remote_limit}
+
+ {:reject, _} = e ->
+ {:error, e}
+ end
+ end
+
+ defp insert_activity_with_expiration(data, local, recipients) do
+ struct = %Activity{
+ data: data,
+ local: local,
+ actor: data["actor"],
+ recipients: recipients
+ }
+
+ with {:ok, activity} <- Repo.insert(struct) do
+ maybe_create_activity_expiration(activity)
end
end
@@ -164,13 +181,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
stream_out_participations(participations)
end
- defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
- with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+ defp maybe_create_activity_expiration(
+ %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
+ ) do
+ with {:ok, _job} <-
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: activity.id,
+ expires_at: expires_at
+ }) do
{:ok, activity}
end
end
- defp maybe_create_activity_expiration(result), do: result
+ defp maybe_create_activity_expiration(activity), do: {:ok, activity}
defp create_or_bump_conversation(activity, actor) do
with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
@@ -744,7 +767,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp restrict_replies(query, %{
- reply_filtering_user: user,
+ reply_filtering_user: %User{} = user,
reply_visibility: "self"
}) do
from(
@@ -760,7 +783,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp restrict_replies(query, %{
- reply_filtering_user: user,
+ reply_filtering_user: %User{} = user,
reply_visibility: "following"
}) do
from(
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 220c4fe52..732c44271 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -399,21 +399,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
defp handle_user_activity(
%User{} = user,
- %{"type" => "Create", "object" => %{"type" => "Note"}} = params
+ %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
) do
- object =
- params["object"]
- |> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id())
- |> Transmogrifier.fix_object()
-
- ActivityPub.create(%{
- to: params["to"],
- actor: user,
- context: object["context"],
- object: object,
- additional: Map.take(params, ["cc"])
- })
+ content = if is_binary(object["content"]), do: object["content"], else: ""
+ name = if is_binary(object["name"]), do: object["name"], else: ""
+ summary = if is_binary(object["summary"]), do: object["summary"], else: ""
+ length = String.length(content <> name <> summary)
+
+ if length > Pleroma.Config.get([:instance, :limit]) do
+ {:error, dgettext("errors", "Note is over the character limit")}
+ else
+ object =
+ object
+ |> Map.merge(Map.take(params, ["to", "cc"]))
+ |> Map.put("attributedTo", user.ap_id())
+ |> Transmogrifier.fix_object()
+
+ ActivityPub.create(%{
+ to: params["to"],
+ actor: user,
+ context: object["context"],
+ object: object,
+ additional: Map.take(params, ["cc"])
+ })
+ end
end
defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index 7b4c78e0f..bee47b4ed 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -31,10 +31,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
defp maybe_add_expiration(activity) do
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
- expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
+ expires_at = DateTime.utc_now() |> Timex.shift(days: days)
with %{"expires_at" => existing_expires_at} <- activity,
- :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
+ :lt <- DateTime.compare(existing_expires_at, expires_at) do
activity
else
_ -> Map.put(activity, "expires_at", expires_at)
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
new file mode 100644
index 000000000..ea9c3d3f5
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
+ alias Pleroma.User
+ @behaviour Pleroma.Web.ActivityPub.MRF
+ @moduledoc "Remove bot posts from federated timeline"
+
+ require Pleroma.Constants
+
+ defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"]
+ defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname)
+
+ defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user)
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
+ ) do
+ user = User.get_cached_by_ap_id(actor)
+ isbot = check_if_bot(user)
+
+ if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do
+ to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
+ cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
+
+ object =
+ object
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+
+ message =
+ message
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ |> Map.put("object", object)
+
+ {:ok, message}
+ else
+ {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index dfab105a3..98d595469 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -13,22 +13,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
@options [
- pool: :media
+ pool: :media,
+ recv_timeout: 10_000
]
def perform(:prefetch, url) do
Logger.debug("Prefetching #{inspect(url)}")
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.put(@options, :recv_timeout, 10_000)
- else
- @options
- end
-
url
|> MediaProxy.url()
- |> HTTP.get([], adapter: opts)
+ |> HTTP.get([], @options)
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index bb193475a..161177727 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -66,7 +66,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
"type" => "Create",
"object" => child_object
} = object
- ) do
+ )
+ when is_map(child_object) do
media_nsfw =
Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index b77c06395..bd0a2a8dc 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -12,11 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
@@ -149,10 +151,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
- def validate(%{"type" => "Audio"} = object, meta) do
+ def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
with {:ok, object} <-
object
- |> AudioValidator.cast_and_validate()
+ |> AudioVideoValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Article"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> ArticleNoteValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
@@ -198,7 +210,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
meta
)
- when objtype in ~w[Question Answer Audio Event] do
+ when objtype in ~w[Question Answer Audio Video Event Article] do
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@@ -232,14 +244,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
AnswerValidator.cast_and_apply(object)
end
- def cast_and_apply(%{"type" => "Audio"} = object) do
- AudioValidator.cast_and_apply(object)
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
+ AudioVideoValidator.cast_and_apply(object)
end
def cast_and_apply(%{"type" => "Event"} = object) do
EventValidator.cast_and_apply(object)
end
+ def cast_and_apply(%{"type" => "Article"} = object) do
+ ArticleNoteValidator.cast_and_apply(object)
+ end
+
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
# is_struct/1 isn't present in Elixir 1.8.x
@@ -262,7 +278,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def stringify_keys(object), do: object
def fetch_actor(object) do
- with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
+ with actor <- Containment.get_actor(object),
+ {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
User.get_or_fetch_by_ap_id(actor)
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index 1a97c504a..5b7dad517 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
@@ -25,14 +25,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
field(:content, :string)
+
field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
- field(:summary, :string)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
@@ -40,13 +45,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, :string)
+ field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
def cast_and_apply(data) do
@@ -62,19 +65,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
end
def cast_data(data) do
+ data = fix(data)
+
%__MODULE__{}
|> changeset(data)
end
- defp fix_url(%{"url" => url} = data) when is_list(url) do
- attachment =
- Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
-
- link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
-
- data
- |> Map.put("attachment", [attachment])
- |> Map.put("url", link_element["href"])
+ defp fix_url(%{"url" => url} = data) when is_map(url) do
+ Map.put(data, "url", url["href"])
end
defp fix_url(data), do: data
@@ -83,8 +81,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
- |> Transmogrifier.fix_emoji()
+ |> CommonFixes.fix_actor()
|> fix_url()
+ |> Transmogrifier.fix_emoji()
end
def changeset(struct, data) do
@@ -97,8 +96,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
def validate_data(data_cng) do
data_cng
- |> validate_inclusion(:type, ["Audio"])
- |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> validate_inclusion(:type, ["Article", "Note"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index c8b148280..df102a134 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
import Ecto.Changeset
@@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
field(:mediaType, :string, default: "application/octet-stream")
field(:name, :string)
- embeds_many(:url, UrlObjectValidator)
+ embeds_many :url, UrlObjectValidator, primary_key: false do
+ field(:type, :string)
+ field(:href, ObjectValidators.Uri)
+ field(:mediaType, :string, default: "application/octet-stream")
+ end
end
def cast_and_validate(data) do
@@ -37,7 +42,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
struct
|> cast(data, [:type, :mediaType, :name])
- |> cast_embed(:url, required: true)
+ |> cast_embed(:url, with: &url_changeset/2)
+ |> validate_inclusion(:type, ~w[Link Document Audio Image Video])
+ |> validate_required([:type, :mediaType, :url])
+ end
+
+ def url_changeset(struct, data) do
+ data = fix_media_type(data)
+
+ struct
+ |> cast(data, [:type, :href, :mediaType])
+ |> validate_inclusion(:type, ["Link"])
+ |> validate_required([:type, :href, :mediaType])
end
def fix_media_type(data) do
@@ -75,6 +91,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
def validate_data(cng) do
cng
+ |> validate_inclusion(:type, ~w[Document Audio Image Video])
|> validate_required([:mediaType, :url, :type])
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
new file mode 100644
index 000000000..16973e5db
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -0,0 +1,134 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EarmarkRenderer
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
+ field(:content, :string)
+
+ field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_url(%{"url" => url} = data) when is_list(url) do
+ attachment =
+ Enum.find(url, fn x ->
+ mime_type = x["mimeType"] || x["mediaType"] || ""
+
+ is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"])
+ end)
+
+ link_element =
+ Enum.find(url, fn x ->
+ mime_type = x["mimeType"] || x["mediaType"] || ""
+
+ is_map(x) and mime_type == "text/html"
+ end)
+
+ data
+ |> Map.put("attachment", [attachment])
+ |> Map.put("url", link_element["href"])
+ end
+
+ defp fix_url(data), do: data
+
+ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
+ when is_binary(content) do
+ content =
+ content
+ |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
+ |> Pleroma.HTML.filter_tags()
+
+ Map.put(data, "content", content)
+ end
+
+ defp fix_content(data), do: data
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> CommonFixes.fix_actor()
+ |> Transmogrifier.fix_emoji()
+ |> fix_url()
+ |> fix_content()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Audio", "Video"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 720213d73..b3638cfc7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
+ alias Pleroma.Object.Containment
alias Pleroma.Web.ActivityPub.Utils
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
@@ -19,4 +20,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
data
|> Map.put_new("actor", data["attributedTo"])
end
+
+ def fix_actor(data) do
+ actor = Containment.get_actor(data)
+
+ data
+ |> Map.put("actor", actor)
+ |> Map.put("attributedTo", actor)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index b3dbeea57..422ee07be 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset
- import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
@@ -75,14 +76,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
data
|> fix_context(meta)
|> fix_addressing(meta)
+ |> CommonFixes.fix_actor()
end
def validate_data(cng, meta \\ []) do
cng
|> validate_required([:actor, :type, :object])
|> validate_inclusion(:type, ["Create"])
- |> validate_actor_presence()
- |> validate_any_presence([:to, :cc])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_any_presence([:to, :cc])
|> validate_actors_match(meta)
|> validate_context_match(meta)
|> validate_object_nonexistence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
deleted file mode 100644
index ab4469a59..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ /dev/null
@@ -1,73 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
- use Ecto.Schema
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Web.ActivityPub.Transmogrifier
-
- import Ecto.Changeset
-
- @primary_key false
-
- embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
-
- field(:name, :string)
- field(:summary, :string)
- field(:content, :string)
-
- field(:context, :string)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- field(:actor, ObjectValidators.ObjectID)
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:published, ObjectValidators.DateTime)
- field(:emoji, ObjectValidators.Emoji, default: %{})
- field(:sensitive, :boolean, default: false)
- # TODO: Write type
- field(:attachment, {:array, :map}, default: [])
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
-
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
- end
-
- def cast_and_validate(data) do
- data
- |> cast_data()
- |> validate_data()
- end
-
- defp fix(data) do
- data
- |> Transmogrifier.fix_emoji()
- end
-
- def cast_data(data) do
- data = fix(data)
-
- %__MODULE__{}
- |> cast(data, __schema__(:fields))
- end
-
- def validate_data(data_cng) do
- data_cng
- |> validate_inclusion(:type, ["Note"])
- |> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 934d3c1ea..9310485dc 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
deleted file mode 100644
index 881030f38..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
- use Ecto.Schema
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
-
- import Ecto.Changeset
- @primary_key false
-
- embedded_schema do
- field(:type, :string)
- field(:href, ObjectValidators.Uri)
- field(:mediaType, :string, default: "application/octet-stream")
- end
-
- def changeset(struct, data) do
- struct
- |> cast(data, __schema__(:fields))
- |> validate_required([:type, :href, :mediaType])
- end
-end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index a5e2323bd..b9a83a544 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
- alias Pleroma.ActivityExpiration
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
@@ -188,10 +187,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Object.increase_replies_count(in_reply_to)
end
- if expires_at = activity.data["expires_at"] do
- ActivityExpiration.create(activity, expires_at)
- end
-
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
meta =
@@ -341,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
def handle_object_creation(%{"type" => objtype} = object, meta)
- when objtype in ~w[Audio Question Event] do
+ when objtype in ~w[Audio Video Question Event Article] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 0831efadc..fcca014f0 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Pleroma.Activity
- alias Pleroma.EarmarkRenderer
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Maps
alias Pleroma.Object
@@ -45,7 +44,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_addressing
|> fix_summary
|> fix_type(options)
- |> fix_content
end
def fix_summary(%{"summary" => nil} = object) do
@@ -168,7 +166,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to)
- object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(depth) do
@@ -176,9 +173,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
- |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("context", replied_object.data["context"] || object["conversation"])
- |> Map.drop(["conversation"])
+ |> Map.drop(["conversation", "inReplyToAtomUri"])
else
e ->
Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
@@ -276,24 +272,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
- attachment =
- Enum.find(url, fn x ->
- media_type = x["mediaType"] || x["mimeType"] || ""
-
- is_map(x) and String.starts_with?(media_type, "video/")
- end)
-
- link_element =
- Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
-
- object
- |> Map.put("attachment", [attachment])
- |> Map.put("url", link_element["href"])
- end
-
- def fix_url(%{"type" => object_type, "url" => url} = object)
- when object_type != "Video" and is_list(url) do
+ def fix_url(%{"url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
url_string =
@@ -311,7 +290,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
emoji =
tags
- |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
+ |> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end)
|> Enum.reduce(%{}, fn data, mapping ->
name = String.trim(data["name"], ":")
@@ -373,18 +352,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_type(object, _), do: object
- defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object)
- when is_binary(content) do
- html_content =
- content
- |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
- |> Pleroma.HTML.filter_tags()
-
- Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"})
- end
-
- defp fix_content(object), do: object
-
# Reduce the object list to find the reported user.
defp get_reported(objects) do
Enum.reduce_while(objects, nil, fn ap_id, _ ->
@@ -457,7 +424,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
- when objtype in ~w{Article Note Video Page} do
+ when objtype in ~w{Note Page} do
actor = Containment.get_actor(data)
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@@ -551,7 +518,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype}} = data,
_options
)
- when objtype in ~w{Question Answer ChatMessage Audio Event} do
+ when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
+ data = Map.put(data, "object", strip_internal_fields(data["object"]))
+
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index aa2af1ab5..f5e4d49f9 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -379,8 +379,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
filters
|> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1))
- |> Enum.map(&String.to_atom/1)
- |> Map.new(&{&1, true})
+ |> Map.new(&{String.to_existing_atom(&1), true})
end
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index f849b2e01..548f76609 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -202,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
additional =
case draft.expires_at do
- %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
+ %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
_ -> additional
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 4ab533658..500c3883e 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -4,7 +4,6 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
@@ -381,9 +380,9 @@ defmodule Pleroma.Web.CommonAPI do
def check_expiry_date({:ok, nil} = res), do: res
def check_expiry_date({:ok, in_seconds}) do
- expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
+ expiry = DateTime.add(DateTime.utc_now(), in_seconds)
- if ActivityExpiration.expires_late_enough?(expiry) do
+ if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
{:ok, expiry}
else
{:error, "Expiry date is too soon"}
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
index 39b2a766a..93a8294b7 100644
--- a/lib/pleroma/web/feed/tag_controller.ex
+++ b/lib/pleroma/web/feed/tag_controller.ex
@@ -9,7 +9,15 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Feed.FeedView
- def feed(conn, %{"tag" => raw_tag} = params) do
+ def feed(conn, params) do
+ unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
+ render_feed(conn, params)
+ else
+ render_error(conn, :not_found, "Not found")
+ end
+ end
+
+ def render_feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index 9cd334a33..71eb1ea7e 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -37,7 +37,15 @@ defmodule Pleroma.Web.Feed.UserController do
end
end
- def feed(conn, %{"nickname" => nickname} = params) do
+ def feed(conn, params) do
+ unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
+ render_feed(conn, params)
+ else
+ errors(conn, {:error, :not_found})
+ end
+ end
+
+ def render_feed(conn, %{"nickname" => nickname} = params) do
format = get_format(conn)
format =
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 3fe1967be..ca42917fc 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
require Pleroma.Constants
alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@@ -245,8 +244,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
expires_at =
with true <- client_posted_this_activity,
- %ActivityExpiration{scheduled_at: scheduled_at} <-
- ActivityExpiration.get_by_activity_id(activity.id) do
+ %Oban.Job{scheduled_at: scheduled_at} <-
+ Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do
scheduled_at
else
_ -> nil
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index 94e4595d8..cf923ded8 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -37,12 +37,12 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
else
{:error, :bad_topic} ->
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
- {:ok, req} = :cowboy_req.reply(404, req)
+ req = :cowboy_req.reply(404, req)
{:ok, req, state}
{:error, :unauthorized} ->
Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}")
- {:ok, req} = :cowboy_req.reply(401, req)
+ req = :cowboy_req.reply(401, req)
{:ok, req, state}
end
end
@@ -64,7 +64,9 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
{:ok, %{state | timer: timer()}}
end
- # We never receive messages.
+ # We only receive pings for now
+ def websocket_handle(:ping, state), do: {:ok, state}
+
def websocket_handle(frame, state) do
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state}
@@ -98,6 +100,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end
+ # State can be `[]` only in case we terminate before switching to websocket,
+ # we already log errors for these cases in `init/1`, so just do nothing here
+ def terminate(_reason, _req, []), do: :ok
+
def terminate(reason, _req, state) do
Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{
diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex
index a9f70c43e..0f2d8d1e7 100644
--- a/lib/pleroma/web/metadata.ex
+++ b/lib/pleroma/web/metadata.ex
@@ -7,8 +7,9 @@ defmodule Pleroma.Web.Metadata do
def build_tags(params) do
providers = [
+ Pleroma.Web.Metadata.Providers.RelMe,
Pleroma.Web.Metadata.Providers.RestrictIndexing
- | Pleroma.Config.get([__MODULE__, :providers], [])
+ | activated_providers()
]
Enum.reduce(providers, "", fn parser, acc ->
@@ -42,4 +43,12 @@ defmodule Pleroma.Web.Metadata do
def activity_nsfw?(_) do
false
end
+
+ defp activated_providers do
+ unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
+ [Pleroma.Web.Metadata.Providers.Feed | Pleroma.Config.get([__MODULE__, :providers], [])]
+ else
+ []
+ end
+ end
end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index dd00600ea..26e68be42 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -145,7 +145,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
"authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
}) do
- render(conn, "oob_authorization_created.html", %{auth: auth})
+ # Enforcing the view to reuse the template when calling from other controllers
+ conn
+ |> put_view(OAuthView)
+ |> render("oob_authorization_created.html", %{auth: auth})
end
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
@@ -197,7 +200,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:mfa_required, user, auth, _},
params
) do
- {:ok, token} = MFA.Token.create_token(user, auth)
+ {:ok, token} = MFA.Token.create(user, auth)
data = %{
"mfa_token" => token.token,
@@ -579,7 +582,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
do: put_session(conn, :registration_id, registration_id)
defp build_and_response_mfa_token(user, auth) do
- with {:ok, token} <- MFA.Token.create_token(user, auth) do
+ with {:ok, token} <- MFA.Token.create(user, auth) do
MFAView.render("mfa_response.json", %{token: token, user: user})
end
end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index 08bb7326d..de37998f2 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.OAuth.Token do
true <- auth.app_id == app.id do
user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
- create_token(
+ create(
app,
user,
%{scopes: auth.scopes}
@@ -83,8 +83,22 @@ defmodule Pleroma.Web.OAuth.Token do
|> validate_required([:valid_until])
end
- @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
- def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
+ @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
+ def create(%App{} = app, %User{} = user, attrs \\ %{}) do
+ with {:ok, token} <- do_create(app, user, attrs) do
+ if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
+ Pleroma.Workers.PurgeExpiredToken.enqueue(%{
+ token_id: token.id,
+ valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
+ mod: __MODULE__
+ })
+ end
+
+ {:ok, token}
+ end
+ end
+
+ defp do_create(app, user, attrs) do
%__MODULE__{user_id: user.id, app_id: app.id}
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|> validate_required([:scopes, :app_id])
@@ -105,11 +119,6 @@ defmodule Pleroma.Web.OAuth.Token do
|> Repo.delete_all()
end
- def delete_expired_tokens do
- Query.get_expired_tokens()
- |> Repo.delete_all()
- end
-
def get_user_tokens(%User{id: user_id}) do
Query.get_by_user(user_id)
|> Query.preload([:app])
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
deleted file mode 100644
index e3aa4eb7e..000000000
--- a/lib/pleroma/web/oauth/token/clean_worker.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.CleanWorker do
- @moduledoc """
- The module represents functions to clean an expired OAuth and MFA tokens.
- """
- use GenServer
-
- @ten_seconds 10_000
- @one_day 86_400_000
-
- alias Pleroma.MFA
- alias Pleroma.Web.OAuth
- alias Pleroma.Workers.BackgroundWorker
-
- def start_link(_), do: GenServer.start_link(__MODULE__, %{})
-
- def init(_) do
- Process.send_after(self(), :perform, @ten_seconds)
- {:ok, nil}
- end
-
- @doc false
- def handle_info(:perform, state) do
- BackgroundWorker.enqueue("clean_expired_tokens", %{})
- interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
-
- Process.send_after(self(), :perform, interval)
- {:noreply, state}
- end
-
- def perform(:clean) do
- OAuth.Token.delete_expired_tokens()
- MFA.Token.delete_expired_tokens()
- end
-end
diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex
index 93d6e26ed..fd6d9b112 100644
--- a/lib/pleroma/web/oauth/token/query.ex
+++ b/lib/pleroma/web/oauth/token/query.ex
@@ -33,12 +33,6 @@ defmodule Pleroma.Web.OAuth.Token.Query do
from(q in query, where: q.id == ^id)
end
- @spec get_expired_tokens(query, DateTime.t() | nil) :: query
- def get_expired_tokens(query \\ Token, date \\ nil) do
- expired_date = date || Timex.now()
- from(q in query, where: fragment("?", q.valid_until) < ^expired_date)
- end
-
@spec get_by_user(query, String.t()) :: query
def get_by_user(query \\ Token, user_id) do
from(q in query, where: q.user_id == ^user_id)
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
index debc29b0b..625b0fde2 100644
--- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
+++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
defp create_access_token({:error, error}, _), do: {:error, error}
defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
- Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
+ Token.create(app, user, add_refresh_token(token_params, token.refresh_token))
end
defp add_refresh_token(params, token) do
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 8e2b51508..28f75b18d 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -5,7 +5,8 @@
defmodule Pleroma.Web.RelMe do
@options [
pool: :media,
- max_body: 2_000_000
+ max_body: 2_000_000,
+ recv_timeout: 2_000
]
if Pleroma.Config.get(:env) == :test do
@@ -23,18 +24,8 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.merge(@options,
- recv_timeout: 2_000,
- with_body: true
- )
- else
- @options
- end
-
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
- Pleroma.HTTP.get(url, [], adapter: opts),
+ Pleroma.HTTP.get(url, [], @options),
{:ok, html_tree} <- Floki.parse_document(html),
data <-
Floki.attribute(html_tree, "link[rel~=me]", "href") ++
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 2fb482b51..d7a19df4a 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -9,14 +9,15 @@ defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
- @rich_media_options [
+ @options [
pool: :media,
- max_body: 2_000_000
+ max_body: 2_000_000,
+ recv_timeout: 2_000
]
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])
+ validate_tld = Config.get([Pleroma.Formatter, :validate_tld])
page_url
|> Linkify.Parser.url?(validate_tld: validate_tld)
@@ -58,7 +59,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
with true <- Config.get([:rich_media, :enabled]),
false <- object.data["sensitive"] || false,
{:ok, page_url} <-
- HTML.extract_first_external_url(object, object.data["content"]),
+ HTML.extract_first_external_url_from_object(object),
:ok <- validate_page_url(page_url),
{:ok, rich_media} <- Parser.parse(page_url) do
%{page_url: page_url, rich_media: rich_media}
@@ -86,16 +87,50 @@ defmodule Pleroma.Web.RichMedia.Helpers do
def rich_media_get(url) do
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
- options =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.merge(@rich_media_options,
- recv_timeout: 2_000,
- with_body: true
- )
- else
- @rich_media_options
+ head_check =
+ case Pleroma.HTTP.head(url, headers, @options) do
+ # If the HEAD request didn't reach the server for whatever reason,
+ # we assume the GET that comes right after won't either
+ {:error, _} = e ->
+ e
+
+ {:ok, %Tesla.Env{status: 200, headers: headers}} ->
+ with :ok <- check_content_type(headers),
+ :ok <- check_content_length(headers),
+ do: :ok
+
+ _ ->
+ :ok
end
- Pleroma.HTTP.get(url, headers, adapter: options)
+ with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options)
+ end
+
+ defp check_content_type(headers) do
+ case List.keyfind(headers, "content-type", 0) do
+ {_, content_type} ->
+ case Plug.Conn.Utils.media_type(content_type) do
+ {:ok, "text", "html", _} -> :ok
+ _ -> {:error, {:content_type, content_type}}
+ end
+
+ _ ->
+ :ok
+ end
+ end
+
+ @max_body @options[:max_body]
+ defp check_content_length(headers) do
+ case List.keyfind(headers, "content-length", 0) do
+ {_, maybe_content_length} ->
+ case Integer.parse(maybe_content_length) do
+ {content_length, ""} when content_length <= @max_body -> :ok
+ {_, ""} -> {:error, :body_too_large}
+ _ -> :ok
+ end
+
+ _ ->
+ :ok
+ end
end
end
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index e98c743ca..33f6f1fa1 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -21,8 +21,13 @@ defmodule Pleroma.Web.RichMedia.Parser do
{:ok, _} <- set_ttl_based_on_image(data, url) do
{:ok, data}
else
+ {:error, {:invalid_metadata, data}} = e ->
+ Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end)
+ e
+
error ->
- Logger.error(fn -> "Rich media error: #{inspect(error)}" end)
+ Logger.error(fn -> "Rich media error for #{url}: #{inspect(error)}" end)
+ error
end
end
@@ -31,6 +36,14 @@ defmodule Pleroma.Web.RichMedia.Parser do
{:ok, _data} = res ->
res
+ {:error, :body_too_large} = e ->
+ e
+
+ {:error, {:content_type, _}} = e ->
+ e
+
+ # The TTL is not set for the errors above, since they are unlikely to change
+ # with time
{:error, _} = e ->
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
Cachex.expire(:rich_media_cache, url, ttl)
@@ -90,7 +103,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end)
end
- defp parse_url(url) do
+ def parse_url(url) do
with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
{:ok, html} <- Floki.parse_document(html) do
html
@@ -116,7 +129,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp check_parsed_data(data) do
- {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
+ {:error, {:invalid_metadata, data}}
end
defp clean_parsed_data(data) do
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
index 8443d906b..ffabe29a6 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
@@ -1,2 +1,2 @@
<h1>Successfully authorized</h1>
-<h2>Token code is <%= @auth.token %></h2>
+<h2>Token code is <br><%= @auth.token %></h2>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
index 961aad976..82785c4b9 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
@@ -1,2 +1,2 @@
<h1>Authorization exists</h1>
-<h2>Access token is <%= @token.token %></h2>
+<h2>Access token is <br><%= @token.token %></h2>
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index 521dc9322..072d889e2 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -135,7 +135,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
end
defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do
- {:ok, %{token: token}} = MFA.Token.create_token(user)
+ {:ok, %{token: token}} = MFA.Token.create(user)
render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false})
end
diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
deleted file mode 100644
index 276f47efc..000000000
--- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
- @moduledoc """
- The worker to cleanup expired oAuth tokens.
- """
-
- use Oban.Worker, queue: "background"
-
- alias Pleroma.Config
- alias Pleroma.Web.OAuth.Token
-
- @impl Oban.Worker
- def perform(_job) do
- if Config.get([:oauth2, :clean_expired_tokens], false) do
- Token.delete_expired_tokens()
- end
-
- :ok
- end
-end
diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
deleted file mode 100644
index 6549207fc..000000000
--- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
+++ /dev/null
@@ -1,48 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
- @moduledoc """
- The worker to purge expired activities.
- """
-
- use Oban.Worker, queue: "activity_expiration"
-
- alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
- alias Pleroma.Config
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI
-
- require Logger
-
- @interval :timer.minutes(1)
-
- @impl Oban.Worker
- def perform(_job) do
- if Config.get([ActivityExpiration, :enabled]) do
- Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
- end
- after
- :ok
- end
-
- def delete_activity(%ActivityExpiration{activity_id: activity_id}) do
- with {:activity, %Activity{} = activity} <-
- {:activity, Activity.get_by_id_with_object(activity_id)},
- {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do
- CommonAPI.delete(activity.id, user)
- else
- {:activity, _} ->
- Logger.error(
- "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}"
- )
-
- {:user, _} ->
- Logger.error(
- "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}"
- )
- end
- end
-end
diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex
deleted file mode 100644
index 6a79540bc..000000000
--- a/lib/pleroma/workers/cron/stats_worker.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.StatsWorker do
- @moduledoc """
- The worker to update peers statistics.
- """
-
- use Oban.Worker, queue: "background"
-
- @impl Oban.Worker
- def perform(_job) do
- Pleroma.Stats.do_collect()
- :ok
- end
-end
diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex
new file mode 100644
index 000000000..c168890a2
--- /dev/null
+++ b/lib/pleroma/workers/purge_expired_activity.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PurgeExpiredActivity do
+ @moduledoc """
+ Worker which purges expired activity.
+ """
+
+ use Oban.Worker, queue: :activity_expiration, max_attempts: 1
+
+ import Ecto.Query
+
+ alias Pleroma.Activity
+
+ @spec enqueue(map()) ::
+ {:ok, Oban.Job.t()}
+ | {:error, :expired_activities_disabled}
+ | {:error, :expiration_too_close}
+ def enqueue(args) do
+ with true <- enabled?() do
+ {scheduled_at, args} = Map.pop(args, :expires_at)
+
+ args
+ |> new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+ end
+
+ @impl true
+ def perform(%Oban.Job{args: %{"activity_id" => id}}) do
+ with %Activity{} = activity <- find_activity(id),
+ %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do
+ Pleroma.Web.CommonAPI.delete(activity.id, user)
+ end
+ end
+
+ defp enabled? do
+ with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do
+ {:error, :expired_activities_disabled}
+ end
+ end
+
+ defp find_activity(id) do
+ with nil <- Activity.get_by_id_with_object(id) do
+ {:error, :activity_not_found}
+ end
+ end
+
+ defp find_user(ap_id) do
+ with nil <- Pleroma.User.get_by_ap_id(ap_id) do
+ {:error, :user_not_found}
+ end
+ end
+
+ def get_expiration(id) do
+ from(j in Oban.Job,
+ where: j.state == "scheduled",
+ where: j.queue == "activity_expiration",
+ where: fragment("?->>'activity_id' = ?", j.args, ^id)
+ )
+ |> Pleroma.Repo.one()
+ end
+
+ @spec expires_late_enough?(DateTime.t()) :: boolean()
+ def expires_late_enough?(scheduled_at) do
+ now = DateTime.utc_now()
+ diff = DateTime.diff(scheduled_at, now, :millisecond)
+ min_lifetime = Pleroma.Config.get([__MODULE__, :min_lifetime], 600)
+ diff > :timer.seconds(min_lifetime)
+ end
+end
diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex
new file mode 100644
index 000000000..a81e0cd28
--- /dev/null
+++ b/lib/pleroma/workers/purge_expired_token.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PurgeExpiredToken do
+ @moduledoc """
+ Worker which purges expired OAuth tokens
+ """
+
+ use Oban.Worker, queue: :token_expiration, max_attempts: 1
+
+ @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) ::
+ {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
+ def enqueue(args) do
+ {scheduled_at, args} = Map.pop(args, :valid_until)
+
+ args
+ |> __MODULE__.new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+
+ @impl true
+ def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do
+ module
+ |> String.to_existing_atom()
+ |> Pleroma.Repo.get(id)
+ |> Pleroma.Repo.delete()
+ end
+end