Resolve merge conflicts
This commit is contained in:
commit
196cad46f3
114 changed files with 3178 additions and 914 deletions
|
|
@ -28,6 +28,14 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
|
||||
|> Enum.each(fn {k, v} ->
|
||||
key = to_string(k) |> String.replace("Elixir.", "")
|
||||
|
||||
key =
|
||||
if String.starts_with?(key, "Pleroma.") do
|
||||
key
|
||||
else
|
||||
":" <> key
|
||||
end
|
||||
|
||||
{:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
|
||||
Mix.shell().info("#{key} is migrated.")
|
||||
end)
|
||||
|
|
@ -53,17 +61,9 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
Repo.all(Config)
|
||||
|> Enum.each(fn config ->
|
||||
mark =
|
||||
if String.starts_with?(config.key, "Pleroma.") or
|
||||
String.starts_with?(config.key, "Ueberauth"),
|
||||
do: ",",
|
||||
else: ":"
|
||||
|
||||
IO.write(
|
||||
file,
|
||||
"config :#{config.group}, #{config.key}#{mark} #{
|
||||
inspect(Config.from_binary(config.value))
|
||||
}\r\n"
|
||||
"config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
|
||||
)
|
||||
|
||||
if delete? do
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
mix pleroma.user unsubscribe NICKNAME
|
||||
|
||||
## Unsubscribe local users from an entire instance and deactivate all accounts
|
||||
|
||||
mix pleroma.user unsubscribe_all_from_instance INSTANCE
|
||||
|
||||
## Create a password reset link.
|
||||
|
||||
mix pleroma.user reset_password NICKNAME
|
||||
|
|
@ -246,6 +250,20 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["unsubscribe_all_from_instance", instance]) do
|
||||
start_pleroma()
|
||||
|
||||
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
|
||||
|> Pleroma.RepoStreamer.chunk_stream(500)
|
||||
|> Stream.each(fn users ->
|
||||
users
|
||||
|> Enum.each(fn user ->
|
||||
run(["unsubscribe", user.nickname])
|
||||
end)
|
||||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def run(["set", nickname | rest]) do
|
||||
start_pleroma()
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,11 @@ defmodule Pleroma.Application do
|
|||
id: :federator_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
|
||||
restart: :temporary
|
||||
},
|
||||
%{
|
||||
id: :internal_fetch_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
] ++
|
||||
streamer_child() ++
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ defmodule Pleroma.Config.TransferTask do
|
|||
if String.starts_with?(setting.key, "Pleroma.") do
|
||||
"Elixir." <> setting.key
|
||||
else
|
||||
setting.key
|
||||
String.trim_leading(setting.key, ":")
|
||||
end
|
||||
|
||||
group = String.to_existing_atom(setting.group)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ defmodule Pleroma.HTTP.Connection do
|
|||
|
||||
# fetch Hackney options
|
||||
#
|
||||
defp hackney_options(opts) do
|
||||
def hackney_options(opts) do
|
||||
options = Keyword.get(opts, :adapter, [])
|
||||
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
||||
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ defmodule Pleroma.HTTP do
|
|||
end
|
||||
|
||||
def process_request_options(options) do
|
||||
case Pleroma.Config.get([:http, :proxy_url]) do
|
||||
nil -> options
|
||||
proxy -> options ++ [proxy: proxy]
|
||||
end
|
||||
Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ defmodule Pleroma.Keys do
|
|||
end
|
||||
|
||||
def keys_from_pem(pem) do
|
||||
[private_key_code] = :public_key.pem_decode(pem)
|
||||
private_key = :public_key.pem_entry_decode(private_key_code)
|
||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
|
||||
public_key = {:RSAPublicKey, modulus, exponent}
|
||||
{:ok, private_key, public_key}
|
||||
with [private_key_code] <- :public_key.pem_decode(pem),
|
||||
private_key <- :public_key.pem_entry_decode(private_key_code),
|
||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
|
||||
{:ok, private_key, {:RSAPublicKey, modulus, exponent}}
|
||||
else
|
||||
error -> {:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.List do
|
|||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
field(:title, :string)
|
||||
field(:following, {:array, :string}, default: [])
|
||||
field(:ap_id, :string)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
@ -55,6 +56,10 @@ defmodule Pleroma.List do
|
|||
Repo.one(query)
|
||||
end
|
||||
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.get_by(__MODULE__, ap_id: ap_id)
|
||||
end
|
||||
|
||||
def get_following(%Pleroma.List{following: following} = _list) do
|
||||
q =
|
||||
from(
|
||||
|
|
@ -105,7 +110,14 @@ defmodule Pleroma.List do
|
|||
|
||||
def create(title, %User{} = creator) do
|
||||
list = %Pleroma.List{user_id: creator.id, title: title}
|
||||
Repo.insert(list)
|
||||
|
||||
Repo.transaction(fn ->
|
||||
list = Repo.insert!(list)
|
||||
|
||||
list
|
||||
|> change(ap_id: "#{creator.ap_id}/lists/#{list.id}")
|
||||
|> Repo.update!()
|
||||
end)
|
||||
end
|
||||
|
||||
def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
|
||||
|
|
@ -125,4 +137,19 @@ defmodule Pleroma.List do
|
|||
|> follow_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def memberships(%User{follower_address: follower_address}) do
|
||||
Pleroma.List
|
||||
|> where([l], ^follower_address in l.following)
|
||||
|> select([l], l.ap_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def memberships(_), do: []
|
||||
|
||||
def member?(%Pleroma.List{following: following}, %User{follower_address: follower_address}) do
|
||||
Enum.member?(following, follower_address)
|
||||
end
|
||||
|
||||
def member?(_, _), do: false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Notification do
|
|||
alias Pleroma.Pagination
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Streamer
|
||||
|
|
@ -32,31 +31,47 @@ defmodule Pleroma.Notification do
|
|||
|> cast(attrs, [:seen])
|
||||
end
|
||||
|
||||
def for_user_query(user) do
|
||||
Notification
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(
|
||||
[n, a],
|
||||
fragment(
|
||||
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||
a.actor
|
||||
)
|
||||
)
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> join(:left, [n, a], object in Object,
|
||||
on:
|
||||
def for_user_query(user, opts) do
|
||||
query =
|
||||
Notification
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(
|
||||
[n, a],
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||
object.data,
|
||||
a.data
|
||||
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||
a.actor
|
||||
)
|
||||
)
|
||||
|> preload([n, a, o], activity: {a, object: o})
|
||||
)
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> join(:left, [n, a], object in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||
object.data,
|
||||
a.data
|
||||
)
|
||||
)
|
||||
|> preload([n, a, o], activity: {a, object: o})
|
||||
|
||||
if opts[:with_muted] do
|
||||
query
|
||||
else
|
||||
where(query, [n, a], a.actor not in ^user.info.muted_notifications)
|
||||
|> where([n, a], a.actor not in ^user.info.blocks)
|
||||
|> where(
|
||||
[n, a],
|
||||
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
|
||||
)
|
||||
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||
)
|
||||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||
end
|
||||
end
|
||||
|
||||
def for_user(user, opts \\ %{}) do
|
||||
user
|
||||
|> for_user_query()
|
||||
|> for_user_query(opts)
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
end
|
||||
|
||||
|
|
@ -179,11 +194,10 @@ defmodule Pleroma.Notification do
|
|||
|
||||
def get_notified_from_activity(_, _local_only), do: []
|
||||
|
||||
@spec skip?(Activity.t(), User.t()) :: boolean()
|
||||
def skip?(activity, user) do
|
||||
[
|
||||
:self,
|
||||
:blocked,
|
||||
:muted,
|
||||
:followers,
|
||||
:follows,
|
||||
:non_followers,
|
||||
|
|
@ -193,21 +207,11 @@ defmodule Pleroma.Notification do
|
|||
|> Enum.any?(&skip?(&1, activity, user))
|
||||
end
|
||||
|
||||
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
||||
def skip?(:self, activity, user) do
|
||||
activity.data["actor"] == user.ap_id
|
||||
end
|
||||
|
||||
def skip?(:blocked, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
User.blocks?(user, %{ap_id: actor})
|
||||
end
|
||||
|
||||
def skip?(:muted, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
|
||||
User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
|
||||
end
|
||||
|
||||
def skip?(
|
||||
:followers,
|
||||
activity,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ defmodule Pleroma.Object.Containment do
|
|||
end
|
||||
end
|
||||
|
||||
def contain_origin(id, %{"attributedTo" => actor} = params),
|
||||
do: contain_origin(id, Map.put(params, "actor", actor))
|
||||
|
||||
def contain_origin_from_id(_id, %{"id" => nil}), do: :error
|
||||
|
||||
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
||||
|
|
@ -60,4 +63,9 @@ defmodule Pleroma.Object.Containment do
|
|||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
||||
do: contain_origin(id, object)
|
||||
|
||||
def contain_child(_), do: :ok
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Object.Fetcher do
|
|||
alias Pleroma.HTTP
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.OStatus
|
||||
|
||||
|
|
@ -32,33 +34,39 @@ defmodule Pleroma.Object.Fetcher do
|
|||
else
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
||||
nil <- Object.normalize(data, false),
|
||||
with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
|
||||
params <- %{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
# Should we seriously keep this attributedTo thing?
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"object" => data
|
||||
},
|
||||
:ok <- Containment.contain_origin(id, params),
|
||||
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params, options),
|
||||
{:object, _data, %Object{} = object} <-
|
||||
{:object, data, Object.normalize(activity, false)} do
|
||||
{:ok, object}
|
||||
else
|
||||
{:containment, _} ->
|
||||
{:error, "Object containment failed."}
|
||||
|
||||
{:error, {:reject, nil}} ->
|
||||
{:reject, nil}
|
||||
|
||||
{:object, data, nil} ->
|
||||
reinject_object(data)
|
||||
|
||||
object = %Object{} ->
|
||||
{:normalize, object = %Object{}} ->
|
||||
{:ok, object}
|
||||
|
||||
_e ->
|
||||
# Only fallback when receiving a fetch/normalization error with ActivityPub
|
||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||
|
||||
# FIXME: OStatus Object Containment?
|
||||
case OStatus.fetch_activity_from_url(id) do
|
||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
||||
e -> e
|
||||
|
|
@ -76,15 +84,52 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
defp make_signature(id, date) do
|
||||
uri = URI.parse(id)
|
||||
|
||||
signature =
|
||||
InternalFetchActor.get_actor()
|
||||
|> Signature.sign(%{
|
||||
"(request-target)": "get #{uri.path}",
|
||||
host: uri.host,
|
||||
date: date
|
||||
})
|
||||
|
||||
[{:Signature, signature}]
|
||||
end
|
||||
|
||||
defp sign_fetch(headers, id, date) do
|
||||
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
|
||||
headers ++ make_signature(id, date)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_date_fetch(headers, date) do
|
||||
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
|
||||
headers ++ [{:Date, date}]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||
Logger.info("Fetching object #{id} via AP")
|
||||
|
||||
date =
|
||||
NaiveDateTime.utc_now()
|
||||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
|
||||
headers =
|
||||
[{:Accept, "application/activity+json"}]
|
||||
|> maybe_date_fetch(date)
|
||||
|> sign_fetch(id, date)
|
||||
|
||||
Logger.debug("Fetch headers: #{inspect(headers)}")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status: code}} when code in 200..299 <-
|
||||
HTTP.get(
|
||||
id,
|
||||
[{:Accept, "application/activity+json"}]
|
||||
),
|
||||
{:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||
{:ok, data}
|
||||
|
|
@ -97,8 +142,8 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(%{"id" => id), do: fetch_and_contain_remote_object_from_id(id)
|
||||
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
|
||||
do: fetch_and_contain_remote_object_from_id(id)
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"}
|
||||
{:error, "id must be a string"}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,9 +6,21 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
|
|||
alias Comeonin.Pbkdf2
|
||||
import Plug.Conn
|
||||
alias Pleroma.User
|
||||
require Logger
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
def init(options), do: options
|
||||
|
||||
def checkpw(password, "$6" <> _ = password_hash) do
|
||||
:crypt.crypt(password, password_hash) == password_hash
|
||||
end
|
||||
|
||||
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
|
||||
Pbkdf2.checkpw(password, password_hash)
|
||||
end
|
||||
|
||||
def checkpw(_password, _password_hash) do
|
||||
Logger.error("Password hash not recognized")
|
||||
false
|
||||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
|
|
@ -16,38 +15,30 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
end
|
||||
|
||||
def call(conn, _opts) do
|
||||
user = Utils.get_ap_id(conn.params["actor"])
|
||||
Logger.debug("Checking sig for #{user}")
|
||||
[signature | _] = get_req_header(conn, "signature")
|
||||
|
||||
cond do
|
||||
signature && String.contains?(signature, user) ->
|
||||
# set (request-target) header to the appropriate value
|
||||
# we also replace the digest header with the one we computed
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"(request-target)",
|
||||
String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
)
|
||||
|
||||
conn =
|
||||
if conn.assigns[:digest] do
|
||||
conn
|
||||
|> put_req_header("digest", conn.assigns[:digest])
|
||||
else
|
||||
conn
|
||||
end
|
||||
|
||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
|
||||
signature ->
|
||||
Logger.debug("Signature not from actor")
|
||||
assign(conn, :valid_signature, false)
|
||||
|
||||
true ->
|
||||
Logger.debug("No signature header!")
|
||||
if signature do
|
||||
# set (request-target) header to the appropriate value
|
||||
# we also replace the digest header with the one we computed
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"(request-target)",
|
||||
String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
)
|
||||
|
||||
conn =
|
||||
if conn.assigns[:digest] do
|
||||
conn
|
||||
|> put_req_header("digest", conn.assigns[:digest])
|
||||
else
|
||||
conn
|
||||
end
|
||||
|
||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
70
lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
Normal file
70
lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
def init(options), do: options
|
||||
|
||||
defp key_id_from_conn(conn) do
|
||||
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do
|
||||
Signature.key_id_to_actor_id(key_id)
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp user_from_key_id(conn) do
|
||||
with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
|
||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
|
||||
user
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def call(%{assigns: %{user: _}} = conn, _opts), do: conn
|
||||
|
||||
# if this has payload make sure it is signed by the same actor that made it
|
||||
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
|
||||
with actor_id <- Utils.get_ap_id(actor),
|
||||
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
|
||||
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
|
||||
assign(conn, :user, user)
|
||||
else
|
||||
{:user_match, false} ->
|
||||
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
||||
assign(conn, :valid_signature, false)
|
||||
|
||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||
{:user, nil} ->
|
||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
# no payload, probably a signed fetch
|
||||
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||
with %User{} = user <- user_from_key_id(conn) do
|
||||
assign(conn, :user, user)
|
||||
else
|
||||
_ ->
|
||||
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}")
|
||||
assign(conn, :valid_signature, false)
|
||||
end
|
||||
end
|
||||
|
||||
# no signature at all
|
||||
def call(conn, _opts), do: conn
|
||||
end
|
||||
|
|
@ -31,12 +31,28 @@ defmodule Pleroma.Plugs.RateLimiter do
|
|||
|
||||
## Usage
|
||||
|
||||
AllowedSyntax:
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :limiter_name)
|
||||
plug(Pleroma.Plugs.RateLimiter, {:limiter_name, options})
|
||||
|
||||
Allowed options:
|
||||
|
||||
* `bucket_name` overrides bucket name (e.g. to have a separate limit for a set of actions)
|
||||
* `params` appends values of specified request params (e.g. ["id"]) to bucket name
|
||||
|
||||
Inside a controller:
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
|
||||
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
|
||||
|
||||
or inside a router pipiline:
|
||||
plug(
|
||||
Pleroma.Plugs.RateLimiter,
|
||||
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||
when action in ~w(fav_status unfav_status)a
|
||||
)
|
||||
|
||||
or inside a router pipeline:
|
||||
|
||||
pipeline :api do
|
||||
...
|
||||
|
|
@ -49,33 +65,56 @@ defmodule Pleroma.Plugs.RateLimiter do
|
|||
|
||||
alias Pleroma.User
|
||||
|
||||
def init(limiter_name) do
|
||||
def init(limiter_name) when is_atom(limiter_name) do
|
||||
init({limiter_name, []})
|
||||
end
|
||||
|
||||
def init({limiter_name, opts}) do
|
||||
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
||||
nil -> nil
|
||||
config -> {limiter_name, config}
|
||||
config -> {limiter_name, config, opts}
|
||||
end
|
||||
end
|
||||
|
||||
# do not limit if there is no limiter configuration
|
||||
# Do not limit if there is no limiter configuration
|
||||
def call(conn, nil), do: conn
|
||||
|
||||
def call(conn, opts) do
|
||||
case check_rate(conn, opts) do
|
||||
{:ok, _count} -> conn
|
||||
{:error, _count} -> render_throttled_error(conn)
|
||||
def call(conn, settings) do
|
||||
case check_rate(conn, settings) do
|
||||
{:ok, _count} ->
|
||||
conn
|
||||
|
||||
{:error, _count} ->
|
||||
render_throttled_error(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp check_rate(%{assigns: %{user: %User{id: user_id}}}, {limiter_name, [_, {scale, limit}]}) do
|
||||
ExRated.check_rate("#{limiter_name}:#{user_id}", scale, limit)
|
||||
defp bucket_name(conn, limiter_name, opts) do
|
||||
bucket_name = opts[:bucket_name] || limiter_name
|
||||
|
||||
if params_names = opts[:params] do
|
||||
params_values = for p <- Enum.sort(params_names), do: conn.params[p]
|
||||
Enum.join([bucket_name] ++ params_values, ":")
|
||||
else
|
||||
bucket_name
|
||||
end
|
||||
end
|
||||
|
||||
defp check_rate(conn, {limiter_name, [{scale, limit} | _]}) do
|
||||
ExRated.check_rate("#{limiter_name}:#{ip(conn)}", scale, limit)
|
||||
defp check_rate(
|
||||
%{assigns: %{user: %User{id: user_id}}} = conn,
|
||||
{limiter_name, [_, {scale, limit}], opts}
|
||||
) do
|
||||
bucket_name = bucket_name(conn, limiter_name, opts)
|
||||
ExRated.check_rate("#{bucket_name}:#{user_id}", scale, limit)
|
||||
end
|
||||
|
||||
defp check_rate(conn, {limiter_name, {scale, limit}}) do
|
||||
check_rate(conn, {limiter_name, [{scale, limit}]})
|
||||
defp check_rate(conn, {limiter_name, [{scale, limit} | _], opts}) do
|
||||
bucket_name = bucket_name(conn, limiter_name, opts)
|
||||
ExRated.check_rate("#{bucket_name}:#{ip(conn)}", scale, limit)
|
||||
end
|
||||
|
||||
defp check_rate(conn, {limiter_name, {scale, limit}, opts}) do
|
||||
check_rate(conn, {limiter_name, [{scale, limit}, {scale, limit}], opts})
|
||||
end
|
||||
|
||||
def ip(%{remote_ip: remote_ip}) do
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ defmodule Pleroma.ReverseProxy do
|
|||
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
||||
|
||||
"""
|
||||
@default_hackney_options []
|
||||
@default_hackney_options [pool: :media]
|
||||
|
||||
@inline_content_types [
|
||||
"image/gif",
|
||||
|
|
@ -94,7 +94,8 @@ defmodule Pleroma.ReverseProxy do
|
|||
|
||||
def call(conn = %{method: method}, url, opts) when method in @methods do
|
||||
hackney_opts =
|
||||
@default_hackney_options
|
||||
Pleroma.HTTP.Connection.hackney_options([])
|
||||
|> Keyword.merge(@default_hackney_options)
|
||||
|> Keyword.merge(Keyword.get(opts, :http, []))
|
||||
|> HTTP.process_request_options()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,16 @@ defmodule Pleroma.Signature do
|
|||
alias Pleroma.Keys
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
def key_id_to_actor_id(key_id) do
|
||||
URI.parse(key_id)
|
||||
|> Map.put(:fragment, nil)
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
actor_id <- key_id_to_actor_id(kid),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
|
|
@ -21,7 +27,8 @@ defmodule Pleroma.Signature do
|
|||
end
|
||||
|
||||
def refetch_public_key(conn) do
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
actor_id <- key_id_to_actor_id(kid),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,19 @@ defmodule Pleroma.Upload.Filter.Dedupe do
|
|||
@behaviour Pleroma.Upload.Filter
|
||||
alias Pleroma.Upload
|
||||
|
||||
def filter(%Upload{name: name} = upload) do
|
||||
extension = String.split(name, ".") |> List.last()
|
||||
shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
|
||||
def filter(%Upload{name: name, tempfile: tempfile} = upload) do
|
||||
extension =
|
||||
name
|
||||
|> String.split(".")
|
||||
|> List.last()
|
||||
|
||||
shasum =
|
||||
:crypto.hash(:sha256, File.read!(tempfile))
|
||||
|> Base.encode16(case: :lower)
|
||||
|
||||
filename = shasum <> "." <> extension
|
||||
{:ok, %Upload{upload | id: shasum, path: filename}}
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Upload.Filter.Mogrifun do
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
alias Pleroma.Upload.Filter
|
||||
|
||||
@filters [
|
||||
{"implode", "1"},
|
||||
|
|
@ -34,31 +35,10 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
|
|||
]
|
||||
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
filter = Enum.random(@filters)
|
||||
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> mogrify_filter(filter)
|
||||
|> Mogrify.save(in_place: true)
|
||||
Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
|
||||
defp mogrify_filter(mogrify, [filter | rest]) do
|
||||
mogrify
|
||||
|> mogrify_filter(filter)
|
||||
|> mogrify_filter(rest)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, []), do: mogrify
|
||||
|
||||
defp mogrify_filter(mogrify, {action, options}) do
|
||||
Mogrify.custom(mogrify, action, options)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, string) when is_binary(string) do
|
||||
Mogrify.custom(mogrify, string)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,16 +11,19 @@ defmodule Pleroma.Upload.Filter.Mogrify do
|
|||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
filters = Pleroma.Config.get!([__MODULE__, :args])
|
||||
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> mogrify_filter(filters)
|
||||
|> Mogrify.save(in_place: true)
|
||||
|
||||
do_filter(file, filters)
|
||||
:ok
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
|
||||
def do_filter(file, filters) do
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> mogrify_filter(filters)
|
||||
|> Mogrify.save(in_place: true)
|
||||
end
|
||||
|
||||
defp mogrify_filter(mogrify, nil), do: mogrify
|
||||
|
||||
defp mogrify_filter(mogrify, [filter | rest]) do
|
||||
|
|
|
|||
|
|
@ -68,7 +68,14 @@ defmodule Pleroma.Uploaders.Uploader do
|
|||
{:error, error}
|
||||
end
|
||||
after
|
||||
30_000 -> {:error, dgettext("errors", "Uploader callback timeout")}
|
||||
callback_timeout() -> {:error, dgettext("errors", "Uploader callback timeout")}
|
||||
end
|
||||
end
|
||||
|
||||
defp callback_timeout do
|
||||
case Mix.env() do
|
||||
:test -> 1_000
|
||||
_ -> 30_000
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -707,9 +707,7 @@ defmodule Pleroma.User do
|
|||
user
|
||||
else
|
||||
e ->
|
||||
Logger.error(
|
||||
"Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}"
|
||||
)
|
||||
Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
|
||||
|
||||
user
|
||||
end
|
||||
|
|
@ -798,10 +796,13 @@ defmodule Pleroma.User do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
def mute(muter, %User{ap_id: ap_id}) do
|
||||
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
|
||||
info = muter.info
|
||||
|
||||
info_cng =
|
||||
muter.info
|
||||
|> User.Info.add_to_mutes(ap_id)
|
||||
User.Info.add_to_mutes(info, ap_id)
|
||||
|> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
|
||||
|
||||
cng =
|
||||
change(muter)
|
||||
|
|
@ -811,9 +812,11 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def unmute(muter, %{ap_id: ap_id}) do
|
||||
info = muter.info
|
||||
|
||||
info_cng =
|
||||
muter.info
|
||||
|> User.Info.remove_from_mutes(ap_id)
|
||||
User.Info.remove_from_mutes(info, ap_id)
|
||||
|> User.Info.remove_from_muted_notifications(info, ap_id)
|
||||
|
||||
cng =
|
||||
change(muter)
|
||||
|
|
@ -909,6 +912,12 @@ defmodule Pleroma.User do
|
|||
def mutes?(nil, _), do: false
|
||||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
||||
|
||||
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
|
||||
def muted_notifications?(nil, _), do: false
|
||||
|
||||
def muted_notifications?(user, %{ap_id: ap_id}),
|
||||
do: Enum.member?(user.info.muted_notifications, ap_id)
|
||||
|
||||
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
|
||||
blocks = info.blocks
|
||||
domain_blocks = info.domain_blocks
|
||||
|
|
@ -1195,19 +1204,18 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def get_or_create_instance_user do
|
||||
relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
|
||||
if user = get_cached_by_ap_id(relay_uri) do
|
||||
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
|
||||
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
|
||||
if user = get_cached_by_ap_id(uri) do
|
||||
user
|
||||
else
|
||||
changes =
|
||||
%User{info: %User.Info{}}
|
||||
|> cast(%{}, [:ap_id, :nickname, :local])
|
||||
|> put_change(:ap_id, relay_uri)
|
||||
|> put_change(:nickname, nil)
|
||||
|> put_change(:ap_id, uri)
|
||||
|> put_change(:nickname, nickname)
|
||||
|> put_change(:local, true)
|
||||
|> put_change(:follower_address, relay_uri <> "/followers")
|
||||
|> put_change(:follower_address, uri <> "/followers")
|
||||
|
||||
{:ok, user} = Repo.insert(changes)
|
||||
user
|
||||
|
|
@ -1228,10 +1236,12 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
# OStatus Magic Key
|
||||
def public_key_from_info(%{magic_key: magic_key}) do
|
||||
def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
|
||||
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
|
||||
end
|
||||
|
||||
def public_key_from_info(_), do: {:error, "not found key"}
|
||||
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key_from_info(user.info) do
|
||||
|
|
@ -1248,7 +1258,7 @@ defmodule Pleroma.User do
|
|||
data
|
||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||
|> remote_user_creation()
|
||||
|> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
|
||||
|> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
|
||||
|> set_cache()
|
||||
end
|
||||
|
||||
|
|
@ -1417,23 +1427,16 @@ defmodule Pleroma.User do
|
|||
}
|
||||
end
|
||||
|
||||
def ensure_keys_present(user) do
|
||||
info = user.info
|
||||
|
||||
def ensure_keys_present(%User{info: info} = user) do
|
||||
if info.keys do
|
||||
{:ok, user}
|
||||
else
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
|
||||
info_cng =
|
||||
info
|
||||
|> User.Info.set_keys(pem)
|
||||
|
||||
cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|> Ecto.Changeset.put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
user
|
||||
|> Ecto.Changeset.change()
|
||||
|> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1454,4 +1457,8 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp put_password_hash(changeset), do: changeset
|
||||
|
||||
def is_internal_user?(%User{nickname: nil}), do: true
|
||||
def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
|
||||
def is_internal_user?(_), do: false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ defmodule Pleroma.User.Info do
|
|||
field(:domain_blocks, {:array, :string}, default: [])
|
||||
field(:mutes, {:array, :string}, default: [])
|
||||
field(:muted_reblogs, {:array, :string}, default: [])
|
||||
field(:muted_notifications, {:array, :string}, default: [])
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
field(:deactivated, :boolean, default: false)
|
||||
field(:no_rich_text, :boolean, default: false)
|
||||
|
|
@ -122,6 +123,16 @@ defmodule Pleroma.User.Info do
|
|||
|> validate_required([:mutes])
|
||||
end
|
||||
|
||||
@spec set_notification_mutes(Changeset.t(), [String.t()], boolean()) :: Changeset.t()
|
||||
def set_notification_mutes(changeset, muted_notifications, notifications?) do
|
||||
if notifications? do
|
||||
put_change(changeset, :muted_notifications, muted_notifications)
|
||||
|> validate_required([:muted_notifications])
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
def set_blocks(info, blocks) do
|
||||
params = %{blocks: blocks}
|
||||
|
||||
|
|
@ -138,14 +149,31 @@ defmodule Pleroma.User.Info do
|
|||
|> validate_required([:subscribers])
|
||||
end
|
||||
|
||||
@spec add_to_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||
def add_to_mutes(info, muted) do
|
||||
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
||||
end
|
||||
|
||||
@spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
|
||||
Changeset.t()
|
||||
def add_to_muted_notifications(changeset, info, muted, notifications?) do
|
||||
set_notification_mutes(
|
||||
changeset,
|
||||
Enum.uniq([muted | info.muted_notifications]),
|
||||
notifications?
|
||||
)
|
||||
end
|
||||
|
||||
@spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||
def remove_from_mutes(info, muted) do
|
||||
set_mutes(info, List.delete(info.mutes, muted))
|
||||
end
|
||||
|
||||
@spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
|
||||
def remove_from_muted_notifications(changeset, info, muted) do
|
||||
set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
|
||||
end
|
||||
|
||||
def add_to_block(info, blocked) do
|
||||
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Conversation
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -26,19 +27,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
# For Announce activities, we filter the recipients based on following status for any actors
|
||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||
to = data["to"] || []
|
||||
cc = data["cc"] || []
|
||||
to = Map.get(data, "to", [])
|
||||
cc = Map.get(data, "cc", [])
|
||||
bcc = Map.get(data, "bcc", [])
|
||||
actor = User.get_cached_by_ap_id(data["actor"])
|
||||
|
||||
recipients =
|
||||
(to ++ cc)
|
||||
|> Enum.filter(fn recipient ->
|
||||
Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
|
||||
case User.get_cached_by_ap_id(recipient) do
|
||||
nil ->
|
||||
true
|
||||
|
||||
user ->
|
||||
User.following?(user, actor)
|
||||
nil -> true
|
||||
user -> User.following?(user, actor)
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
@ -46,17 +44,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
defp get_recipients(%{"type" => "Create"} = data) do
|
||||
to = data["to"] || []
|
||||
cc = data["cc"] || []
|
||||
actor = data["actor"] || []
|
||||
recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
|
||||
to = Map.get(data, "to", [])
|
||||
cc = Map.get(data, "cc", [])
|
||||
bcc = Map.get(data, "bcc", [])
|
||||
actor = Map.get(data, "actor", [])
|
||||
recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
|
||||
{recipients, to, cc}
|
||||
end
|
||||
|
||||
defp get_recipients(data) do
|
||||
to = data["to"] || []
|
||||
cc = data["cc"] || []
|
||||
recipients = to ++ cc
|
||||
to = Map.get(data, "to", [])
|
||||
cc = Map.get(data, "cc", [])
|
||||
bcc = Map.get(data, "bcc", [])
|
||||
recipients = Enum.concat([to, cc, bcc])
|
||||
{recipients, to, cc}
|
||||
end
|
||||
|
||||
|
|
@ -126,6 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
{:ok, map} <- MRF.filter(map),
|
||||
{recipients, _, _} = get_recipients(map),
|
||||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||
:ok <- Containment.contain_child(map),
|
||||
{:ok, map, object} <- insert_full_object(map) do
|
||||
{:ok, activity} =
|
||||
Repo.insert(%Activity{
|
||||
|
|
@ -896,13 +897,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp maybe_order(query, _), do: query
|
||||
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
base_query = from(activity in Activity)
|
||||
|
||||
config = %{
|
||||
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
||||
}
|
||||
|
||||
base_query
|
||||
Activity
|
||||
|> maybe_preload_objects(opts)
|
||||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|
|
@ -931,11 +930,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
fetch_activities_query(recipients, opts)
|
||||
list_memberships = Pleroma.List.memberships(opts["user"])
|
||||
|
||||
fetch_activities_query(recipients ++ list_memberships, opts)
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
|> Enum.reverse()
|
||||
|> maybe_update_cc(list_memberships, opts["user"])
|
||||
end
|
||||
|
||||
defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
|
||||
when is_list(list_memberships) and length(list_memberships) > 0 do
|
||||
Enum.map(activities, fn
|
||||
%{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
|
||||
if Enum.any?(bcc, &(&1 in list_memberships)) do
|
||||
update_in(activity.data["cc"], &[user_ap_id | &1])
|
||||
else
|
||||
activity
|
||||
end
|
||||
|
||||
activity ->
|
||||
activity
|
||||
end)
|
||||
end
|
||||
|
||||
defp maybe_update_cc(activities, _, _), do: activities
|
||||
|
||||
def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
|
||||
from(activity in query,
|
||||
where:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
|
@ -206,9 +207,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
json(conn, dgettext("errors", "error"))
|
||||
end
|
||||
|
||||
def relay(conn, _params) do
|
||||
with %User{} = user <- Relay.get_actor(),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
defp represent_service_actor(%User{} = user, conn) do
|
||||
with {:ok, user} <- User.ensure_keys_present(user) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("user.json", %{user: user}))
|
||||
|
|
@ -217,6 +217,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
defp represent_service_actor(nil, _), do: {:error, :not_found}
|
||||
|
||||
def relay(conn, _params) do
|
||||
Relay.get_actor()
|
||||
|> represent_service_actor(conn)
|
||||
end
|
||||
|
||||
def internal_fetch(conn, _params) do
|
||||
InternalFetchActor.get_actor()
|
||||
|> represent_service_actor(conn)
|
||||
end
|
||||
|
||||
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|
|
|
|||
20
lib/pleroma/web/activity_pub/internal_fetch_actor.ex
Normal file
20
lib/pleroma/web/activity_pub/internal_fetch_actor.ex
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.InternalFetchActor do
|
||||
alias Pleroma.User
|
||||
|
||||
require Logger
|
||||
|
||||
def init do
|
||||
# Wait for everything to settle.
|
||||
Process.sleep(1000 * 5)
|
||||
get_actor()
|
||||
end
|
||||
|
||||
def get_actor do
|
||||
"#{Pleroma.Web.Endpoint.url()}/internal/fetch"
|
||||
|> User.get_or_create_service_actor_by_ap_id("internal.fetch")
|
||||
end
|
||||
end
|
||||
24
lib/pleroma/web/activity_pub/mrf/mention_policy.ex
Normal file
24
lib/pleroma/web/activity_pub/mrf/mention_policy.ex
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
|
||||
@moduledoc "Block messages which mention a user"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create"} = message) do
|
||||
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
|
||||
recipients = (message["to"] || []) ++ (message["cc"] || [])
|
||||
|
||||
if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
|
||||
{:reject, nil}
|
||||
else
|
||||
{:ok, message}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
end
|
||||
|
|
@ -92,18 +92,68 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publishes an activity to all relevant peers.
|
||||
"""
|
||||
def publish(%User{} = actor, %Activity{} = activity) do
|
||||
remote_followers =
|
||||
defp recipients(actor, activity) do
|
||||
followers =
|
||||
if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
followers |> Enum.filter(&(!&1.local))
|
||||
Enum.filter(followers, &(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
|
||||
end
|
||||
|
||||
defp get_cc_ap_ids(ap_id, recipients) do
|
||||
host = Map.get(URI.parse(ap_id), :host)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|
||||
|> Enum.map(& &1.ap_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publishes an activity with BCC to all relevant peers.
|
||||
"""
|
||||
|
||||
def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
|
||||
public = is_public?(activity)
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
recipients = recipients(actor, activity)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(&User.ap_enabled?/1)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
%User{ap_id: ap_id} =
|
||||
Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
|
||||
|
||||
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
|
||||
# instance would only accept a first message for the first recipient and ignore the rest.
|
||||
cc = get_cc_ap_ids(ap_id, recipients)
|
||||
|
||||
json =
|
||||
data
|
||||
|> Map.put("cc", cc)
|
||||
|> Jason.encode!()
|
||||
|
||||
Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"],
|
||||
unreachable_since: unreachable_since
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publishes an activity to all relevant peers.
|
||||
"""
|
||||
def publish(%User{} = actor, %Activity{} = activity) do
|
||||
public = is_public?(activity)
|
||||
|
||||
if public && Config.get([:instance, :allow_relay]) do
|
||||
|
|
@ -114,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
json = Jason.encode!(data)
|
||||
|
||||
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|
||||
recipients(actor, activity)
|
||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} ->
|
||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
require Logger
|
||||
|
||||
def get_actor do
|
||||
User.get_or_create_instance_user()
|
||||
"#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
|> User.get_or_create_service_actor_by_ap_id()
|
||||
end
|
||||
|
||||
def follow(target_instance) do
|
||||
|
|
|
|||
|
|
@ -814,13 +814,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
|
||||
object =
|
||||
Object.normalize(object_id).data
|
||||
object_id
|
||||
|> Object.normalize()
|
||||
|> Map.get(:data)
|
||||
|> prepare_object
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|> Map.delete("bcc")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,12 +25,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
def get_ap_id(object) do
|
||||
case object do
|
||||
%{"id" => id} -> id
|
||||
id -> id
|
||||
end
|
||||
end
|
||||
def get_ap_id(%{"id" => id} = _), do: id
|
||||
def get_ap_id(id), do: id
|
||||
|
||||
def normalize_params(params) do
|
||||
Map.put(params, "actor", get_ap_id(params["actor"]))
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
def render("endpoints.json", _), do: %{}
|
||||
|
||||
# the instance itself is not a Person, but instead an Application
|
||||
def render("user.json", %{user: %{nickname: nil} = user}) do
|
||||
def render("service.json", %{user: user}) do
|
||||
{:ok, user} = User.ensure_keys_present(user)
|
||||
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
|
||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||
|
|
@ -47,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"followers" => "#{user.ap_id}/followers",
|
||||
"inbox" => "#{user.ap_id}/inbox",
|
||||
"name" => "Pleroma",
|
||||
"summary" => "Virtual actor for Pleroma relay",
|
||||
"summary" =>
|
||||
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
|
||||
"url" => user.ap_id,
|
||||
"manuallyApprovesFollowers" => false,
|
||||
"publicKey" => %{
|
||||
|
|
@ -60,6 +60,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
# the instance itself is not a Person, but instead an Application
|
||||
def render("user.json", %{user: %User{nickname: nil} = user}),
|
||||
do: render("service.json", %{user: user})
|
||||
|
||||
def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
|
||||
do: render("service.json", %{user: user})
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
{:ok, user} = User.ensure_keys_present(user)
|
||||
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
!is_public?(activity) && !is_private?(activity)
|
||||
end
|
||||
|
||||
def is_list?(%{data: %{"listMessage" => _}}), do: true
|
||||
def is_list?(_), do: false
|
||||
|
||||
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
||||
|
||||
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
||||
user.ap_id in activity.data["to"] ||
|
||||
list_ap_id
|
||||
|> Pleroma.List.get_by_ap_id()
|
||||
|> Pleroma.List.member?(user)
|
||||
end
|
||||
|
||||
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
||||
|
||||
def visible_for_user?(activity, nil) do
|
||||
is_public?(activity)
|
||||
end
|
||||
|
|
@ -73,6 +87,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
object.data["directMessage"] == true ->
|
||||
"direct"
|
||||
|
||||
is_binary(object.data["listMessage"]) ->
|
||||
"list"
|
||||
|
||||
length(cc) > 0 ->
|
||||
"private"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Plugs.AuthenticationPlug
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -16,7 +16,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
def get_user(%Plug.Conn{} = conn) do
|
||||
with {:ok, {name, password}} <- fetch_credentials(conn),
|
||||
{_, %User{} = user} <- {:user, fetch_user(name)},
|
||||
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
|
||||
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)} do
|
||||
{:ok, user}
|
||||
else
|
||||
error ->
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.CommonAPI do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.ThreadMute
|
||||
|
|
@ -31,7 +30,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||
{:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
|
@ -175,6 +175,11 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
when visibility in ~w{public unlisted private direct},
|
||||
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
||||
|
||||
def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
|
||||
visibility = {:list, String.to_integer(list_id)}
|
||||
{visibility, get_replied_to_visibility(in_reply_to)}
|
||||
end
|
||||
|
||||
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
|
||||
visibility = get_replied_to_visibility(in_reply_to)
|
||||
{visibility, visibility}
|
||||
|
|
@ -235,19 +240,18 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
"emoji",
|
||||
Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
|
||||
) do
|
||||
res =
|
||||
ActivityPub.create(
|
||||
%{
|
||||
to: to,
|
||||
actor: user,
|
||||
context: context,
|
||||
object: object,
|
||||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
|
||||
},
|
||||
Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||
)
|
||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||
direct? = visibility == "direct"
|
||||
|
||||
res
|
||||
%{
|
||||
to: to,
|
||||
actor: user,
|
||||
context: context,
|
||||
object: object,
|
||||
additional: %{"cc" => cc, "directMessage" => direct?}
|
||||
}
|
||||
|> maybe_add_list_data(user, visibility)
|
||||
|> ActivityPub.create(preview?)
|
||||
else
|
||||
{:private_to_public, true} ->
|
||||
{:error, dgettext("errors", "The message visibility must be direct")}
|
||||
|
|
@ -351,15 +355,6 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def bookmarked?(user, activity) do
|
||||
with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
|
||||
true
|
||||
else
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def report(user, data) do
|
||||
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
||||
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
import Pleroma.Web.Gettext
|
||||
|
||||
alias Calendar.Strftime
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Plugs.AuthenticationPlug
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
|
@ -100,12 +100,29 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []}
|
||||
|
||||
def get_addressed_users(_, to) when is_list(to) do
|
||||
User.get_ap_ids_by_nicknames(to)
|
||||
end
|
||||
|
||||
def get_addressed_users(mentioned_users, _), do: mentioned_users
|
||||
|
||||
def maybe_add_list_data(activity_params, user, {:list, list_id}) do
|
||||
case Pleroma.List.get(list_id, user) do
|
||||
%Pleroma.List{} = list ->
|
||||
activity_params
|
||||
|> put_in([:additional, "bcc"], [list.ap_id])
|
||||
|> put_in([:additional, "listMessage"], list.ap_id)
|
||||
|> put_in([:object, "listMessage"], list.ap_id)
|
||||
|
||||
_ ->
|
||||
activity_params
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_add_list_data(activity_params, _, _), do: activity_params
|
||||
|
||||
def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
|
||||
when is_list(options) do
|
||||
%{max_expiration: max_expiration, min_expiration: min_expiration} =
|
||||
|
|
@ -371,7 +388,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def confirm_current_password(user, password) do
|
||||
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
|
||||
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||
true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
|
||||
{:ok, db_user}
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Invalid password.")}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
options = cast_params(params)
|
||||
|
||||
user
|
||||
|> Notification.for_user_query()
|
||||
|> Notification.for_user_query(options)
|
||||
|> restrict(:exclude_types, options)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
|
@ -67,7 +67,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
defp cast_params(params) do
|
||||
param_types = %{
|
||||
exclude_types: {:array, :string},
|
||||
reblogs: :boolean
|
||||
reblogs: :boolean,
|
||||
with_muted: :boolean
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Stats
|
||||
|
|
@ -46,8 +47,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
@rate_limited_relations_actions ~w(follow unfollow)a
|
||||
|
||||
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
|
||||
post_status delete_status)a
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
||||
when action in ~w(reblog_status unreblog_status)a
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||
when action in ~w(fav_status unfav_status)a
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
{:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
|
||||
)
|
||||
|
||||
plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
|
||||
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
||||
plug(RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
plug(RateLimiter, :password_reset when action == :password_reset)
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
|
|
@ -414,7 +440,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||
with %User{} = user <- User.get_cached_by_id(params["id"]) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("tag", params["tagged"])
|
||||
|
|
@ -676,11 +702,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{"error" => reason})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -721,11 +742,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -864,7 +880,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
||||
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
||||
q = from(u in User, where: u.ap_id in ^likes)
|
||||
users = Repo.all(q)
|
||||
|
|
@ -878,7 +894,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
|
||||
q = from(u in User, where: u.ap_id in ^announces)
|
||||
users = Repo.all(q)
|
||||
|
|
@ -1051,9 +1067,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
|
||||
notifications =
|
||||
if Map.has_key?(params, "notifications"),
|
||||
do: params["notifications"] in [true, "True", "true", "1"],
|
||||
else: true
|
||||
|
||||
with %User{} = muted <- User.get_cached_by_id(id),
|
||||
{:ok, muter} <- User.mute(muter, muted) do
|
||||
{:ok, muter} <- User.mute(muter, muted, notifications) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: muter, target: muted})
|
||||
|
|
@ -1629,6 +1650,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
render_error(conn, :not_found, "Record not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:error, error_message}) do
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: error_message})
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|
|
@ -1790,6 +1817,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def password_reset(conn, params) do
|
||||
nickname_or_email = params["email"] || params["nickname"]
|
||||
|
||||
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
|
||||
conn
|
||||
|> put_status(:no_content)
|
||||
|> json("")
|
||||
else
|
||||
{:error, "unknown user"} ->
|
||||
send_resp(conn, :not_found, "")
|
||||
|
||||
{:error, _} ->
|
||||
send_resp(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, target, params)
|
||||
when is_binary(target) do
|
||||
case render(conn, target, params) do
|
||||
|
|
|
|||
|
|
@ -51,8 +51,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
following: User.following?(user, target),
|
||||
followed_by: User.following?(target, user),
|
||||
blocking: User.blocks?(user, target),
|
||||
blocked_by: User.blocks?(target, user),
|
||||
muting: User.mutes?(user, target),
|
||||
muting_notifications: false,
|
||||
muting_notifications: User.muted_notifications?(user, target),
|
||||
subscribing: User.subscribed_to?(user, target),
|
||||
requested: requested,
|
||||
domain_blocking: false,
|
||||
|
|
@ -136,6 +137,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|> maybe_put_notification_settings(user, opts[:for])
|
||||
|> maybe_put_settings_store(user, opts[:for], opts)
|
||||
|> maybe_put_chat_token(user, opts[:for], opts)
|
||||
|> maybe_put_activation_status(user, opts[:for])
|
||||
end
|
||||
|
||||
defp username_from_nickname(string) when is_binary(string) do
|
||||
|
|
@ -196,6 +198,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
defp maybe_put_notification_settings(data, _, _), do: data
|
||||
|
||||
defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
|
||||
Kernel.put_in(data, [:pleroma, :deactivated], user.info.deactivated)
|
||||
end
|
||||
|
||||
defp maybe_put_activation_status(data, _, _), do: data
|
||||
|
||||
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
%{
|
||||
# Mastodon uses separate ids for polls, but an object can't have
|
||||
# more than one poll embedded so object id is fine
|
||||
id: object.id,
|
||||
id: to_string(object.id),
|
||||
expires_at: Utils.to_masto_date(end_time),
|
||||
expired: expired,
|
||||
multiple: multiple,
|
||||
|
|
|
|||
|
|
@ -3,68 +3,71 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MediaProxy do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web
|
||||
|
||||
@base64_opts [padding: false]
|
||||
|
||||
def url(nil), do: nil
|
||||
|
||||
def url(""), do: nil
|
||||
|
||||
def url(url) when is_nil(url) or url == "", do: nil
|
||||
def url("/" <> _ = url), do: url
|
||||
|
||||
def url(url) do
|
||||
if !enabled?() or local?(url) or whitelisted?(url) do
|
||||
if disabled?() or local?(url) or whitelisted?(url) do
|
||||
url
|
||||
else
|
||||
encode_url(url)
|
||||
end
|
||||
end
|
||||
|
||||
defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false)
|
||||
defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
|
||||
|
||||
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
|
||||
|
||||
defp whitelisted?(url) do
|
||||
%{host: domain} = URI.parse(url)
|
||||
|
||||
Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||
Enum.any?(Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||
String.equivalent?(domain, pattern)
|
||||
end)
|
||||
end
|
||||
|
||||
def encode_url(url) do
|
||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
base64 = Base.url_encode64(url, @base64_opts)
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
|
||||
sig64 =
|
||||
base64
|
||||
|> signed_url
|
||||
|> Base.url_encode64(@base64_opts)
|
||||
|
||||
build_url(sig64, base64, filename(url))
|
||||
end
|
||||
|
||||
def decode_url(sig, url) do
|
||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
sig = Base.url_decode64!(sig, @base64_opts)
|
||||
local_sig = :crypto.hmac(:sha, secret, url)
|
||||
|
||||
if local_sig == sig do
|
||||
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
|
||||
signature when signature == sig <- signed_url(url) do
|
||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
||||
else
|
||||
{:error, :invalid_signature}
|
||||
_ -> {:error, :invalid_signature}
|
||||
end
|
||||
end
|
||||
|
||||
defp signed_url(url) do
|
||||
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
|
||||
end
|
||||
|
||||
def filename(url_or_path) do
|
||||
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
||||
end
|
||||
|
||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
||||
[
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()),
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
"proxy",
|
||||
sig_base64,
|
||||
url_base64,
|
||||
filename
|
||||
]
|
||||
|> Enum.filter(fn value -> value end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Path.join()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
with config <- Pleroma.Config.get([:media_proxy], []),
|
||||
true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
||||
:ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
|
||||
:ok <- filename_matches(params, conn.request_path, url) do
|
||||
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
||||
else
|
||||
false ->
|
||||
|
|
@ -27,13 +27,20 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
end
|
||||
end
|
||||
|
||||
def filename_matches(has_filename, path, url) do
|
||||
filename = url |> MediaProxy.filename()
|
||||
def filename_matches(%{"filename" => _} = _, path, url) do
|
||||
filename = MediaProxy.filename(url)
|
||||
|
||||
if has_filename && filename && Path.basename(path) != filename do
|
||||
if filename && does_not_match(path, filename) do
|
||||
{:wrong_filename, filename}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def filename_matches(_, _, _), do: :ok
|
||||
|
||||
defp does_not_match(path, filename) do
|
||||
basename = Path.basename(path)
|
||||
basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename
|
||||
end
|
||||
end
|
||||
|
|
@ -34,8 +34,11 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
def raw_nodeinfo do
|
||||
stats = Stats.get_stats()
|
||||
|
||||
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
||||
|
||||
mrf_simple =
|
||||
Config.get(:mrf_simple)
|
||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
# This horror is needed to convert regex sigils to strings
|
||||
|
|
@ -86,7 +89,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
mrf_simple: mrf_simple,
|
||||
mrf_keyword: mrf_keyword,
|
||||
mrf_user_allowlist: mrf_user_allowlist,
|
||||
quarantined_instances: quarantined
|
||||
quarantined_instances: quarantined,
|
||||
exclusions: length(exclusions) > 0
|
||||
}
|
||||
else
|
||||
%{}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser do
|
||||
@parsers [
|
||||
Pleroma.Web.RichMedia.Parsers.OGP,
|
||||
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
||||
Pleroma.Web.RichMedia.Parsers.OEmbed
|
||||
]
|
||||
|
||||
@hackney_options [
|
||||
pool: :media,
|
||||
recv_timeout: 2_000,
|
||||
|
|
@ -16,6 +10,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
with_body: true
|
||||
]
|
||||
|
||||
defp parsers do
|
||||
Pleroma.Config.get([:rich_media, :parsers])
|
||||
end
|
||||
|
||||
def parse(nil), do: {:error, "No URL provided"}
|
||||
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
|
|
@ -26,6 +24,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
Cachex.fetch!(:rich_media_cache, url, fn _ ->
|
||||
{:commit, parse_url(url)}
|
||||
end)
|
||||
|> set_ttl_based_on_image(url)
|
||||
rescue
|
||||
e ->
|
||||
{:error, "Cachex error: #{inspect(e)}"}
|
||||
|
|
@ -33,6 +32,50 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set the rich media cache based on the expiration time of image.
|
||||
|
||||
Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL`
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyModule do
|
||||
@behaviour Pleroma.Web.RichMedia.Parser.TTL
|
||||
def ttl(data, url) do
|
||||
image_url = Map.get(data, :image)
|
||||
# do some parsing in the url and get the ttl of the image
|
||||
# and return ttl is unix time
|
||||
parse_ttl_from_url(image_url)
|
||||
end
|
||||
end
|
||||
|
||||
Define the module in the config
|
||||
|
||||
config :pleroma, :rich_media,
|
||||
ttl_setters: [MyModule]
|
||||
"""
|
||||
def set_ttl_based_on_image({:ok, data}, url) do
|
||||
with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url) do
|
||||
ttl = get_ttl_from_image(data, url)
|
||||
Cachex.expire_at(:rich_media_cache, url, ttl * 1000)
|
||||
{:ok, data}
|
||||
else
|
||||
_ ->
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_ttl_from_image(data, url) do
|
||||
Pleroma.Config.get([:rich_media, :ttl_setters])
|
||||
|> Enum.reduce({:ok, nil}, fn
|
||||
module, {:ok, _ttl} ->
|
||||
module.ttl(data, url)
|
||||
|
||||
_, error ->
|
||||
error
|
||||
end)
|
||||
end
|
||||
|
||||
defp parse_url(url) do
|
||||
try do
|
||||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
|
||||
|
|
@ -48,7 +91,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
end
|
||||
|
||||
defp maybe_parse(html) do
|
||||
Enum.reduce_while(@parsers, %{}, fn parser, acc ->
|
||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||
case parser.parse(html, acc) do
|
||||
{:ok, data} -> {:halt, data}
|
||||
{:error, _msg} -> {:cont, acc}
|
||||
|
|
|
|||
52
lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
Normal file
52
lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
|
||||
@behaviour Pleroma.Web.RichMedia.Parser.TTL
|
||||
|
||||
@impl Pleroma.Web.RichMedia.Parser.TTL
|
||||
def ttl(data, _url) do
|
||||
image = Map.get(data, :image)
|
||||
|
||||
if is_aws_signed_url(image) do
|
||||
image
|
||||
|> parse_query_params()
|
||||
|> format_query_params()
|
||||
|> get_expiration_timestamp()
|
||||
end
|
||||
end
|
||||
|
||||
defp is_aws_signed_url(""), do: nil
|
||||
defp is_aws_signed_url(nil), do: nil
|
||||
|
||||
defp is_aws_signed_url(image) when is_binary(image) do
|
||||
%URI{host: host, query: query} = URI.parse(image)
|
||||
|
||||
if String.contains?(host, "amazonaws.com") and
|
||||
String.contains?(query, "X-Amz-Expires") do
|
||||
image
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp is_aws_signed_url(_), do: nil
|
||||
|
||||
defp parse_query_params(image) do
|
||||
%URI{query: query} = URI.parse(image)
|
||||
query
|
||||
end
|
||||
|
||||
defp format_query_params(query) do
|
||||
query
|
||||
|> String.split(~r/&|=/)
|
||||
|> Enum.chunk_every(2)
|
||||
|> Map.new(fn [k, v] -> {k, v} end)
|
||||
end
|
||||
|
||||
defp get_expiration_timestamp(params) when is_map(params) do
|
||||
{:ok, date} =
|
||||
params
|
||||
|> Map.get("X-Amz-Date")
|
||||
|> Timex.parse("{ISO:Basic:Z}")
|
||||
|
||||
Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))
|
||||
end
|
||||
end
|
||||
3
lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
Normal file
3
lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
defmodule Pleroma.Web.RichMedia.Parser.TTL do
|
||||
@callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()}
|
||||
end
|
||||
|
|
@ -586,7 +586,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
end
|
||||
|
||||
pipeline :ap_relay do
|
||||
pipeline :ap_service_actor do
|
||||
plug(:accepts, ["activity+json", "json"])
|
||||
end
|
||||
|
||||
|
|
@ -617,6 +617,7 @@ defmodule Pleroma.Web.Router do
|
|||
pipeline :activitypub do
|
||||
plug(:accepts, ["activity+json", "json"])
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
|
|
@ -663,8 +664,17 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_relay)
|
||||
pipe_through(:ap_service_actor)
|
||||
|
||||
get("/", ActivityPubController, :relay)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
||||
scope "/internal/fetch", Pleroma.Web.ActivityPub do
|
||||
pipe_through(:ap_service_actor)
|
||||
|
||||
get("/", ActivityPubController, :internal_fetch)
|
||||
post("/inbox", ActivityPubController, :inbox)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.ActivityPub do
|
||||
|
|
@ -691,6 +701,8 @@ defmodule Pleroma.Web.Router do
|
|||
get("/web/login", MastodonAPIController, :login)
|
||||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||
|
||||
post("/auth/password", MastodonAPIController, :password_reset)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_public)
|
||||
get("/web/*path", MastodonAPIController, :index)
|
||||
|
|
|
|||
|
|
@ -123,11 +123,26 @@ defmodule Pleroma.Web.Salmon do
|
|||
{:ok, salmon}
|
||||
end
|
||||
|
||||
def remote_users(%{data: %{"to" => to} = data}) do
|
||||
to = to ++ (data["cc"] || [])
|
||||
def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
|
||||
cc = Map.get(data, "cc", [])
|
||||
|
||||
to
|
||||
|> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
|
||||
bcc =
|
||||
data
|
||||
|> Map.get("bcc", [])
|
||||
|> Enum.reduce([], fn ap_id, bcc ->
|
||||
case Pleroma.List.get_by_ap_id(ap_id) do
|
||||
%Pleroma.List{user_id: ^user_id} = list ->
|
||||
{:ok, following} = Pleroma.List.get_following(list)
|
||||
bcc ++ Enum.map(following, & &1.ap_id)
|
||||
|
||||
_ ->
|
||||
bcc
|
||||
end
|
||||
end)
|
||||
|
||||
[to, cc, bcc]
|
||||
|> Enum.concat()
|
||||
|> Enum.map(&User.get_cached_by_ap_id/1)
|
||||
|> Enum.filter(fn user -> user && !user.local end)
|
||||
end
|
||||
|
||||
|
|
@ -191,7 +206,7 @@ defmodule Pleroma.Web.Salmon do
|
|||
{:ok, private, _} = Keys.keys_from_pem(keys)
|
||||
{:ok, feed} = encode(private, feed)
|
||||
|
||||
remote_users = remote_users(activity)
|
||||
remote_users = remote_users(user, activity)
|
||||
|
||||
salmon_urls = Enum.map(remote_users, & &1.info.salmon)
|
||||
reachable_urls_metadata = Instances.filter_reachable(salmon_urls)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
require Logger
|
||||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Healthcheck
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Plugs.AuthenticationPlug
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
|
@ -23,7 +25,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||
avatar = User.avatar_url(user) do
|
||||
conn
|
||||
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
|
||||
else
|
||||
|
|
@ -96,7 +99,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
name = followee.nickname
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
true <- AuthenticationPlug.checkpw(password, user.password_hash),
|
||||
%User{} = _followed <- User.get_cached_by_id(id),
|
||||
{:ok, follower} <- User.follow(user, followee),
|
||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||
|
|
@ -338,20 +341,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def healthcheck(conn, _params) do
|
||||
info =
|
||||
if Pleroma.Config.get([:instance, :healthcheck]) do
|
||||
Pleroma.Healthcheck.system_info()
|
||||
else
|
||||
%{}
|
||||
end
|
||||
with true <- Config.get([:instance, :healthcheck]),
|
||||
%{healthy: true} = info <- Healthcheck.system_info() do
|
||||
json(conn, info)
|
||||
else
|
||||
%{healthy: false} = info ->
|
||||
service_unavailable(conn, info)
|
||||
|
||||
conn =
|
||||
if info[:healthy] do
|
||||
conn
|
||||
else
|
||||
Plug.Conn.put_status(conn, :service_unavailable)
|
||||
end
|
||||
_ ->
|
||||
service_unavailable(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
json(conn, info)
|
||||
defp service_unavailable(conn, info) do
|
||||
conn
|
||||
|> put_status(:service_unavailable)
|
||||
|> json(info)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -221,6 +221,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
user
|
||||
|> UserEmail.password_reset_email(token_record.token)
|
||||
|> Mailer.deliver_async()
|
||||
|
||||
{:ok, :enqueued}
|
||||
else
|
||||
false ->
|
||||
{:error, "bad user identifier"}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
|
||||
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
|
||||
action_fallback(:errors)
|
||||
|
||||
|
|
@ -192,6 +193,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
params =
|
||||
if Map.has_key?(params, "with_muted") do
|
||||
Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
conn
|
||||
|
|
@ -430,6 +438,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:error, "unknown user"} ->
|
||||
send_resp(conn, :not_found, "")
|
||||
|
||||
{:error, _} ->
|
||||
send_resp(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ defmodule Pleroma.Web.UploaderController do
|
|||
process_callback(conn, :global.whereis_name({Uploader, upload_path}), params)
|
||||
end
|
||||
|
||||
def callbacks(conn, _) do
|
||||
render_error(conn, :bad_request, "bad request")
|
||||
end
|
||||
|
||||
defp process_callback(conn, pid, params) when is_pid(pid) do
|
||||
send(pid, {Uploader, self(), conn, params})
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||
regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||
%User{} = user <- User.get_cached_by_nickname(username) do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue