Merge remote-tracking branch 'origin/develop' into shigusegubu
This commit is contained in:
commit
8b249431da
107 changed files with 1536 additions and 619 deletions
25
lib/mix/tasks/pleroma/test_runner.ex
Normal file
25
lib/mix/tasks/pleroma/test_runner.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
defmodule Mix.Tasks.Pleroma.TestRunner do
|
||||
@shortdoc "Retries tests once if they fail"
|
||||
|
||||
use Mix.Task
|
||||
|
||||
def run(args \\ []) do
|
||||
case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||
{_, 0} ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
retry(args)
|
||||
end
|
||||
end
|
||||
|
||||
def retry(args) do
|
||||
case System.cmd("mix", ["test", "--failed"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||
{_, 0} ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -94,6 +94,7 @@ defmodule Pleroma.Application do
|
|||
children =
|
||||
[
|
||||
Pleroma.PromEx,
|
||||
Pleroma.LDAP,
|
||||
Pleroma.Repo,
|
||||
Config.TransferTask,
|
||||
Pleroma.Emoji,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ defmodule Pleroma.Config.TransferTask do
|
|||
{:pleroma, :markup},
|
||||
{:pleroma, :streamer},
|
||||
{:pleroma, :pools},
|
||||
{:pleroma, :connections_pool}
|
||||
{:pleroma, :connections_pool},
|
||||
{:pleroma, :ldap}
|
||||
]
|
||||
|
||||
defp reboot_time_subkeys,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,36 @@ defmodule Pleroma.Constants do
|
|||
]
|
||||
)
|
||||
|
||||
const(activity_types,
|
||||
do: [
|
||||
"Create",
|
||||
"Update",
|
||||
"Delete",
|
||||
"Follow",
|
||||
"Accept",
|
||||
"Reject",
|
||||
"Add",
|
||||
"Remove",
|
||||
"Like",
|
||||
"Announce",
|
||||
"Undo",
|
||||
"Flag",
|
||||
"EmojiReact"
|
||||
]
|
||||
)
|
||||
|
||||
const(allowed_activity_types_from_strangers,
|
||||
do: [
|
||||
"Block",
|
||||
"Create",
|
||||
"Flag",
|
||||
"Follow",
|
||||
"Like",
|
||||
"EmojiReact",
|
||||
"Announce"
|
||||
]
|
||||
)
|
||||
|
||||
# basic regex, just there to weed out potential mistakes
|
||||
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
|
||||
const(mime_regex,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
|||
case adapter() do
|
||||
Tesla.Adapter.Gun -> AdapterHelper.Gun
|
||||
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
|
||||
{Tesla.Adapter.Finch, _} -> AdapterHelper.Finch
|
||||
_ -> AdapterHelper.Default
|
||||
end
|
||||
end
|
||||
|
|
@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
|||
host_charlist
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_stream? :: bool()
|
||||
def can_stream? do
|
||||
case Application.get_env(:tesla, :adapter) do
|
||||
Tesla.Adapter.Gun -> true
|
||||
{Tesla.Adapter.Finch, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
33
lib/pleroma/http/adapter_helper/finch.ex
Normal file
33
lib/pleroma/http/adapter_helper/finch.ex
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.AdapterHelper.Finch do
|
||||
@behaviour Pleroma.HTTP.AdapterHelper
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.HTTP.AdapterHelper
|
||||
|
||||
@spec options(keyword(), URI.t()) :: keyword()
|
||||
def options(incoming_opts \\ [], %URI{} = _uri) do
|
||||
proxy =
|
||||
[:http, :proxy_url]
|
||||
|> Config.get()
|
||||
|> AdapterHelper.format_proxy()
|
||||
|
||||
config_opts = Config.get([:http, :adapter], [])
|
||||
|
||||
config_opts
|
||||
|> Keyword.merge(incoming_opts)
|
||||
|> AdapterHelper.maybe_add_proxy(proxy)
|
||||
|> maybe_stream()
|
||||
end
|
||||
|
||||
# Finch uses [response: :stream]
|
||||
defp maybe_stream(opts) do
|
||||
case Keyword.pop(opts, :stream, nil) do
|
||||
{true, opts} -> Keyword.put(opts, :response, :stream)
|
||||
{_, opts} -> opts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
|||
|> AdapterHelper.maybe_add_proxy(proxy)
|
||||
|> Keyword.merge(incoming_opts)
|
||||
|> put_timeout()
|
||||
|> maybe_stream()
|
||||
end
|
||||
|
||||
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
|
||||
|
|
@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
|||
Keyword.put(opts, :timeout, recv_timeout)
|
||||
end
|
||||
|
||||
# Gun uses [body_as: :stream]
|
||||
defp maybe_stream(opts) do
|
||||
case Keyword.pop(opts, :stream, nil) do
|
||||
{true, opts} -> Keyword.put(opts, :body_as, :stream)
|
||||
{_, opts} -> opts
|
||||
end
|
||||
end
|
||||
|
||||
@spec pool_timeout(pool()) :: non_neg_integer()
|
||||
def pool_timeout(pool) do
|
||||
default = Config.get([:pools, :default, :recv_timeout], 5_000)
|
||||
|
|
|
|||
245
lib/pleroma/ldap.ex
Normal file
245
lib/pleroma/ldap.ex
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
defmodule Pleroma.LDAP do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
|
||||
import Pleroma.Web.Auth.Helpers, only: [fetch_user: 1]
|
||||
|
||||
@connection_timeout 2_000
|
||||
@search_timeout 2_000
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(state) do
|
||||
case {Config.get(Pleroma.Web.Auth.Authenticator), Config.get([:ldap, :enabled])} do
|
||||
{Pleroma.Web.Auth.LDAPAuthenticator, true} ->
|
||||
{:ok, state, {:continue, :connect}}
|
||||
|
||||
{Pleroma.Web.Auth.LDAPAuthenticator, false} ->
|
||||
Logger.error(
|
||||
"LDAP Authenticator enabled but :pleroma, :ldap is not enabled. Auth will not work."
|
||||
)
|
||||
|
||||
{:ok, state}
|
||||
|
||||
{_, true} ->
|
||||
Logger.warning(
|
||||
":pleroma, :ldap is enabled but Pleroma.Web.Authenticator is not set to the LDAPAuthenticator. LDAP will not be used."
|
||||
)
|
||||
|
||||
{:ok, state}
|
||||
|
||||
_ ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_continue(:connect, _state), do: do_handle_connect()
|
||||
|
||||
@impl true
|
||||
def handle_info(:connect, _state), do: do_handle_connect()
|
||||
|
||||
def handle_info({:bind_after_reconnect, name, password, from}, state) do
|
||||
result = bind_user(state[:handle], name, password)
|
||||
|
||||
GenServer.reply(from, result)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp do_handle_connect do
|
||||
state =
|
||||
case connect() do
|
||||
{:ok, handle} ->
|
||||
:eldap.controlling_process(handle, self())
|
||||
Process.link(handle)
|
||||
[handle: handle]
|
||||
|
||||
_ ->
|
||||
Logger.error("Failed to connect to LDAP. Retrying in 5000ms")
|
||||
Process.send_after(self(), :connect, 5_000)
|
||||
[]
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:bind_user, name, password}, from, state) do
|
||||
case bind_user(state[:handle], name, password) do
|
||||
:needs_reconnect ->
|
||||
Process.send(self(), {:bind_after_reconnect, name, password, from}, [])
|
||||
{:noreply, state, {:continue, :connect}}
|
||||
|
||||
result ->
|
||||
{:reply, result, state, :hibernate}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def terminate(_, state) do
|
||||
handle = Keyword.get(state, :handle)
|
||||
|
||||
if not is_nil(handle) do
|
||||
:eldap.close(handle)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def bind_user(name, password) do
|
||||
GenServer.call(__MODULE__, {:bind_user, name, password})
|
||||
end
|
||||
|
||||
defp connect do
|
||||
ldap = Config.get(:ldap, [])
|
||||
host = Keyword.get(ldap, :host, "localhost")
|
||||
port = Keyword.get(ldap, :port, 389)
|
||||
ssl = Keyword.get(ldap, :ssl, false)
|
||||
tls = Keyword.get(ldap, :tls, false)
|
||||
cacertfile = Keyword.get(ldap, :cacertfile) || CAStore.file_path()
|
||||
|
||||
if ssl, do: Application.ensure_all_started(:ssl)
|
||||
|
||||
default_secure_opts = [
|
||||
verify: :verify_peer,
|
||||
cacerts: decode_certfile(cacertfile),
|
||||
customize_hostname_check: [
|
||||
fqdn_fun: fn _ -> to_charlist(host) end
|
||||
]
|
||||
]
|
||||
|
||||
sslopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :sslopts, []))
|
||||
tlsopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :tlsopts, []))
|
||||
|
||||
default_options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}]
|
||||
|
||||
# :sslopts can only be included in :eldap.open/2 when {ssl: true}
|
||||
# or the connection will fail
|
||||
options =
|
||||
if ssl do
|
||||
default_options ++ [{:sslopts, sslopts}]
|
||||
else
|
||||
default_options
|
||||
end
|
||||
|
||||
case :eldap.open([to_charlist(host)], options) do
|
||||
{:ok, handle} ->
|
||||
try do
|
||||
cond do
|
||||
tls ->
|
||||
case :eldap.start_tls(
|
||||
handle,
|
||||
tlsopts,
|
||||
@connection_timeout
|
||||
) do
|
||||
:ok ->
|
||||
{:ok, handle}
|
||||
|
||||
error ->
|
||||
Logger.error("Could not start TLS: #{inspect(error)}")
|
||||
:eldap.close(handle)
|
||||
end
|
||||
|
||||
true ->
|
||||
{:ok, handle}
|
||||
end
|
||||
after
|
||||
:ok
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
||||
{:error, {:ldap_connection_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp bind_user(handle, name, password) do
|
||||
uid = Config.get([:ldap, :uid], "cn")
|
||||
base = Config.get([:ldap, :base])
|
||||
|
||||
case :eldap.simple_bind(handle, "#{uid}=#{name},#{base}", password) do
|
||||
:ok ->
|
||||
case fetch_user(name) do
|
||||
%User{} = user ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
register_user(handle, base, uid, name)
|
||||
end
|
||||
|
||||
# eldap does not inform us of socket closure
|
||||
# until it is used
|
||||
{:error, {:gen_tcp_error, :closed}} ->
|
||||
:eldap.close(handle)
|
||||
:needs_reconnect
|
||||
|
||||
{:error, error} = e ->
|
||||
Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
defp register_user(handle, base, uid, name) do
|
||||
case :eldap.search(handle, [
|
||||
{:base, to_charlist(base)},
|
||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||
{:scope, :eldap.wholeSubtree()},
|
||||
{:timeout, @search_timeout}
|
||||
]) do
|
||||
# The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
|
||||
# https://github.com/erlang/otp/pull/5538
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
|
||||
try_register(name, attributes)
|
||||
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
|
||||
try_register(name, attributes)
|
||||
|
||||
error ->
|
||||
Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
|
||||
{:error, {:ldap_search_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp try_register(name, attributes) do
|
||||
mail_attribute = Config.get([:ldap, :mail])
|
||||
|
||||
params = %{
|
||||
name: name,
|
||||
nickname: name,
|
||||
password: nil
|
||||
}
|
||||
|
||||
params =
|
||||
case List.keyfind(attributes, to_charlist(mail_attribute), 0) do
|
||||
{_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
|
||||
_ -> params
|
||||
end
|
||||
|
||||
changeset = User.register_changeset_ldap(%User{}, params)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} -> user
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_certfile(file) do
|
||||
with {:ok, data} <- File.read(file) do
|
||||
data
|
||||
|> :public_key.pem_decode()
|
||||
|> Enum.map(fn {_, b, _} -> b end)
|
||||
else
|
||||
_ ->
|
||||
Logger.error("Unable to read certfile: #{file}")
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -20,15 +20,13 @@ defmodule Pleroma.Maps do
|
|||
end
|
||||
|
||||
def filter_empty_values(data) do
|
||||
# TODO: Change to Map.filter in Elixir 1.13+
|
||||
data
|
||||
|> Enum.filter(fn
|
||||
|> Map.filter(fn
|
||||
{_k, nil} -> false
|
||||
{_k, ""} -> false
|
||||
{_k, []} -> false
|
||||
{_k, %{} = v} -> Map.keys(v) != []
|
||||
{_k, _v} -> true
|
||||
end)
|
||||
|> Map.new()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
@typep fetcher_errors ::
|
||||
:error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier
|
||||
|
||||
# Note: will create a Create activity, which we need internally at the moment.
|
||||
@spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()}
|
||||
@spec fetch_object_from_id(String.t(), list()) ::
|
||||
{:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors()
|
||||
def fetch_object_from_id(id, options \\ []) do
|
||||
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||
|
|
@ -141,6 +145,7 @@ defmodule Pleroma.Object.Fetcher do
|
|||
Logger.debug("Fetching object #{id} via AP")
|
||||
|
||||
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
||||
{_, true} <- {:mrf, MRF.id_filter(id)},
|
||||
{:ok, body} <- get_object(id),
|
||||
{:ok, data} <- safe_json_decode(body),
|
||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||
|
|
@ -156,6 +161,9 @@ defmodule Pleroma.Object.Fetcher do
|
|||
{:error, e} ->
|
||||
{:error, e}
|
||||
|
||||
{:mrf, false} ->
|
||||
{:error, {:reject, "Filtered by id"}}
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -419,6 +419,11 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def image_description(image, default \\ "")
|
||||
|
||||
def image_description(%{"name" => name}, _default), do: name
|
||||
def image_description(_, default), do: default
|
||||
|
||||
# Should probably be renamed or removed
|
||||
@spec ap_id(User.t()) :: String.t()
|
||||
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
|
||||
|
|
@ -586,16 +591,26 @@ defmodule Pleroma.User do
|
|||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
|
||||
|> validate_image_description(:avatar_description, params)
|
||||
|> validate_image_description(:header_description, params)
|
||||
|> put_fields()
|
||||
|> put_emoji()
|
||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
||||
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
||||
|> put_change_if_present(
|
||||
:avatar,
|
||||
&put_upload(&1, :avatar, Map.get(params, :avatar_description))
|
||||
)
|
||||
|> put_change_if_present(
|
||||
:banner,
|
||||
&put_upload(&1, :banner, Map.get(params, :header_description))
|
||||
)
|
||||
|> put_change_if_present(:background, &put_upload(&1, :background))
|
||||
|> put_change_if_present(
|
||||
:pleroma_settings_store,
|
||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||
)
|
||||
|> maybe_update_image_description(:avatar, Map.get(params, :avatar_description))
|
||||
|> maybe_update_image_description(:banner, Map.get(params, :header_description))
|
||||
|> validate_fields(false)
|
||||
end
|
||||
|
||||
|
|
@ -674,13 +689,41 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
defp put_upload(value, type) do
|
||||
defp put_upload(value, type, description \\ nil) do
|
||||
with %Plug.Upload{} <- value,
|
||||
{:ok, object} <- ActivityPub.upload(value, type: type) do
|
||||
{:ok, object} <- ActivityPub.upload(value, type: type, description: description) do
|
||||
{:ok, object.data}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_image_description(changeset, key, params) do
|
||||
description_limit = Config.get([:instance, :description_limit], 5_000)
|
||||
description = Map.get(params, key)
|
||||
|
||||
if is_binary(description) and String.length(description) > description_limit do
|
||||
changeset
|
||||
|> add_error(key, "#{key} is too long")
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_image_description(changeset, image_field, description)
|
||||
when is_binary(description) do
|
||||
with {:image_missing, true} <- {:image_missing, not changed?(changeset, image_field)},
|
||||
{:existing_image, %{"id" => id}} <-
|
||||
{:existing_image, Map.get(changeset.data, image_field)},
|
||||
{:object, %Object{} = object} <- {:object, Object.get_by_ap_id(id)},
|
||||
{:ok, object} <- Object.update_data(object, %{"name" => description}) do
|
||||
put_change(changeset, image_field, object.data)
|
||||
else
|
||||
{:description_too_long, true} -> {:error}
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_image_description(changeset, _, _), do: changeset
|
||||
|
||||
def update_as_admin_changeset(struct, params) do
|
||||
struct
|
||||
|> update_changeset(params)
|
||||
|
|
|
|||
|
|
@ -92,9 +92,6 @@ defmodule Pleroma.User.Backup do
|
|||
else
|
||||
true ->
|
||||
{:error, "Backup is missing id. Please insert it into the Repo first."}
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -121,14 +118,13 @@ defmodule Pleroma.User.Backup do
|
|||
end
|
||||
|
||||
defp permitted?(user) do
|
||||
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)},
|
||||
days = Config.get([__MODULE__, :limit_days]),
|
||||
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days),
|
||||
{_, true} <- {:diff, diff > days} do
|
||||
true
|
||||
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do
|
||||
days = Config.get([__MODULE__, :limit_days])
|
||||
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days)
|
||||
|
||||
diff > days
|
||||
else
|
||||
{:last, nil} -> true
|
||||
{:diff, false} -> false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -297,9 +293,6 @@ defmodule Pleroma.User.Backup do
|
|||
)
|
||||
|
||||
acc
|
||||
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.User.Import do
|
|||
|
||||
require Logger
|
||||
|
||||
@spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()}
|
||||
@spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()}
|
||||
def perform(:mute_import, %User{} = user, actor) do
|
||||
with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
|
||||
{_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},
|
||||
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.User.Import do
|
|||
|
||||
defp handle_error(op, user_id, error) do
|
||||
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
|
||||
error
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
def blocks_import(%User{} = user, [_ | _] = actors) do
|
||||
|
|
|
|||
|
|
@ -1542,16 +1542,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp get_actor_url(_url), do: nil
|
||||
|
||||
defp normalize_image(%{"url" => url}) do
|
||||
defp normalize_image(%{"url" => url} = data) do
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => url}]
|
||||
}
|
||||
|> maybe_put_description(data)
|
||||
end
|
||||
|
||||
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||
defp normalize_image(_), do: nil
|
||||
|
||||
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
|
||||
Map.put(map, "name", description)
|
||||
end
|
||||
|
||||
defp maybe_put_description(map, _), do: map
|
||||
|
||||
defp object_to_user_data(data, additional) do
|
||||
fields =
|
||||
data
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
post_inbox_relayed_create(conn, params)
|
||||
else
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> put_status(403)
|
||||
|> json("Not federating")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -108,6 +108,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
|
||||
def filter(%{} = object), do: get_policies() |> filter(object)
|
||||
|
||||
def id_filter(policies, id) when is_binary(id) do
|
||||
policies
|
||||
|> Enum.filter(&function_exported?(&1, :id_filter, 1))
|
||||
|> Enum.all?(& &1.id_filter(id))
|
||||
end
|
||||
|
||||
def id_filter(id) when is_binary(id), do: get_policies() |> id_filter(id)
|
||||
|
||||
@impl true
|
||||
def pipeline_filter(%{} = message, meta) do
|
||||
object = meta[:object_data]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
|
|||
{:reject, activity}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def id_filter(id) do
|
||||
Logger.debug("REJECTING #{id}")
|
||||
false
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
|
||||
@callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
|
||||
@callback id_filter(String.t()) :: boolean()
|
||||
@callback describe() :: {:ok | :error, map()}
|
||||
@callback config_description() :: %{
|
||||
optional(:children) => [map()],
|
||||
|
|
@ -13,5 +14,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.Policy do
|
|||
description: String.t()
|
||||
}
|
||||
@callback history_awareness() :: :auto | :manual
|
||||
@optional_callbacks config_description: 0, history_awareness: 0
|
||||
@optional_callbacks config_description: 0, history_awareness: 0, id_filter: 1
|
||||
end
|
||||
|
|
|
|||
|
|
@ -191,6 +191,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|> MRF.instance_list_from_tuples()
|
||||
end
|
||||
|
||||
@impl true
|
||||
def id_filter(id) do
|
||||
host_info = URI.parse(id)
|
||||
|
||||
with {:ok, _} <- check_accept(host_info, %{}),
|
||||
{:ok, _} <- check_reject(host_info, %{}) do
|
||||
true
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Delete", "actor" => actor} = activity) do
|
||||
%{host: actor_host} = URI.parse(actor)
|
||||
|
|
|
|||
|
|
@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
|
||||
defp config, do: Config.get([:pipeline, :config], Config)
|
||||
|
||||
@spec common_pipeline(map(), keyword()) ::
|
||||
{:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()}
|
||||
@type results :: {:ok, Activity.t() | Object.t(), keyword()}
|
||||
@type errors :: {:error | :reject, any()}
|
||||
|
||||
# The Repo.transaction will wrap the result in an {:ok, _}
|
||||
# and only returns an {:error, _} if the error encountered was related
|
||||
# to the SQL transaction
|
||||
@spec common_pipeline(map(), keyword()) :: results() | errors()
|
||||
def common_pipeline(object, meta) do
|
||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
||||
{:ok, {:ok, activity, meta}} ->
|
||||
side_effects().handle_after_transaction(meta)
|
||||
{:ok, activity, meta}
|
||||
|
||||
{:ok, value} ->
|
||||
value
|
||||
{:ok, {:error, _} = error} ->
|
||||
error
|
||||
|
||||
{:ok, {:reject, _} = error} ->
|
||||
error
|
||||
|
||||
{:error, e} ->
|
||||
{:error, e}
|
||||
|
||||
{:reject, e} ->
|
||||
{:reject, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -129,8 +129,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"vcard:bday" => birthday,
|
||||
"webfinger" => "acct:#{User.full_nickname(user)}"
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|> Map.merge(
|
||||
maybe_make_image(
|
||||
&User.avatar_url/2,
|
||||
User.image_description(user.avatar, nil),
|
||||
"icon",
|
||||
user
|
||||
)
|
||||
)
|
||||
|> Map.merge(
|
||||
maybe_make_image(
|
||||
&User.banner_url/2,
|
||||
User.image_description(user.banner, nil),
|
||||
"image",
|
||||
user
|
||||
)
|
||||
)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
|
|
@ -305,16 +319,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_make_image(func, key, user) do
|
||||
defp maybe_make_image(func, description, key, user) do
|
||||
if image = func.(user, no_default: true) do
|
||||
%{
|
||||
key => %{
|
||||
"type" => "Image",
|
||||
"url" => image
|
||||
}
|
||||
key =>
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => image
|
||||
}
|
||||
|> maybe_put_description(description)
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_description(map, description) when is_binary(description) do
|
||||
Map.put(map, "name", description)
|
||||
end
|
||||
|
||||
defp maybe_put_description(map, _description), do: map
|
||||
end
|
||||
|
|
|
|||
|
|
@ -813,6 +813,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "User's birthday will be visible"
|
||||
},
|
||||
avatar_description: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "Avatar image description."
|
||||
},
|
||||
header_description: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "Header image description."
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
group_key: %Schema{
|
||||
type: :string,
|
||||
description: "Group key shared by similar notifications"
|
||||
},
|
||||
type: notification_type(),
|
||||
created_at: %Schema{type: :string, format: :"date-time"},
|
||||
account: %Schema{
|
||||
|
|
@ -180,6 +184,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
},
|
||||
example: %{
|
||||
"id" => "34975861",
|
||||
"group-key" => "ungrouped-34975861",
|
||||
"type" => "mention",
|
||||
"created_at" => "2019-11-23T07:49:02.064Z",
|
||||
"account" => Account.schema().example,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
format: :uri,
|
||||
nullable: true,
|
||||
description: "Favicon image of the user's instance"
|
||||
}
|
||||
},
|
||||
avatar_description: %Schema{type: :string},
|
||||
header_description: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
source: %Schema{
|
||||
|
|
@ -152,6 +154,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
example: %{
|
||||
"acct" => "foobar",
|
||||
"avatar" => "https://mypleroma.com/images/avi.png",
|
||||
"avatar_description" => "",
|
||||
"avatar_static" => "https://mypleroma.com/images/avi.png",
|
||||
"bot" => false,
|
||||
"created_at" => "2020-03-24T13:05:58.000Z",
|
||||
|
|
@ -162,6 +165,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
"followers_count" => 0,
|
||||
"following_count" => 1,
|
||||
"header" => "https://mypleroma.com/images/banner.png",
|
||||
"header_description" => "",
|
||||
"header_static" => "https://mypleroma.com/images/banner.png",
|
||||
"id" => "9tKi3esbG7OQgZ2920",
|
||||
"locked" => false,
|
||||
|
|
|
|||
|
|
@ -249,6 +249,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
nullable: true,
|
||||
description:
|
||||
"A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
|
||||
},
|
||||
list_id: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description:
|
||||
"The ID of the list the post is addressed to (if any, only returned to author)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,18 +3,14 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||
alias Pleroma.LDAP
|
||||
alias Pleroma.User
|
||||
|
||||
require Logger
|
||||
|
||||
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
||||
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1]
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||
|
||||
@connection_timeout 10_000
|
||||
@search_timeout 10_000
|
||||
|
||||
defdelegate get_registration(conn), to: @base
|
||||
defdelegate create_from_registration(conn, registration), to: @base
|
||||
defdelegate handle_error(conn, error), to: @base
|
||||
|
|
@ -24,7 +20,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
def get_user(%Plug.Conn{} = conn) do
|
||||
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
|
||||
{:ok, {name, password}} <- fetch_credentials(conn),
|
||||
%User{} = user <- ldap_user(name, password) do
|
||||
%User{} = user <- LDAP.bind_user(name, password) do
|
||||
{:ok, user}
|
||||
else
|
||||
{:ldap, _} ->
|
||||
|
|
@ -34,107 +30,4 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp ldap_user(name, password) do
|
||||
ldap = Pleroma.Config.get(:ldap, [])
|
||||
host = Keyword.get(ldap, :host, "localhost")
|
||||
port = Keyword.get(ldap, :port, 389)
|
||||
ssl = Keyword.get(ldap, :ssl, false)
|
||||
sslopts = Keyword.get(ldap, :sslopts, [])
|
||||
|
||||
options =
|
||||
[{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
|
||||
if sslopts != [], do: [{:sslopts, sslopts}], else: []
|
||||
|
||||
case :eldap.open([to_charlist(host)], options) do
|
||||
{:ok, connection} ->
|
||||
try do
|
||||
if Keyword.get(ldap, :tls, false) do
|
||||
:application.ensure_all_started(:ssl)
|
||||
|
||||
case :eldap.start_tls(
|
||||
connection,
|
||||
Keyword.get(ldap, :tlsopts, []),
|
||||
@connection_timeout
|
||||
) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
error ->
|
||||
Logger.error("Could not start TLS: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
bind_user(connection, ldap, name, password)
|
||||
after
|
||||
:eldap.close(connection)
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
||||
{:error, {:ldap_connection_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp bind_user(connection, ldap, name, password) do
|
||||
uid = Keyword.get(ldap, :uid, "cn")
|
||||
base = Keyword.get(ldap, :base)
|
||||
|
||||
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
|
||||
:ok ->
|
||||
case fetch_user(name) do
|
||||
%User{} = user ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
register_user(connection, base, uid, name)
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
|
||||
{:error, {:ldap_bind_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp register_user(connection, base, uid, name) do
|
||||
case :eldap.search(connection, [
|
||||
{:base, to_charlist(base)},
|
||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||
{:scope, :eldap.wholeSubtree()},
|
||||
{:timeout, @search_timeout}
|
||||
]) do
|
||||
# The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
|
||||
# https://github.com/erlang/otp/pull/5538
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
|
||||
try_register(name, attributes)
|
||||
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
|
||||
try_register(name, attributes)
|
||||
|
||||
error ->
|
||||
Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
|
||||
{:error, {:ldap_search_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp try_register(name, attributes) do
|
||||
params = %{
|
||||
name: name,
|
||||
nickname: name,
|
||||
password: nil
|
||||
}
|
||||
|
||||
params =
|
||||
case List.keyfind(attributes, ~c"mail", 0) do
|
||||
{_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
|
||||
_ -> params
|
||||
end
|
||||
|
||||
changeset = User.register_changeset_ldap(%User{}, params)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} -> user
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
require Pleroma.Constants
|
||||
require Logger
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def block(blocked, blocker) do
|
||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||
|
|
@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec post_chat_message(User.t(), User.t(), String.t(), list()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | Pipeline.errors()
|
||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||
:ok <- validate_chat_attachment_attribution(maybe_attachment, user),
|
||||
|
|
@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
)} do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:common_pipeline, {:reject, _} = e} -> e
|
||||
{:common_pipeline, e} -> e
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
|
@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unblock(User.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking}
|
||||
def unblock(blocked, blocker) do
|
||||
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
||||
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
||||
|
|
@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec follow(User.t(), User.t()) ::
|
||||
{:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected}
|
||||
{:ok, User.t(), User.t(), Activity.t() | Object.t()}
|
||||
| {:error, :rejected}
|
||||
| Pipeline.errors()
|
||||
def follow(followed, follower) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
||||
|
|
@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors()
|
||||
def accept_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
|
||||
|
|
@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil
|
||||
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil
|
||||
def reject_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
|
||||
|
|
@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec delete(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()}
|
||||
def delete(activity_id, user) do
|
||||
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
||||
|
|
@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found}
|
||||
def repeat(id, user, params \\ %{}) do
|
||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
||||
object = %Object{} <- Object.normalize(activity, fetch: false),
|
||||
|
|
@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||
def unrepeat(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
|
|
@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec favorite(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()}
|
||||
def favorite(id, %User{} = user) do
|
||||
case favorite_helper(user, id) do
|
||||
{:ok, _} = res ->
|
||||
|
|
@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unfavorite(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||
def unfavorite(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
|
|
@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec react_with_emoji(String.t(), User.t(), String.t()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | {:error, String.t()}
|
||||
def react_with_emoji(id, user, emoji) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||
object <- Object.normalize(activity, fetch: false),
|
||||
|
|
@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec unreact_with_emoji(String.t(), User.t(), String.t()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | {:error, String.t()}
|
||||
def unreact_with_emoji(id, user, emoji) do
|
||||
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
|
||||
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)},
|
||||
|
|
@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()}
|
||||
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors()
|
||||
def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do
|
||||
with :ok <- validate_not_author(object, user),
|
||||
:ok <- validate_existing_votes(user, object),
|
||||
|
|
@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil}
|
||||
def update(orig_activity, %User{} = user, changes) do
|
||||
with orig_object <- Object.normalize(orig_activity),
|
||||
{:ok, new_object} <- make_update_data(user, orig_object, changes),
|
||||
|
|
@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def pin(id, %User{} = user) do
|
||||
with %Activity{} = activity <- create_activity_by_id(id),
|
||||
true <- activity_belongs_to_actor(activity, user.ap_id),
|
||||
|
|
@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def unpin(id, user) do
|
||||
with %Activity{} = activity <- create_activity_by_id(id),
|
||||
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
||||
|
|
@ -552,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()}
|
||||
def add_mute(activity, user, params \\ %{}) do
|
||||
expires_in = Map.get(params, :expires_in, 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ defmodule Pleroma.Web.Federator do
|
|||
|
||||
# NOTE: we use the actor ID to do the containment, this is fine because an
|
||||
# actor shouldn't be acting on objects outside their own AP server.
|
||||
with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
|
||||
with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
|
||||
{:user_active, true} <- {:user_active, match?(true, user.is_active)},
|
||||
nil <- Activity.normalize(params["id"]),
|
||||
{_, :ok} <-
|
||||
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
|
||||
|
|
@ -121,11 +122,6 @@ defmodule Pleroma.Web.Federator do
|
|||
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
|
||||
{:error, e}
|
||||
|
||||
{:error, {:validate_object, _}} = e ->
|
||||
Logger.error("Incoming AP doc validation error: #{inspect(e)}")
|
||||
Logger.debug(Jason.encode!(params, pretty: true))
|
||||
e
|
||||
|
||||
e ->
|
||||
# Just drop those for now
|
||||
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
||||
|
|
|
|||
|
|
@ -232,6 +232,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||
|> Maps.put_if_present(:birthday, params[:birthday])
|
||||
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
||||
|> Maps.put_if_present(:avatar_description, params[:avatar_description])
|
||||
|> Maps.put_if_present(:header_description, params[:header_description])
|
||||
|
||||
# What happens here:
|
||||
#
|
||||
|
|
@ -277,6 +279,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
|
||||
render_error(conn, :request_entity_too_large, "Name is too long")
|
||||
|
||||
{:error, %Ecto.Changeset{errors: [{:avatar_description, {_, _}} | _]}} ->
|
||||
render_error(conn, :request_entity_too_large, "Avatar description is too long")
|
||||
|
||||
{:error, %Ecto.Changeset{errors: [{:header_description, {_, _}} | _]}} ->
|
||||
render_error(conn, :request_entity_too_large, "Banner description is too long")
|
||||
|
||||
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
|
||||
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create)
|
||||
|
||||
plug(:skip_auth when action in [:create, :verify_credentials])
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
|
|
|||
|
|
@ -92,14 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
User.get_follow_state(reading_user, target)
|
||||
end
|
||||
|
||||
followed_by =
|
||||
if following_relationships do
|
||||
case FollowingRelationship.find(following_relationships, target, reading_user) do
|
||||
%{state: :follow_accept} -> true
|
||||
_ -> false
|
||||
end
|
||||
else
|
||||
User.following?(target, reading_user)
|
||||
followed_by = FollowingRelationship.following?(target, reading_user)
|
||||
following = FollowingRelationship.following?(reading_user, target)
|
||||
|
||||
requested =
|
||||
cond do
|
||||
following -> false
|
||||
true -> match?(:follow_pending, follow_state)
|
||||
end
|
||||
|
||||
subscribing =
|
||||
|
|
@ -114,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: follow_state == :follow_accept,
|
||||
following: following,
|
||||
followed_by: followed_by,
|
||||
blocking:
|
||||
UserRelationship.exists?(
|
||||
|
|
@ -150,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
),
|
||||
subscribing: subscribing,
|
||||
notifying: subscribing,
|
||||
requested: follow_state == :follow_pending,
|
||||
requested: requested,
|
||||
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||
showing_reblogs:
|
||||
not UserRelationship.exists?(
|
||||
|
|
@ -220,8 +219,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
avatar = User.avatar_url(user) |> MediaProxy.url()
|
||||
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
|
||||
avatar_description = User.image_description(user.avatar)
|
||||
header = User.banner_url(user) |> MediaProxy.url()
|
||||
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
|
||||
header_description = User.image_description(user.banner)
|
||||
|
||||
following_count =
|
||||
if !user.hide_follows_count or !user.hide_follows or self,
|
||||
|
|
@ -322,7 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
skip_thread_containment: user.skip_thread_containment,
|
||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||
accepts_chat_messages: user.accepts_chat_messages,
|
||||
favicon: favicon
|
||||
favicon: favicon,
|
||||
avatar_description: avatar_description,
|
||||
header_description: header_description
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
|
||||
response = %{
|
||||
id: to_string(notification.id),
|
||||
group_key: "ungrouped-" <> to_string(notification.id),
|
||||
type: notification.type,
|
||||
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
||||
account: account,
|
||||
|
|
|
|||
|
|
@ -465,7 +465,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
parent_visible: visible_for_user?(reply_to, opts[:for]),
|
||||
pinned_at: pinned_at,
|
||||
quotes_count: object.data["quotesCount"] || 0,
|
||||
bookmark_folder: bookmark_folder
|
||||
bookmark_folder: bookmark_folder,
|
||||
list_id: get_list_id(object, client_posted_this_activity)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -803,19 +804,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
defp build_application(_), do: nil
|
||||
|
||||
# Workaround for Elixir issue #10771
|
||||
# Avoid applying URI.merge unless necessary
|
||||
# TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
|
||||
# when Elixir 1.12 is the minimum supported version
|
||||
@spec build_image_url(struct() | nil, struct()) :: String.t() | nil
|
||||
defp build_image_url(
|
||||
%URI{scheme: image_scheme, host: image_host} = image_url_data,
|
||||
%URI{} = _page_url_data
|
||||
)
|
||||
when not is_nil(image_scheme) and not is_nil(image_host) do
|
||||
image_url_data |> to_string
|
||||
end
|
||||
|
||||
@spec build_image_url(URI.t(), URI.t()) :: String.t()
|
||||
defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
|
||||
URI.merge(page_url_data, image_url_data) |> to_string
|
||||
end
|
||||
|
|
@ -847,4 +836,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp get_list_id(object, client_posted_this_activity) do
|
||||
with true <- client_posted_this_activity,
|
||||
%{data: %{"listMessage" => list_ap_id}} when is_binary(list_ap_id) <- object,
|
||||
%{id: list_id} <- Pleroma.List.get_by_ap_id(list_ap_id) do
|
||||
list_id
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
|
|
@ -155,4 +156,29 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
Map.put(acc, key, error)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec maybe_update_owner(Token.t()) :: :ok
|
||||
def maybe_update_owner(%Token{app_id: app_id, user_id: user_id}) when not is_nil(user_id) do
|
||||
__MODULE__.update(app_id, %{user_id: user_id})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def maybe_update_owner(_), do: :ok
|
||||
|
||||
@spec remove_orphans(pos_integer()) :: :ok
|
||||
def remove_orphans(limit \\ 100) do
|
||||
fifteen_mins_ago = DateTime.add(DateTime.utc_now(), -900, :second)
|
||||
|
||||
Repo.transaction(fn ->
|
||||
from(a in __MODULE__,
|
||||
where: is_nil(a.user_id) and a.inserted_at < ^fifteen_mins_ago,
|
||||
limit: ^limit
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.each(&Repo.delete(&1))
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -318,6 +318,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
|
||||
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
|
||||
App.maybe_update_owner(token)
|
||||
|
||||
conn
|
||||
|> AuthHelper.put_session_token(token.token)
|
||||
|> json(OAuthView.render("token.json", view_params))
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
|
|||
Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
|
||||
end
|
||||
|
||||
def checkpw(password, "$argon2" <> _ = password_hash) do
|
||||
# Handle argon2 passwords for Akkoma migration
|
||||
Argon2.verify_pass(password, password_hash)
|
||||
end
|
||||
|
||||
def checkpw(_password, _password_hash) do
|
||||
Logger.error("Password hash not recognized")
|
||||
false
|
||||
|
|
@ -56,6 +61,10 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
|
|||
do_update_password(user, password)
|
||||
end
|
||||
|
||||
def maybe_update_password(%User{password_hash: "$argon2" <> _} = user, password) do
|
||||
do_update_password(user, password)
|
||||
end
|
||||
|
||||
def maybe_update_password(user, _), do: {:ok, user}
|
||||
|
||||
defp do_update_password(user, password) do
|
||||
|
|
|
|||
89
lib/pleroma/web/plugs/inbox_guard_plug.ex
Normal file
89
lib/pleroma/web/plugs/inbox_guard_plug.ex
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.InboxGuardPlug do
|
||||
import Plug.Conn
|
||||
import Pleroma.Constants, only: [activity_types: 0, allowed_activity_types_from_strangers: 0]
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||
with {_, true} <- {:federating, Config.get!([:instance, :federating])} do
|
||||
conn
|
||||
|> filter_activity_types()
|
||||
else
|
||||
{:federating, false} ->
|
||||
conn
|
||||
|> json(403, "Not federating")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _opts) do
|
||||
with {_, true} <- {:federating, Config.get!([:instance, :federating])},
|
||||
conn = filter_activity_types(conn),
|
||||
{:known, true} <- {:known, known_actor?(conn)} do
|
||||
conn
|
||||
else
|
||||
{:federating, false} ->
|
||||
conn
|
||||
|> json(403, "Not federating")
|
||||
|> halt()
|
||||
|
||||
{:known, false} ->
|
||||
conn
|
||||
|> filter_from_strangers()
|
||||
end
|
||||
end
|
||||
|
||||
# Early rejection of unrecognized types
|
||||
defp filter_activity_types(%{body_params: %{"type" => type}} = conn) do
|
||||
with true <- type in activity_types() do
|
||||
conn
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> json(400, "Invalid activity type")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
# If signature failed but we know this actor we should
|
||||
# accept it as we may only need to refetch their public key
|
||||
# during processing
|
||||
defp known_actor?(%{body_params: data}) do
|
||||
case Pleroma.Object.Containment.get_actor(data) |> User.get_cached_by_ap_id() do
|
||||
%User{} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
# Only permit a subset of activity types from strangers
|
||||
# or else it will add actors you've never interacted with
|
||||
# to the database
|
||||
defp filter_from_strangers(%{body_params: %{"type" => type}} = conn) do
|
||||
with true <- type in allowed_activity_types_from_strangers() do
|
||||
conn
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> json(400, "Invalid activity type for an unknown actor")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp json(conn, status, resp) do
|
||||
json_resp = Jason.encode!(resp)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> resp(status, json_resp)
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
|
@ -11,16 +11,39 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
|
||||
@spec rich_media_get(String.t()) :: {:ok, String.t()} | get_errors()
|
||||
def rich_media_get(url) do
|
||||
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
case Pleroma.HTTP.AdapterHelper.can_stream?() do
|
||||
true -> stream(url)
|
||||
false -> head_first(url)
|
||||
end
|
||||
|> handle_result(url)
|
||||
end
|
||||
|
||||
defp stream(url) do
|
||||
with {_, {:ok, %Tesla.Env{status: 200, body: stream_body, headers: headers}}} <-
|
||||
{:get, Pleroma.HTTP.get(url, req_headers(), http_options())},
|
||||
{_, :ok} <- {:content_type, check_content_type(headers)},
|
||||
{_, :ok} <- {:content_length, check_content_length(headers)},
|
||||
{:read_stream, {:ok, body}} <- {:read_stream, read_stream(stream_body)} do
|
||||
{:ok, body}
|
||||
end
|
||||
end
|
||||
|
||||
defp head_first(url) do
|
||||
with {_, {:ok, %Tesla.Env{status: 200, headers: headers}}} <-
|
||||
{:head, Pleroma.HTTP.head(url, headers, http_options())},
|
||||
{:head, Pleroma.HTTP.head(url, req_headers(), http_options())},
|
||||
{_, :ok} <- {:content_type, check_content_type(headers)},
|
||||
{_, :ok} <- {:content_length, check_content_length(headers)},
|
||||
{_, {:ok, %Tesla.Env{status: 200, body: body}}} <-
|
||||
{:get, Pleroma.HTTP.get(url, headers, http_options())} do
|
||||
{:get, Pleroma.HTTP.get(url, req_headers(), http_options())} do
|
||||
{:ok, body}
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_result(result, url) do
|
||||
case result do
|
||||
{:ok, body} ->
|
||||
{:ok, body}
|
||||
|
||||
{:head, _} ->
|
||||
Logger.debug("Rich media error for #{url}: HTTP HEAD failed")
|
||||
{:error, :head}
|
||||
|
|
@ -29,8 +52,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
Logger.debug("Rich media error for #{url}: content-type is #{type}")
|
||||
{:error, :content_type}
|
||||
|
||||
{:content_length, {_, length}} ->
|
||||
Logger.debug("Rich media error for #{url}: content-length is #{length}")
|
||||
{:content_length, :error} ->
|
||||
Logger.debug("Rich media error for #{url}: content-length exceeded")
|
||||
{:error, :body_too_large}
|
||||
|
||||
{:read_stream, :error} ->
|
||||
Logger.debug("Rich media error for #{url}: content-length exceeded")
|
||||
{:error, :body_too_large}
|
||||
|
||||
{:get, _} ->
|
||||
|
|
@ -59,7 +86,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
{_, maybe_content_length} ->
|
||||
case Integer.parse(maybe_content_length) do
|
||||
{content_length, ""} when content_length <= max_body -> :ok
|
||||
{_, ""} -> {:error, maybe_content_length}
|
||||
{_, ""} -> :error
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
|
|
@ -68,13 +95,37 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
end
|
||||
end
|
||||
|
||||
defp http_options do
|
||||
timeout = Config.get!([:rich_media, :timeout])
|
||||
defp read_stream(stream) do
|
||||
max_body = Keyword.get(http_options(), :max_body)
|
||||
|
||||
try do
|
||||
result =
|
||||
Stream.transform(stream, 0, fn chunk, total_bytes ->
|
||||
new_total = total_bytes + byte_size(chunk)
|
||||
|
||||
if new_total > max_body do
|
||||
raise("Exceeds max body limit of #{max_body}")
|
||||
else
|
||||
{[chunk], new_total}
|
||||
end
|
||||
end)
|
||||
|> Enum.into(<<>>)
|
||||
|
||||
{:ok, result}
|
||||
rescue
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp http_options do
|
||||
[
|
||||
pool: :rich_media,
|
||||
max_body: Config.get([:rich_media, :max_body], 5_000_000),
|
||||
tesla_middleware: [{Tesla.Middleware.Timeout, timeout: timeout}]
|
||||
stream: true
|
||||
]
|
||||
end
|
||||
|
||||
defp req_headers do
|
||||
[{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
pipeline :well_known do
|
||||
plug(:accepts, ["json", "jrd", "jrd+json", "xml", "xrd+xml"])
|
||||
plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml"])
|
||||
end
|
||||
|
||||
pipeline :config do
|
||||
|
|
@ -217,6 +217,10 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
end
|
||||
|
||||
pipeline :inbox_guard do
|
||||
plug(Pleroma.Web.Plugs.InboxGuardPlug)
|
||||
end
|
||||
|
||||
pipeline :static_fe do
|
||||
plug(Pleroma.Web.Plugs.StaticFEPlug)
|
||||
end
|
||||
|
|
@ -920,7 +924,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:activitypub)
|
||||
pipe_through([:activitypub, :inbox_guard])
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
post("/users/:nickname/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
|
|
|||
21
lib/pleroma/workers/cron/app_cleanup_worker.ex
Normal file
21
lib/pleroma/workers/cron/app_cleanup_worker.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.AppCleanupWorker do
|
||||
@moduledoc """
|
||||
Cleans up registered apps that were never associated with a user.
|
||||
"""
|
||||
|
||||
use Oban.Worker, queue: "background"
|
||||
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
@impl true
|
||||
def perform(_job) do
|
||||
App.remove_orphans()
|
||||
end
|
||||
|
||||
@impl true
|
||||
def timeout(_job), do: :timer.seconds(30)
|
||||
end
|
||||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
use Oban.Worker, queue: :federator_incoming, max_attempts: 5
|
||||
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
query_string: query_string
|
||||
}
|
||||
|
||||
with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
|
||||
with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
|
||||
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
|
||||
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
|
||||
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
|
||||
|
|
@ -56,17 +56,29 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
|
||||
def timeout(_job), do: :timer.seconds(5)
|
||||
|
||||
defp process_errors({:error, {:error, _} = error}), do: process_errors(error)
|
||||
|
||||
defp process_errors(errors) do
|
||||
case errors do
|
||||
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
|
||||
{:error, :already_present} -> {:cancel, :already_present}
|
||||
{:error, {:validate_object, _} = reason} -> {:cancel, reason}
|
||||
{:error, {:error, {:validate, {:error, _changeset} = reason}}} -> {:cancel, reason}
|
||||
{:error, {:reject, _} = reason} -> {:cancel, reason}
|
||||
{:signature, false} -> {:cancel, :invalid_signature}
|
||||
{:error, "Object has been deleted"} = reason -> {:cancel, reason}
|
||||
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
|
||||
# User fetch failures
|
||||
{:error, :not_found} = reason -> {:cancel, reason}
|
||||
{:error, :forbidden} = reason -> {:cancel, reason}
|
||||
# Inactive user
|
||||
{:error, {:user_active, false} = reason} -> {:cancel, reason}
|
||||
# Validator will error and return a changeset error
|
||||
# e.g., duplicate activities or if the object was deleted
|
||||
{:error, {:validate, {:error, _changeset} = reason}} -> {:cancel, reason}
|
||||
# Duplicate detection during Normalization
|
||||
{:error, :already_present} -> {:cancel, :already_present}
|
||||
# MRFs will return a reject
|
||||
{:error, {:reject, _} = reason} -> {:cancel, reason}
|
||||
# HTTP Sigs
|
||||
{:signature, false} -> {:cancel, :invalid_signature}
|
||||
# Origin / URL validation failed somewhere possibly due to spoofing
|
||||
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
|
||||
# Unclear if this can be reached
|
||||
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
|
||||
# Catchall
|
||||
{:error, _} = e -> e
|
||||
e -> {:error, e}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
||||
use Oban.Worker, queue: :background
|
||||
use Oban.Worker, queue: :background, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.RichMediaWorker do
|
|||
alias Pleroma.Web.RichMedia.Backfill
|
||||
alias Pleroma.Web.RichMedia.Card
|
||||
|
||||
use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300]
|
||||
use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.UserRefreshWorker do
|
||||
use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: 300]
|
||||
use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity]
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.Push.Impl
|
||||
|
||||
use Oban.Worker, queue: :web_push
|
||||
use Oban.Worker, queue: :web_push, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue