Merge branch 'develop' into issue/2099

This commit is contained in:
Maksim Pechnikov 2020-09-18 07:08:37 +03:00
commit 2ec0dcf001
62 changed files with 1421 additions and 159 deletions

View file

@ -32,7 +32,8 @@ defmodule Mix.Tasks.Pleroma.Config do
@spec migrate_to_db(Path.t() | nil) :: any()
def migrate_to_db(file_path \\ nil) do
if Pleroma.Config.get([:configurable_from_database]) do
with true <- Pleroma.Config.get([:configurable_from_database]),
:ok <- Pleroma.Config.DeprecationWarnings.warn() do
config_file =
if file_path do
file_path
@ -46,7 +47,8 @@ defmodule Mix.Tasks.Pleroma.Config do
do_migrate_to_db(config_file)
else
migration_error()
:error -> deprecation_error()
_ -> migration_error()
end
end
@ -120,6 +122,10 @@ defmodule Mix.Tasks.Pleroma.Config do
)
end
defp deprecation_error do
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
end
if Code.ensure_loaded?(Config.Reader) do
defp config_header, do: "import Config\r\n\r\n"
defp read_file(config_file), do: Config.Reader.read_imports!(config_file)

View file

@ -6,7 +6,9 @@ defmodule Pleroma.Chat do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Chat
alias Pleroma.Repo
alias Pleroma.User
@ -69,4 +71,12 @@ defmodule Pleroma.Chat do
conflict_target: [:user_id, :recipient]
)
end
@spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t()
def for_user_query(user_id) do
from(c in Chat,
where: c.user_id == ^user_id,
order_by: [desc: c.updated_at]
)
end
end

View file

@ -26,6 +26,10 @@ defmodule Pleroma.Config.DeprecationWarnings do
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
""")
:error
else
:ok
end
end
@ -47,17 +51,26 @@ defmodule Pleroma.Config.DeprecationWarnings do
config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
""")
:error
else
:ok
end
end
def warn do
check_hellthread_threshold()
mrf_user_allowlist()
check_old_mrf_config()
check_media_proxy_whitelist_config()
check_welcome_message_config()
check_gun_pool_options()
check_activity_expiration_config()
with :ok <- check_hellthread_threshold(),
:ok <- mrf_user_allowlist(),
:ok <- check_old_mrf_config(),
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
:ok <- check_gun_pool_options(),
:ok <- check_activity_expiration_config() do
:ok
else
_ ->
:error
end
end
def check_welcome_message_config do
@ -74,6 +87,10 @@ defmodule Pleroma.Config.DeprecationWarnings do
\n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname`
\n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message`
""")
:error
else
:ok
end
end
@ -101,8 +118,11 @@ defmodule Pleroma.Config.DeprecationWarnings do
end
end)
if warning != "" do
if warning == "" do
:ok
else
Logger.warn(warning_preface <> warning)
:error
end
end
@ -115,6 +135,10 @@ defmodule Pleroma.Config.DeprecationWarnings do
!!!DEPRECATION WARNING!!!
Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
""")
:error
else
:ok
end
end
@ -157,6 +181,9 @@ defmodule Pleroma.Config.DeprecationWarnings do
Logger.warn(Enum.join([warning_preface | pool_warnings]))
Config.put(:pools, updated_config)
:error
else
:ok
end
end

View file

@ -320,6 +320,19 @@ defmodule Pleroma.ModerationLog do
|> insert_log_entry_with_message()
end
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
%ModerationLog{
data: %{
"actor" => %{"nickname" => actor.nickname},
"action" => "chat_message_delete",
"subject_id" => subject_id
}
}
|> insert_log_entry_with_message()
end
@spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
defp insert_log_entry_with_message(entry) do
entry.data["message"]
@ -627,6 +640,17 @@ defmodule Pleroma.ModerationLog do
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "chat_message_delete",
"subject_id" => subject_id
}
}) do
"@#{actor_nickname} deleted chat message ##{subject_id}"
end
defp nicknames_to_string(nicknames) do
nicknames
|> Enum.map(&"@#{&1}")

View file

@ -52,6 +52,7 @@ defmodule Pleroma.User.Search do
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_invisible_users()
|> filter_discoverable_users()
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
@ -122,6 +123,10 @@ defmodule Pleroma.User.Search do
from(q in query, where: q.invisible == false)
end
defp filter_discoverable_users(query) do
from(q in query, where: q.discoverable == true)
end
defp filter_internal_users(query) do
from(q in query, where: q.actor_type != "Application")
end

View file

@ -5,16 +5,34 @@
defmodule Pleroma.Web.ActivityPub.MRF do
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
def filter(policies, %{} = object) do
def filter(policies, %{} = message) do
policies
|> Enum.reduce({:ok, object}, fn
policy, {:ok, object} -> policy.filter(object)
|> Enum.reduce({:ok, message}, fn
policy, {:ok, message} -> policy.filter(message)
_, error -> error
end)
end
def filter(%{} = object), do: get_policies() |> filter(object)
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]
ap_id = message["object"]
if object && ap_id do
with {:ok, message} <- filter(Map.put(message, "object", object)) do
meta = Keyword.put(meta, :object_data, message["object"])
{:ok, Map.put(message, "object", ap_id), meta}
else
{err, message} -> {err, message, meta}
end
else
{err, message} = filter(message)
{err, message, meta}
end
end
def get_policies do
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end

View file

@ -20,9 +20,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
String.match?(string, pattern)
end
defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
defp object_payload(%{} = object) do
[object["content"], object["summary"], object["name"]]
|> Enum.filter(& &1)
|> Enum.join("\n")
end
defp check_reject(%{"object" => %{} = object} = message) do
payload = object_payload(object)
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
string_matches?(payload, pattern)
end) do
{:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
@ -30,12 +38,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
end
defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do
defp check_ftl_removal(%{"to" => to, "object" => %{} = object} = message) do
payload = object_payload(object)
if Pleroma.Constants.as_public() in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
string_matches?(payload, pattern)
end) do
to = List.delete(to, Pleroma.Constants.as_public())
cc = [Pleroma.Constants.as_public() | message["cc"] || []]
@ -51,35 +59,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
end
defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
content =
if is_binary(content) do
content
else
""
end
defp check_replace(%{"object" => %{} = object} = message) do
object =
["content", "name", "summary"]
|> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
|> Enum.reduce(object, fn field, object ->
data =
Enum.reduce(
Pleroma.Config.get([:mrf_keyword, :replace]),
object[field],
fn {pat, repl}, acc -> String.replace(acc, pat, repl) end
)
summary =
if is_binary(summary) do
summary
else
""
end
Map.put(object, field, data)
end)
{content, summary} =
Enum.reduce(
Pleroma.Config.get([:mrf_keyword, :replace]),
{content, summary},
fn {pattern, replacement}, {content_acc, summary_acc} ->
{String.replace(content_acc, pattern, replacement),
String.replace(summary_acc, pattern, replacement)}
end
)
message = Map.put(message, "object", object)
{:ok,
message
|> put_in(["object", "content"], content)
|> put_in(["object", "summary"], summary)}
{:ok, message}
end
@impl true

View file

@ -28,8 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
}"
)
subchain
|> MRF.filter(message)
MRF.filter(subchain, message)
else
_e -> {:ok, message}
end

View file

@ -26,13 +26,17 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
{:error, e} ->
{:error, e}
{:reject, e} ->
{:reject, e}
end
end
def do_common_pipeline(object, meta) do
with {_, {:ok, validated_object, meta}} <-
{:validate_object, ObjectValidator.validate(object, meta)},
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
{_, {:ok, mrfd_object, meta}} <-
{:mrf_object, MRF.pipeline_filter(validated_object, meta)},
{_, {:ok, activity, meta}} <-
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
{_, {:ok, activity, meta}} <-
@ -40,7 +44,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
{:ok, activity, meta}
else
{:mrf_object, {:reject, _}} -> {:ok, nil, meta}
{:mrf_object, {:reject, message, _}} -> {:reject, message}
e -> {:error, e}
end
end

View file

@ -23,8 +23,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
require Logger
@users_page_size 50
plug(
@ -68,6 +66,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
when action in [:list_user_statuses, :list_instance_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:chats"], admin: true}
when action in [:list_user_chats]
)
plug(
OAuthScopesPlug,
%{scopes: ["read"], admin: true}
@ -256,6 +260,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
chats =
Pleroma.Chat.for_user_query(user_id)
|> Pleroma.Repo.all()
conn
|> put_view(AdminAPI.ChatView)
|> render("index.json", chats: chats)
else
_ -> {:error, :not_found}
end
end
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)

View file

@ -0,0 +1,85 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ChatController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.ModerationLog
alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["read:chats"], admin: true} when action in [:show, :messages]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:chats"], admin: true} when action in [:delete_message]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation
def delete_message(%{assigns: %{user: user}} = conn, %{
message_id: message_id,
id: chat_id
}) do
with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <-
MessageReference.get_by_id(message_id),
^chat_id <- to_string(cm_ref.chat_id),
%Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id),
{:ok, _} <- CommonAPI.delete(activity_id, user) do
ModerationLog.insert_log(%{
action: "chat_message_delete",
actor: user,
subject_id: message_id
})
conn
|> put_view(MessageReferenceView)
|> render("show.json", chat_message_reference: cm_ref)
else
_e ->
{:error, :could_not_delete}
end
end
def messages(conn, %{id: id} = params) do
with %Chat{} = chat <- Chat.get_by_id(id) do
cm_refs =
chat
|> MessageReference.for_chat_query()
|> Pagination.fetch_paginated(params)
conn
|> put_view(MessageReferenceView)
|> render("index.json", chat_message_references: cm_refs)
else
_ ->
conn
|> put_status(:not_found)
|> json(%{error: "not found"})
end
end
def show(conn, %{id: id}) do
with %Chat{} = chat <- Chat.get_by_id(id) do
conn
|> put_view(AdminAPI.ChatView)
|> render("show.json", chat: chat)
end
end
end

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.InstanceStatic
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.InstanceDocument
plug(Pleroma.Web.ApiSpec.CastAndValidate)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation
plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete])
def show(conn, %{name: document_name}) do
with {:ok, url} <- InstanceDocument.get(document_name),
{:ok, content} <- File.read(InstanceStatic.file_path(url)) do
conn
|> put_resp_content_type("text/html")
|> send_resp(200, content)
end
end
def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do
with {:ok, url} <- InstanceDocument.put(document_name, file.path) do
json(conn, %{"url" => url})
end
end
def delete(conn, %{name: document_name}) do
with :ok <- InstanceDocument.delete(document_name) do
json(conn, %{})
end
end
end

View file

@ -0,0 +1,30 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ChatView do
use Pleroma.Web, :view
alias Pleroma.Chat
alias Pleroma.User
alias Pleroma.Web.MastodonAPI
alias Pleroma.Web.PleromaAPI
def render("index.json", %{chats: chats} = opts) do
render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
end
def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do
user = User.get_by_id(user_id)
sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true)
serialized_chat = PleromaAPI.ChatView.render("show.json", opts)
serialized_chat
|> Map.put(:sender, sender)
|> Map.put(:receiver, serialized_chat[:account])
|> Map.delete(:account)
end
def render(view, opts), do: PleromaAPI.ChatView.render(view, opts)
end

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
require Pleroma.Constants
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI
defdelegate merge_account_views(user), to: AdminAPI.AccountView
@ -17,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
user = MastodonAPI.StatusView.get_user(activity.data["actor"])
user = CommonAPI.get_user(activity.data["actor"])
MastodonAPI.StatusView.render("show.json", opts)
|> Map.merge(%{account: merge_account_views(user)})

View file

@ -0,0 +1,96 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
alias OpenApiSpex.Operation
alias Pleroma.Web.ApiSpec.Schemas.Chat
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def delete_message_operation do
%Operation{
tags: ["admin", "chat"],
summary: "Delete an individual chat message",
operationId: "AdminAPI.ChatController.delete_message",
parameters: [
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
Operation.parameter(:message_id, :path, :string, "The ID of the message")
],
responses: %{
200 =>
Operation.response(
"The deleted ChatMessage",
"application/json",
ChatMessage
)
},
security: [
%{
"oAuth" => ["write:chats"]
}
]
}
end
def messages_operation do
%Operation{
tags: ["admin", "chat"],
summary: "Get the most recent messages of the chat",
operationId: "AdminAPI.ChatController.messages",
parameters:
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
pagination_params(),
responses: %{
200 =>
Operation.response(
"The messages in the chat",
"application/json",
Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response()
)
},
security: [
%{
"oAuth" => ["read:chats"]
}
]
}
end
def show_operation do
%Operation{
tags: ["chat"],
summary: "Create a chat",
operationId: "AdminAPI.ChatController.show",
parameters: [
Operation.parameter(
:id,
:path,
:string,
"The id of the chat",
required: true,
example: "1234"
)
],
responses: %{
200 =>
Operation.response(
"The existing chat",
"application/json",
Chat
)
},
security: [
%{
"oAuth" => ["read"]
}
]
}
end
end

View file

@ -0,0 +1,115 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
alias Pleroma.Web.ApiSpec.Schemas.ApiError
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def show_operation do
%Operation{
tags: ["Admin", "InstanceDocument"],
summary: "Get the instance document",
operationId: "AdminAPI.InstanceDocumentController.show",
security: [%{"oAuth" => ["read"]}],
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
required: true
)
| Helpers.admin_api_params()
],
responses: %{
200 => document_content(),
400 => Operation.response("Bad Request", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
tags: ["Admin", "InstanceDocument"],
summary: "Update the instance document",
operationId: "AdminAPI.InstanceDocumentController.update",
security: [%{"oAuth" => ["write"]}],
requestBody: Helpers.request_body("Parameters", update_request()),
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
required: true
)
| Helpers.admin_api_params()
],
responses: %{
200 => Operation.response("InstanceDocument", "application/json", instance_document()),
400 => Operation.response("Bad Request", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
defp update_request do
%Schema{
title: "UpdateRequest",
description: "POST body for uploading the file",
type: :object,
required: [:file],
properties: %{
file: %Schema{
type: :string,
format: :binary,
description: "The file to be uploaded, using multipart form data."
}
}
}
end
def delete_operation do
%Operation{
tags: ["Admin", "InstanceDocument"],
summary: "Get the instance document",
operationId: "AdminAPI.InstanceDocumentController.delete",
security: [%{"oAuth" => ["write"]}],
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
required: true
)
| Helpers.admin_api_params()
],
responses: %{
200 => Operation.response("InstanceDocument", "application/json", instance_document()),
400 => Operation.response("Bad Request", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
defp instance_document do
%Schema{
title: "InstanceDocument",
type: :object,
properties: %{
url: %Schema{type: :string}
},
example: %{
"url" => "https://example.com/static/terms-of-service.html"
}
}
end
defp document_content do
Operation.response("InstanceDocumentContent", "text/html", %Schema{
type: :string,
example: "<h1>Instance panel</h1>"
})
end
end

View file

@ -184,7 +184,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"application/json",
ChatMessage
),
400 => Operation.response("Bad Request", "application/json", ApiError)
400 => Operation.response("Bad Request", "application/json", ApiError),
422 => Operation.response("MRF Rejection", "application/json", ApiError)
},
security: [
%{

View file

@ -55,7 +55,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
"application/json",
%Schema{oneOf: [Status, ScheduledStatus]}
),
422 => Operation.response("Bad Request", "application/json", ApiError)
422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
}
}
end

View file

@ -48,6 +48,9 @@ defmodule Pleroma.Web.CommonAPI do
local: true
)} do
{:ok, activity}
else
{:common_pipeline, {:reject, _} = e} -> e
e -> e
end
end
@ -550,4 +553,21 @@ defmodule Pleroma.Web.CommonAPI do
def show_reblogs(%User{} = user, %User{} = target) do
UserRelationship.delete_reblog_mute(user, target)
end
def get_user(ap_id, fake_record_fallback \\ true) do
cond do
user = User.get_cached_by_ap_id(ap_id) ->
user
user = User.get_by_guessed_nickname(ap_id) ->
user
fake_record_fallback ->
# TODO: refactor (fake records is never a good idea)
User.error_user(ap_id)
true ->
nil
end
end
end

View file

@ -0,0 +1,62 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.InstanceDocument do
alias Pleroma.Config
alias Pleroma.Web.Endpoint
@instance_documents %{
"terms-of-service" => "/static/terms-of-service.html",
"instance-panel" => "/instance/panel.html"
}
@spec get(String.t()) :: {:ok, String.t()} | {:error, atom()}
def get(document_name) do
case Map.fetch(@instance_documents, document_name) do
{:ok, path} -> {:ok, path}
_ -> {:error, :not_found}
end
end
@spec put(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()}
def put(document_name, origin_path) do
with {_, {:ok, destination_path}} <-
{:instance_document, Map.fetch(@instance_documents, document_name)},
:ok <- put_file(origin_path, destination_path) do
{:ok, Path.join(Endpoint.url(), destination_path)}
else
{:instance_document, :error} -> {:error, :not_found}
error -> error
end
end
@spec delete(String.t()) :: :ok | {:error, atom()}
def delete(document_name) do
with {_, {:ok, path}} <- {:instance_document, Map.fetch(@instance_documents, document_name)},
instance_static_dir_path <- instance_static_dir(path),
:ok <- File.rm(instance_static_dir_path) do
:ok
else
{:instance_document, :error} -> {:error, :not_found}
{:error, :enoent} -> {:error, :not_found}
error -> error
end
end
defp put_file(origin_path, destination_path) do
with destination <- instance_static_dir(destination_path),
{_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))},
{_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do
:ok
else
{error, _} -> {:error, error}
end
end
defp instance_static_dir(filename) do
[:instance, :static_dir]
|> Config.get!()
|> Path.join(filename)
end
end

View file

@ -55,23 +55,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end)
end
def get_user(ap_id, fake_record_fallback \\ true) do
cond do
user = User.get_cached_by_ap_id(ap_id) ->
user
user = User.get_by_guessed_nickname(ap_id) ->
user
fake_record_fallback ->
# TODO: refactor (fake records is never a good idea)
User.error_user(ap_id)
true ->
nil
end
end
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
do: context_id
@ -119,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
# Note: unresolved users are filtered out
actors =
(activities ++ parent_activities)
|> Enum.map(&get_user(&1.data["actor"], false))
|> Enum.map(&CommonAPI.get_user(&1.data["actor"], false))
|> Enum.filter(& &1)
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
@ -138,7 +121,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
"show.json",
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
) do
user = get_user(activity.data["actor"])
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity)
@ -211,7 +194,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
object = Object.normalize(activity)
user = get_user(activity.data["actor"])
user = CommonAPI.get_user(activity.data["actor"])
user_follower_address = user.follower_address
like_count = object.data["like_count"] || 0
@ -265,7 +248,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reply_to = get_reply_to(activity, opts)
reply_to_user = reply_to && get_user(reply_to.data["actor"])
reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
content =
object

View file

@ -10,7 +10,9 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
"""
@impl true
def build_tags(%{user: %{local: false}}) do
def build_tags(%{user: %{local: true, discoverable: true}}), do: []
def build_tags(_) do
[
{:meta,
[
@ -19,7 +21,4 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
], []}
]
end
@impl true
def build_tags(%{user: %{local: true}}), do: []
end

View file

@ -90,6 +90,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
conn
|> put_view(MessageReferenceView)
|> render("show.json", chat_message_reference: cm_ref)
else
{:reject, message} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{error: message})
end
end
@ -146,11 +156,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
blocked_ap_ids = User.blocked_users_ap_ids(user)
chats =
from(c in Chat,
where: c.user_id == ^user_id,
where: c.recipient not in ^blocked_ap_ids,
order_by: [desc: c.updated_at]
)
Chat.for_user_query(user_id)
|> where([c], c.recipient not in ^blocked_ap_ids)
|> Repo.all()
conn

View file

@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
object = Object.normalize(activity)
user = StatusView.get_user(activity.data["actor"])
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
%{

View file

@ -20,36 +20,61 @@ defmodule Pleroma.Web.RichMedia.Parser do
with {:ok, data} <- get_cached_or_parse(url),
{:ok, _} <- set_ttl_based_on_image(data, url) do
{:ok, data}
else
{:error, {:invalid_metadata, data}} = e ->
Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end)
e
error ->
Logger.error(fn -> "Rich media error for #{url}: #{inspect(error)}" end)
error
end
end
defp get_cached_or_parse(url) do
case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do
{:ok, _data} = res ->
res
case Cachex.fetch(:rich_media_cache, url, fn ->
case parse_url(url) do
{:ok, _} = res ->
{:commit, res}
{:error, :body_too_large} = e ->
e
{:error, reason} = e ->
# Unfortunately we have to log errors here, instead of doing that
# along with ttl setting at the bottom. Otherwise we can get log spam
# if more than one process was waiting for the rich media card
# while it was generated. Ideally we would set ttl here as well,
# so we don't override it number_of_waiters_on_generation
# times, but one, obviously, can't set ttl for not-yet-created entry
# and Cachex doesn't support returning ttl from the fetch callback.
log_error(url, reason)
{:commit, e}
end
end) do
{action, res} when action in [:commit, :ok] ->
case res do
{:ok, _data} = res ->
res
{:error, {:content_type, _}} = e ->
e
{:error, reason} = e ->
if action == :commit, do: set_error_ttl(url, reason)
e
end
# The TTL is not set for the errors above, since they are unlikely to change
# with time
{:error, _} = e ->
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
Cachex.expire(:rich_media_cache, url, ttl)
e
{:error, e} ->
{:error, {:cachex_error, e}}
end
end
defp set_error_ttl(_url, :body_too_large), do: :ok
defp set_error_ttl(_url, {:content_type, _}), do: :ok
# The TTL is not set for the errors above, since they are unlikely to change
# with time
defp set_error_ttl(url, _reason) do
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
Cachex.expire(:rich_media_cache, url, ttl)
:ok
end
defp log_error(url, {:invalid_metadata, data}) do
Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end)
end
defp log_error(url, reason) do
Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end)
end
end
@doc """

View file

@ -178,9 +178,14 @@ defmodule Pleroma.Web.Router do
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
get("/instance_document/:name", InstanceDocumentController, :show)
patch("/instance_document/:name", InstanceDocumentController, :update)
delete("/instance_document/:name", InstanceDocumentController, :delete)
patch("/users/confirm_email", AdminAPIController, :confirm_email)
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
@ -214,6 +219,10 @@ defmodule Pleroma.Web.Router do
get("/media_proxy_caches", MediaProxyCacheController, :index)
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do