aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlex Gleason <alex@alexgleason.me>2020-11-14 19:48:47 -0600
committerAlex Gleason <alex@alexgleason.me>2020-11-14 19:48:47 -0600
commit9546c1444c2c8c4abc9bcb35b6a8ff360ddc83af (patch)
treecc6b9add45f34878b1ce053b6a408e059768fa6a /lib
parentdc38dc847207e4724265fbeb111d0a236b75f93f (diff)
parent28da36975dc325bb577ee81fb791fbdc7ec3e7e2 (diff)
downloadpleroma-9546c1444c2c8c4abc9bcb35b6a8ff360ddc83af.tar.gz
Merge remote-tracking branch 'upstream/develop' into registration-workflow
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/instance.ex2
-rw-r--r--lib/mix/tasks/pleroma/user.ex6
-rw-r--r--lib/phoenix/transports/web_socket/raw.ex7
-rw-r--r--lib/pleroma/activity.ex13
-rw-r--r--lib/pleroma/activity/ir/topics.ex13
-rw-r--r--lib/pleroma/application.ex26
-rw-r--r--lib/pleroma/captcha/kocaptcha.ex2
-rw-r--r--lib/pleroma/conversation.ex6
-rw-r--r--lib/pleroma/conversation/participation.ex27
-rw-r--r--lib/pleroma/docs/json.ex6
-rw-r--r--lib/pleroma/emails/admin_email.ex10
-rw-r--r--lib/pleroma/emails/user_email.ex26
-rw-r--r--lib/pleroma/emoji/pack.ex4
-rw-r--r--lib/pleroma/helpers/inet_helper.ex19
-rw-r--r--lib/pleroma/instances.ex1
-rw-r--r--lib/pleroma/instances/instance.ex11
-rw-r--r--lib/pleroma/mime.ex120
-rw-r--r--lib/pleroma/moderation_log.ex10
-rw-r--r--lib/pleroma/notification.ex12
-rw-r--r--lib/pleroma/object/fetcher.ex20
-rw-r--r--lib/pleroma/stats.ex19
-rw-r--r--lib/pleroma/upload.ex19
-rw-r--r--lib/pleroma/user.ex137
-rw-r--r--lib/pleroma/user/backup.ex258
-rw-r--r--lib/pleroma/user/query.ex12
-rw-r--r--lib/pleroma/user/search.ex2
-rw-r--r--lib/pleroma/web.ex2
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex33
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex19
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex94
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex27
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex42
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/mrf/normalize_markup.ex19
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex28
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex23
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex74
-rw-r--r--lib/pleroma/web/activity_pub/mrf/subchain_policy.ex24
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex21
-rw-r--r--lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex28
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex3
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex4
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex11
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex20
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex4
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex39
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex272
-rw-r--r--lib/pleroma/web/admin_api/controllers/report_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/user_controller.ex281
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex4
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex17
-rw-r--r--lib/pleroma/web/api_spec/operations/chat_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/notification_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex79
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex40
-rw-r--r--lib/pleroma/web/api_spec/operations/status_operation.ex22
-rw-r--r--lib/pleroma/web/api_spec/operations/timeline_operation.ex12
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/poll.ex9
-rw-r--r--lib/pleroma/web/api_spec/schemas/status.ex2
-rw-r--r--lib/pleroma/web/common_api.ex33
-rw-r--r--lib/pleroma/web/common_api/utils.ex11
-rw-r--r--lib/pleroma/web/endpoint.ex42
-rw-r--r--lib/pleroma/web/fallback/redirect_controller.ex6
-rw-r--r--lib/pleroma/web/feed/feed_view.ex2
-rw-r--r--lib/pleroma/web/feed/tag_controller.ex17
-rw-r--r--lib/pleroma/web/feed/user_controller.ex27
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex30
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/media_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex13
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex14
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex11
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex3
-rw-r--r--lib/pleroma/web/media_proxy/invalidation/http.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/restrict_indexing.ex2
-rw-r--r--lib/pleroma/web/o_status/o_status_controller.ex17
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex5
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/backup_controller.ex28
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/chat_controller.ex38
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/instances_controller.ex21
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/views/backup_view.ex28
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex11
-rw-r--r--lib/pleroma/web/plugs/frontend_static.ex26
-rw-r--r--lib/pleroma/web/router.ex85
-rw-r--r--lib/pleroma/web/static_fe/static_fe_controller.ex177
-rw-r--r--lib/pleroma/web/streamer.ex9
-rw-r--r--lib/pleroma/web/templates/feed/feed/_activity.atom.eex2
-rw-r--r--lib/pleroma/web/templates/feed/feed/_activity.rss.eex2
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/email_styled.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/metadata_player.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/static_fe.html.eex2
-rw-r--r--lib/pleroma/workers/backup_worker.ex54
-rw-r--r--lib/pleroma/workers/mute_expire_worker.ex20
101 files changed, 2020 insertions, 840 deletions
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index fc21ae062..ac8688424 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -284,7 +284,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
defp upload_filters(filters) when is_map(filters) do
enabled_filters =
if filters.strip do
- [Pleroma.Upload.Filter.ExifTool]
+ [Pleroma.Upload.Filter.Exiftool]
else
[]
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index c454f1d28..544ba3550 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -419,7 +419,7 @@ defmodule Mix.Tasks.Pleroma.User do
|> Enum.each(fn user ->
shell_info(
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
- user.locked
+ user.is_locked
}, deactivated: #{user.deactivated}"
)
end)
@@ -447,10 +447,10 @@ defmodule Mix.Tasks.Pleroma.User do
defp set_locked(user, value) do
{:ok, user} =
user
- |> Changeset.change(%{locked: value})
+ |> Changeset.change(%{is_locked: value})
|> User.update_and_set_cache()
- shell_info("Locked status of #{user.nickname}: #{user.locked}")
+ shell_info("Locked status of #{user.nickname}: #{user.is_locked}")
user
end
diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex
index aab7fad99..c3665bebe 100644
--- a/lib/phoenix/transports/web_socket/raw.ex
+++ b/lib/phoenix/transports/web_socket/raw.ex
@@ -31,7 +31,12 @@ defmodule Phoenix.Transports.WebSocket.Raw do
case conn do
%{halted: false} = conn ->
- case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do
+ case handler.connect(%{
+ endpoint: endpoint,
+ transport: transport,
+ options: [serializer: nil],
+ params: conn.params
+ }) do
{:ok, socket} ->
{:ok, conn, {__MODULE__, {socket, opts}}}
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 17af04257..553834da0 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Activity do
alias Pleroma.ReportNote
alias Pleroma.ThreadMute
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
import Ecto.Changeset
import Ecto.Query
@@ -153,6 +154,18 @@ defmodule Pleroma.Activity do
def get_bookmark(_, _), do: nil
+ def get_report(activity_id) do
+ opts = %{
+ type: "Flag",
+ skip_preload: true,
+ preload_report_notes: true
+ }
+
+ ActivityPub.fetch_activities_query([], opts)
+ |> where(id: ^activity_id)
+ |> Repo.one()
+ end
+
def change(struct, params \\ %{}) do
struct
|> cast(params, [:data, :recipients])
diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex
index 9e65bedad..fe2e8cb5c 100644
--- a/lib/pleroma/activity/ir/topics.ex
+++ b/lib/pleroma/activity/ir/topics.ex
@@ -40,7 +40,8 @@ defmodule Pleroma.Activity.Ir.Topics do
end
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
- tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
+ tags ++
+ remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
end
defp item_creation_tags(tags, _, _) do
@@ -55,9 +56,19 @@ defmodule Pleroma.Activity.Ir.Topics do
defp hashtags_to_topics(_), do: []
+ defp remote_topics(%{local: true}), do: []
+
+ defp remote_topics(%{actor: actor}) when is_binary(actor),
+ do: ["public:remote:" <> URI.parse(actor).host]
+
+ defp remote_topics(_), do: []
+
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
+ defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
+ do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
+
defp attachment_topics(_object, _act), do: ["public:media"]
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 958e32db2..7c4cd9626 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -95,11 +95,12 @@ defmodule Pleroma.Application do
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
+ {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
{Oban, Config.get(Oban)}
] ++
task_children(@env) ++
dont_run_in_test(@env) ++
- chat_child(@env, chat_enabled?()) ++
+ chat_child(chat_enabled?()) ++
[
Pleroma.Web.Endpoint,
Pleroma.Gopher.Server
@@ -150,7 +151,10 @@ defmodule Pleroma.Application do
Pleroma.Web.Endpoint.MetricsExporter.setup()
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
- Pleroma.Web.Endpoint.Instrumenter.setup()
+
+ # Note: disabled until prometheus-phx is integrated into prometheus-phoenix:
+ # Pleroma.Web.Endpoint.Instrumenter.setup()
+ PrometheusPhx.setup()
end
defp cachex_children do
@@ -164,7 +168,11 @@ defmodule Pleroma.Application do
build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
build_cachex("failed_proxy_url", limit: 2500),
- build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
+ build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
+ build_cachex("chat_message_id_idempotency_key",
+ expiration: chat_message_id_idempotency_key_expiration(),
+ limit: 500_000
+ )
]
end
@@ -174,6 +182,9 @@ defmodule Pleroma.Application do
defp idempotency_expiration,
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
+ defp chat_message_id_idempotency_key_expiration,
+ do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
+
defp seconds_valid_interval,
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
@@ -201,11 +212,14 @@ defmodule Pleroma.Application do
]
end
- defp chat_child(_env, true) do
- [Pleroma.Web.ChatChannel.ChatChannelState]
+ defp chat_child(true) do
+ [
+ Pleroma.Web.ChatChannel.ChatChannelState,
+ {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
+ ]
end
- defp chat_child(_, _), do: []
+ defp chat_child(_), do: []
defp task_children(:test) do
[
diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex
index 337506647..201b55ab4 100644
--- a/lib/pleroma/captcha/kocaptcha.ex
+++ b/lib/pleroma/captcha/kocaptcha.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
def new do
endpoint = Pleroma.Config.get!([__MODULE__, :endpoint])
- case Tesla.get(endpoint <> "/new") do
+ case Pleroma.HTTP.get(endpoint <> "/new") do
{:error, _} ->
%{error: :kocaptcha_service_unavailable}
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index e76eb0087..77933f0be 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -43,7 +43,7 @@ defmodule Pleroma.Conversation do
def maybe_create_recipientships(participation, activity) do
participation = Repo.preload(participation, :recipients)
- if participation.recipients |> Enum.empty?() do
+ if Enum.empty?(participation.recipients) do
recipients = User.get_all_by_ap_id(activity.recipients)
RecipientShip.create(recipients, participation)
end
@@ -69,10 +69,6 @@ defmodule Pleroma.Conversation do
Enum.map(users, fn user ->
invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
- unless invisible_conversation do
- User.increment_unread_conversation_count(conversation, user)
- end
-
opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
{:ok, participation} =
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 8bc3e85d6..4c32b273a 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -63,21 +63,10 @@ defmodule Pleroma.Conversation.Participation do
end
end
- def mark_as_read(participation) do
- __MODULE__
- |> where(id: ^participation.id)
- |> update(set: [read: true])
- |> select([p], p)
- |> Repo.update_all([])
- |> case do
- {1, [participation]} ->
- participation = Repo.preload(participation, :user)
- User.set_unread_conversation_count(participation.user)
- {:ok, participation}
-
- error ->
- error
- end
+ def mark_as_read(%__MODULE__{} = participation) do
+ participation
+ |> change(read: true)
+ |> Repo.update()
end
def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
@@ -93,7 +82,6 @@ defmodule Pleroma.Conversation.Participation do
|> update([p], set: [read: true])
|> Repo.update_all([])
- {:ok, user} = User.set_unread_conversation_count(user)
{:ok, user, []}
end
@@ -108,7 +96,6 @@ defmodule Pleroma.Conversation.Participation do
|> select([p], p)
|> Repo.update_all([])
- {:ok, user} = User.set_unread_conversation_count(user)
{:ok, user, participations}
end
@@ -220,6 +207,12 @@ defmodule Pleroma.Conversation.Participation do
{:ok, Repo.preload(participation, :recipients, force: true)}
end
+ @spec unread_count(User.t()) :: integer()
+ def unread_count(%User{id: user_id}) do
+ from(q in __MODULE__, where: q.user_id == ^user_id and q.read == false)
+ |> Repo.aggregate(:count, :id)
+ end
+
def unread_conversation_count_for_user(user) do
from(p in __MODULE__,
where: p.user_id == ^user.id,
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
index 13618b509..a583e2a5b 100644
--- a/lib/pleroma/docs/json.ex
+++ b/lib/pleroma/docs/json.ex
@@ -11,7 +11,11 @@ defmodule Pleroma.Docs.JSON do
@spec compile :: :ok
def compile do
- :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
+ descriptions =
+ Pleroma.Web.ActivityPub.MRF.config_descriptions()
+ |> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
+
+ :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(descriptions))
end
@spec compiled_descriptions :: Map.t()
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index 8979db2f8..02274554f 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -18,10 +18,6 @@ defmodule Pleroma.Emails.AdminEmail do
Keyword.get(instance_config(), :notify_email, instance_config()[:email])
end
- defp user_url(user) do
- Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
- end
-
def test_email(mail_to \\ nil) do
html_body = """
<h3>Instance Test Email</h3>
@@ -69,8 +65,8 @@ defmodule Pleroma.Emails.AdminEmail do
end
html_body = """
- <p>Reported by: <a href="#{user_url(reporter)}">#{reporter.nickname}</a></p>
- <p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
+ <p>Reported by: <a href="#{reporter.ap_id}">#{reporter.nickname}</a></p>
+ <p>Reported Account: <a href="#{account.ap_id}">#{account.nickname}</a></p>
#{comment_html}
#{statuses_html}
<p>
@@ -86,7 +82,7 @@ defmodule Pleroma.Emails.AdminEmail do
def new_unapproved_registration(to, account) do
html_body = """
- <p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
+ <p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
"""
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 831e5464f..3b9275bc3 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -202,4 +202,30 @@ defmodule Pleroma.Emails.UserEmail do
Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)
end
+
+ def backup_is_ready_email(backup, admin_user_id \\ nil) do
+ %{user: user} = Pleroma.Repo.preload(backup, :user)
+ download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
+
+ html_body =
+ if is_nil(admin_user_id) do
+ """
+ <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="#{download_url}">#{download_url}</a></p>
+ """
+ else
+ admin = Pleroma.Repo.get(User, admin_user_id)
+
+ """
+ <p>Admin @#{admin.nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="#{download_url}">#{download_url}</a></p>
+ """
+ end
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject("Your account archive is ready")
+ |> html_body(html_body)
+ end
end
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index 0670f29f1..ca58e5432 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -594,7 +594,7 @@ defmodule Pleroma.Emoji.Pack do
end
defp download_archive(url, sha) do
- with {:ok, %{body: archive}} <- Tesla.get(url) do
+ with {:ok, %{body: archive}} <- Pleroma.HTTP.get(url) do
if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
{:ok, archive}
else
@@ -617,7 +617,7 @@ defmodule Pleroma.Emoji.Pack do
end
defp update_sha_and_save_metadata(pack, data) do
- with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]),
+ with {:ok, %{body: zip}} <- Pleroma.HTTP.get(data[:"fallback-src"]),
:ok <- validate_has_all_files(pack, zip) do
fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16()
diff --git a/lib/pleroma/helpers/inet_helper.ex b/lib/pleroma/helpers/inet_helper.ex
new file mode 100644
index 000000000..126f82381
--- /dev/null
+++ b/lib/pleroma/helpers/inet_helper.ex
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Helpers.InetHelper do
+ def parse_address(ip) when is_tuple(ip) do
+ {:ok, ip}
+ end
+
+ def parse_address(ip) when is_binary(ip) do
+ ip
+ |> String.to_charlist()
+ |> parse_address()
+ end
+
+ def parse_address(ip) do
+ :inet.parse_address(ip)
+ end
+end
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index 557e8decf..7315bd7cb 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Instances do
defdelegate reachable?(url_or_host), to: @adapter
defdelegate set_reachable(url_or_host), to: @adapter
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
+ defdelegate get_consistently_unreachable(), to: @adapter
def set_consistently_unreachable(url_or_host),
do: set_unreachable(url_or_host, reachability_datetime_threshold())
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index f0f601469..df471a39d 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -119,6 +119,17 @@ defmodule Pleroma.Instances.Instance do
def set_unreachable(_, _), do: {:error, nil}
+ def get_consistently_unreachable do
+ reachability_datetime_threshold = Instances.reachability_datetime_threshold()
+
+ from(i in Instance,
+ where: ^reachability_datetime_threshold > i.unreachable_since,
+ order_by: i.unreachable_since,
+ select: {i.host, i.unreachable_since}
+ )
+ |> Repo.all()
+ end
+
defp parse_datetime(datetime) when is_binary(datetime) do
NaiveDateTime.from_iso8601(datetime)
end
diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex
deleted file mode 100644
index 6ee055f50..000000000
--- a/lib/pleroma/mime.ex
+++ /dev/null
@@ -1,120 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.MIME do
- @moduledoc """
- Returns the mime-type of a binary and optionally a normalized file-name.
- """
- @default "application/octet-stream"
- @read_bytes 35
-
- @spec file_mime_type(String.t(), String.t()) ::
- {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
- def file_mime_type(path, filename) do
- with {:ok, content_type} <- file_mime_type(path),
- filename <- fix_extension(filename, content_type) do
- {:ok, content_type, filename}
- end
- end
-
- @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error
- def file_mime_type(filename) do
- File.open(filename, [:read], fn f ->
- check_mime_type(IO.binread(f, @read_bytes))
- end)
- end
-
- def bin_mime_type(binary, filename) do
- with {:ok, content_type} <- bin_mime_type(binary),
- filename <- fix_extension(filename, content_type) do
- {:ok, content_type, filename}
- end
- end
-
- @spec bin_mime_type(binary()) :: {:ok, String.t()} | :error
- def bin_mime_type(<<head::binary-size(@read_bytes), _::binary>>) do
- {:ok, check_mime_type(head)}
- end
-
- def bin_mime_type(_), do: :error
-
- def mime_type(<<_::binary>>), do: {:ok, @default}
-
- defp fix_extension(filename, content_type) do
- parts = String.split(filename, ".")
-
- new_filename =
- if length(parts) > 1 do
- Enum.drop(parts, -1) |> Enum.join(".")
- else
- Enum.join(parts)
- end
-
- cond do
- content_type == "application/octet-stream" ->
- filename
-
- ext = List.first(MIME.extensions(content_type)) ->
- new_filename <> "." <> ext
-
- true ->
- Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".")
- end
- end
-
- defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do
- "image/png"
- end
-
- defp check_mime_type(<<0x47, 0x49, 0x46, 0x38, _, 0x61, _::binary>>) do
- "image/gif"
- end
-
- defp check_mime_type(<<0xFF, 0xD8, 0xFF, _::binary>>) do
- "image/jpeg"
- end
-
- defp check_mime_type(<<0x1A, 0x45, 0xDF, 0xA3, _::binary>>) do
- "video/webm"
- end
-
- defp check_mime_type(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do
- "video/mp4"
- end
-
- defp check_mime_type(<<0x49, 0x44, 0x33, _::binary>>) do
- "audio/mpeg"
- end
-
- defp check_mime_type(<<255, 251, _, 68, 0, 0, 0, 0, _::binary>>) do
- "audio/mpeg"
- end
-
- defp check_mime_type(
- <<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::size(160), 0x80, 0x74, 0x68, 0x65,
- 0x6F, 0x72, 0x61, _::binary>>
- ) do
- "video/ogg"
- end
-
- defp check_mime_type(<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::binary>>) do
- "audio/ogg"
- end
-
- defp check_mime_type(<<"RIFF", _::binary-size(4), "WAVE", _::binary>>) do
- "audio/wav"
- end
-
- defp check_mime_type(<<"RIFF", _::binary-size(4), "WEBP", _::binary>>) do
- "image/webp"
- end
-
- defp check_mime_type(<<"RIFF", _::binary-size(4), "AVI.", _::binary>>) do
- "video/avi"
- end
-
- defp check_mime_type(_) do
- @default
- end
-end
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
index 38a863443..142dd8e0a 100644
--- a/lib/pleroma/moderation_log.ex
+++ b/lib/pleroma/moderation_log.ex
@@ -655,6 +655,16 @@ defmodule Pleroma.ModerationLog do
"@#{actor_nickname} deleted chat message ##{subject_id}"
end
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "create_backup",
+ "subject" => %{"nickname" => user_nickname}
+ }
+ }) do
+ "@#{actor_nickname} requested account backup for @#{user_nickname}"
+ end
+
defp nicknames_to_string(nicknames) do
nicknames
|> Enum.map(&"@#{&1}")
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 8868a910e..dd7a1c824 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -70,6 +70,7 @@ defmodule Pleroma.Notification do
move
pleroma:chat_mention
pleroma:emoji_reaction
+ pleroma:report
reblog
}
@@ -367,7 +368,7 @@ defmodule Pleroma.Notification do
end
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
- when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
+ when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag"] do
do_create_notifications(activity, options)
end
@@ -410,6 +411,9 @@ defmodule Pleroma.Notification do
"EmojiReact" ->
"pleroma:emoji_reaction"
+ "Flag" ->
+ "pleroma:report"
+
# Compatibility with old reactions
"EmojiReaction" ->
"pleroma:emoji_reaction"
@@ -467,7 +471,7 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
- when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
+ when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact", "Flag"] do
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
potential_receivers =
@@ -503,6 +507,10 @@ defmodule Pleroma.Notification do
[object_id]
end
+ def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag"}}) do
+ User.all_superusers() |> Enum.map(fn user -> user.ap_id end)
+ end
+
def get_potential_receiver_ap_ids(activity) do
[]
|> Utils.maybe_notify_to_recipients(activity)
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 169298b34..ae4301738 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -232,8 +232,24 @@ defmodule Pleroma.Object.Fetcher do
|> sign_fetch(id, date)
case HTTP.get(id, headers) do
- {:ok, %{body: body, status: code}} when code in 200..299 ->
- {:ok, body}
+ {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
+ case List.keyfind(headers, "content-type", 0) do
+ {_, content_type} ->
+ case Plug.Conn.Utils.media_type(content_type) do
+ {:ok, "application", "activity+json", _} ->
+ {:ok, body}
+
+ {:ok, "application", "ld+json",
+ %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
+ {:ok, body}
+
+ _ ->
+ {:error, {:content_type, content_type}}
+ end
+
+ _ ->
+ {:error, {:content_type, nil}}
+ end
{:ok, %{status: code}} when code in [404, 410] ->
{:error, "Object has been deleted"}
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index e5c9c668b..48afe901e 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -23,7 +23,6 @@ defmodule Pleroma.Stats do
@impl true
def init(_args) do
- if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
{:ok, nil, {:continue, :calculate_stats}}
end
@@ -32,11 +31,6 @@ defmodule Pleroma.Stats do
GenServer.call(__MODULE__, :force_update)
end
- @doc "Performs collect stats"
- def do_collect do
- GenServer.cast(__MODULE__, :run_update)
- end
-
@doc "Returns stats data"
@spec get_stats() :: %{
domain_count: non_neg_integer(),
@@ -111,7 +105,11 @@ defmodule Pleroma.Stats do
@impl true
def handle_continue(:calculate_stats, _) do
stats = calculate_stat_data()
- Process.send_after(self(), :run_update, @interval)
+
+ unless Pleroma.Config.get(:env) == :test do
+ Process.send_after(self(), :run_update, @interval)
+ end
+
{:noreply, stats}
end
@@ -127,13 +125,6 @@ defmodule Pleroma.Stats do
end
@impl true
- def handle_cast(:run_update, _state) do
- new_stats = calculate_stat_data()
-
- {:noreply, new_stats}
- end
-
- @impl true
def handle_info(:run_update, _) do
new_stats = calculate_stat_data()
Process.send_after(self(), :run_update, @interval)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 015c87593..db2cc1dae 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -66,6 +66,7 @@ defmodule Pleroma.Upload do
end
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
+ @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
def store(upload, opts \\ []) do
opts = get_opts(opts)
@@ -139,14 +140,13 @@ defmodule Pleroma.Upload do
end
defp prepare_upload(%Plug.Upload{} = file, opts) do
- with :ok <- check_file_size(file.path, opts.size_limit),
- {:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do
+ with :ok <- check_file_size(file.path, opts.size_limit) do
{:ok,
%__MODULE__{
id: UUID.generate(),
- name: name,
+ name: file.filename,
tempfile: file.path,
- content_type: content_type
+ content_type: file.content_type
}}
end
end
@@ -154,16 +154,17 @@ defmodule Pleroma.Upload do
defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
data = Base.decode64!(parsed["data"], ignore: :whitespace)
- hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data)))
+ hash = Base.encode16(:crypto.hash(:sha256, data), lower: true)
with :ok <- check_binary_size(data, opts.size_limit),
tmp_path <- tempfile_for_image(data),
- {:ok, content_type, name} <-
- Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do
+ {:ok, %{mime_type: content_type}} <-
+ Majic.perform({:bytes, data}, pool: Pleroma.MajicPool),
+ [ext | _] <- MIME.extensions(content_type) do
{:ok,
%__MODULE__{
id: UUID.generate(),
- name: name,
+ name: hash <> "." <> ext,
tempfile: tmp_path,
content_type: content_type
}}
@@ -172,7 +173,7 @@ defmodule Pleroma.Upload do
# For Mix.Tasks.MigrateLocalUploads
defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do
- with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do
+ with {:ok, %{mime_type: content_type}} <- Majic.perform(path, pool: Pleroma.MajicPool) do
{:ok, %__MODULE__{upload | content_type: content_type}}
end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 0dabb2a1e..f93fd7a0c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -107,7 +107,7 @@ defmodule Pleroma.User do
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
field(:following_count, :integer, default: 0)
- field(:locked, :boolean, default: false)
+ field(:is_locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false)
field(:approval_pending, :boolean, default: false)
@@ -128,7 +128,6 @@ defmodule Pleroma.User do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
- field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
@@ -136,7 +135,7 @@ defmodule Pleroma.User do
field(:pleroma_settings_store, :map, default: %{})
field(:fields, {:array, :map}, default: [])
field(:raw_fields, {:array, :map}, default: [])
- field(:discoverable, :boolean, default: false)
+ field(:is_discoverable, :boolean, default: false)
field(:invisible, :boolean, default: false)
field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false)
@@ -426,7 +425,6 @@ defmodule Pleroma.User do
params,
[
:bio,
- :name,
:emoji,
:ap_id,
:inbox,
@@ -436,7 +434,7 @@ defmodule Pleroma.User do
:avatar,
:ap_enabled,
:banner,
- :locked,
+ :is_locked,
:last_refreshed_at,
:uri,
:follower_address,
@@ -448,14 +446,16 @@ defmodule Pleroma.User do
:follower_count,
:fields,
:following_count,
- :discoverable,
+ :is_discoverable,
:invisible,
:actor_type,
:also_known_as,
:accepts_chat_messages
]
)
- |> validate_required([:name, :ap_id])
+ |> cast(params, [:name], empty_values: [])
+ |> validate_required([:ap_id])
+ |> validate_required([:name], trim: false)
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
@@ -479,7 +479,7 @@ defmodule Pleroma.User do
:public_key,
:inbox,
:shared_inbox,
- :locked,
+ :is_locked,
:no_rich_text,
:default_scope,
:banner,
@@ -495,7 +495,7 @@ defmodule Pleroma.User do
:fields,
:raw_fields,
:pleroma_settings_store,
- :discoverable,
+ :is_discoverable,
:actor_type,
:also_known_as,
:accepts_chat_messages
@@ -765,6 +765,16 @@ defmodule Pleroma.User do
follow_all(user, autofollowed_users)
end
+ defp autofollowing_users(user) do
+ candidates = Config.get([:instance, :autofollowing_nicknames])
+
+ User.Query.build(%{nickname: candidates, local: true, deactivated: false})
+ |> Repo.all()
+ |> Enum.each(&follow(&1, user, :follow_accept))
+
+ {:ok, :success}
+ end
+
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset) do
@@ -787,6 +797,7 @@ defmodule Pleroma.User do
def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
with {:ok, user} <- autofollow_users(user),
+ {:ok, _} <- autofollowing_users(user),
{:ok, user} <- set_cache(user),
{:ok, _} <- send_welcome_email(user),
{:ok, _} <- send_welcome_message(user),
@@ -879,7 +890,7 @@ defmodule Pleroma.User do
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
# "Locked" (self-locked) users demand explicit authorization of follow requests
- def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
+ def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
follow(follower, followed, :follow_pending)
end
@@ -986,7 +997,7 @@ defmodule Pleroma.User do
end
def locked?(%User{} = user) do
- user.locked || false
+ user.is_locked || false
end
def get_by_id(id) do
@@ -1325,47 +1336,6 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def set_unread_conversation_count(%User{local: true} = user) do
- unread_query = Participation.unread_conversation_count_for_user(user)
-
- User
- |> join(:inner, [u], p in subquery(unread_query))
- |> update([u, p],
- set: [unread_conversation_count: p.count]
- )
- |> where([u], u.id == ^user.id)
- |> select([u], u)
- |> Repo.update_all([])
- |> case do
- {1, [user]} -> set_cache(user)
- _ -> {:error, user}
- end
- end
-
- def set_unread_conversation_count(user), do: {:ok, user}
-
- def increment_unread_conversation_count(conversation, %User{local: true} = user) do
- unread_query =
- Participation.unread_conversation_count_for_user(user)
- |> where([p], p.conversation_id == ^conversation.id)
-
- User
- |> join(:inner, [u], p in subquery(unread_query))
- |> update([u, p],
- inc: [unread_conversation_count: 1]
- )
- |> where([u], u.id == ^user.id)
- |> where([u, p], p.count == 0)
- |> select([u], u)
- |> Repo.update_all([])
- |> case do
- {1, [user]} -> set_cache(user)
- _ -> {:error, user}
- end
- end
-
- def increment_unread_conversation_count(_, user), do: {:ok, user}
-
@spec get_users_from_set([String.t()], keyword()) :: [User.t()]
def get_users_from_set(ap_ids, opts \\ []) do
local_only = Keyword.get(opts, :local_only, true)
@@ -1386,14 +1356,48 @@ defmodule Pleroma.User do
|> Repo.all()
end
- @spec mute(User.t(), User.t(), boolean()) ::
+ @spec mute(User.t(), User.t(), map()) ::
{:ok, list(UserRelationship.t())} | {:error, String.t()}
- def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
- add_to_mutes(muter, mutee, notifications?)
+ def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
+ notifications? = Map.get(params, :notifications, true)
+ expires_in = Map.get(params, :expires_in, 0)
+
+ with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
+ {:ok, user_notification_mute} <-
+ (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
+ {:ok, nil} do
+ if expires_in > 0 do
+ Pleroma.Workers.MuteExpireWorker.enqueue(
+ "unmute_user",
+ %{"muter_id" => muter.id, "mutee_id" => mutee.id},
+ schedule_in: expires_in
+ )
+ end
+
+ {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
+ end
end
def unmute(%User{} = muter, %User{} = mutee) do
- remove_from_mutes(muter, mutee)
+ with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
+ {:ok, user_notification_mute} <-
+ UserRelationship.delete_notification_mute(muter, mutee) do
+ {:ok, [user_mute, user_notification_mute]}
+ end
+ end
+
+ def unmute(muter_id, mutee_id) do
+ with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
+ {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
+ unmute(muter, mutee)
+ else
+ {who, result} = error ->
+ Logger.warn(
+ "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
+ )
+
+ {:error, error}
+ end
end
def subscribe(%User{} = subscriber, %User{} = target) do
@@ -1656,7 +1660,7 @@ defmodule Pleroma.User do
note_count: 0,
follower_count: 0,
following_count: 0,
- locked: false,
+ is_locked: false,
confirmation_pending: false,
password_reset_pending: false,
approval_pending: false,
@@ -1673,7 +1677,7 @@ defmodule Pleroma.User do
pleroma_settings_store: %{},
fields: [],
raw_fields: [],
- discoverable: false,
+ is_discoverable: false,
also_known_as: []
})
end
@@ -2393,23 +2397,6 @@ defmodule Pleroma.User do
UserRelationship.delete_block(user, blocked)
end
- defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
- with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
- {:ok, user_notification_mute} <-
- (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
- {:ok, nil} do
- {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
- end
- end
-
- defp remove_from_mutes(user, %User{} = muted_user) do
- with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
- {:ok, user_notification_mute} <-
- UserRelationship.delete_notification_mute(user, muted_user) do
- {:ok, [user_mute, user_notification_mute]}
- end
- end
-
def set_invisible(user, invisible) do
params = %{invisible: invisible}
diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex
new file mode 100644
index 000000000..a9041fd94
--- /dev/null
+++ b/lib/pleroma/user/backup.ex
@@ -0,0 +1,258 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.Backup do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+ import Pleroma.Web.Gettext
+
+ require Pleroma.Constants
+
+ alias Pleroma.Activity
+ alias Pleroma.Bookmark
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.ActivityPub.UserView
+ alias Pleroma.Workers.BackupWorker
+
+ schema "backups" do
+ field(:content_type, :string)
+ field(:file_name, :string)
+ field(:file_size, :integer, default: 0)
+ field(:processed, :boolean, default: false)
+
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+
+ timestamps()
+ end
+
+ def create(user, admin_id \\ nil) do
+ with :ok <- validate_email_enabled(),
+ :ok <- validate_user_email(user),
+ :ok <- validate_limit(user, admin_id),
+ {:ok, backup} <- user |> new() |> Repo.insert() do
+ BackupWorker.process(backup, admin_id)
+ end
+ end
+
+ def new(user) do
+ rand_str = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
+ datetime = Calendar.NaiveDateTime.Format.iso8601_basic(NaiveDateTime.utc_now())
+ name = "archive-#{user.nickname}-#{datetime}-#{rand_str}.zip"
+
+ %__MODULE__{
+ user_id: user.id,
+ content_type: "application/zip",
+ file_name: name
+ }
+ end
+
+ def delete(backup) do
+ uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
+
+ with :ok <- uploader.delete_file(Path.join("backups", backup.file_name)) do
+ Repo.delete(backup)
+ end
+ end
+
+ defp validate_limit(_user, admin_id) when is_binary(admin_id), do: :ok
+
+ defp validate_limit(user, nil) do
+ case get_last(user.id) do
+ %__MODULE__{inserted_at: inserted_at} ->
+ days = Pleroma.Config.get([__MODULE__, :limit_days])
+ diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days)
+
+ if diff > days do
+ :ok
+ else
+ {:error,
+ dngettext(
+ "errors",
+ "Last export was less than a day ago",
+ "Last export was less than %{days} days ago",
+ days,
+ days: days
+ )}
+ end
+
+ nil ->
+ :ok
+ end
+ end
+
+ defp validate_email_enabled do
+ if Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
+ :ok
+ else
+ {:error, dgettext("errors", "Backups require enabled email")}
+ end
+ end
+
+ defp validate_user_email(%User{email: nil}) do
+ {:error, dgettext("errors", "Email is required")}
+ end
+
+ defp validate_user_email(%User{email: email}) when is_binary(email), do: :ok
+
+ def get_last(user_id) do
+ __MODULE__
+ |> where(user_id: ^user_id)
+ |> order_by(desc: :id)
+ |> limit(1)
+ |> Repo.one()
+ end
+
+ def list(%User{id: user_id}) do
+ __MODULE__
+ |> where(user_id: ^user_id)
+ |> order_by(desc: :id)
+ |> Repo.all()
+ end
+
+ def remove_outdated(%__MODULE__{id: latest_id, user_id: user_id}) do
+ __MODULE__
+ |> where(user_id: ^user_id)
+ |> where([b], b.id != ^latest_id)
+ |> Repo.all()
+ |> Enum.each(&BackupWorker.delete/1)
+ end
+
+ def get(id), do: Repo.get(__MODULE__, id)
+
+ def process(%__MODULE__{} = backup) do
+ with {:ok, zip_file} <- export(backup),
+ {:ok, %{size: size}} <- File.stat(zip_file),
+ {:ok, _upload} <- upload(backup, zip_file) do
+ backup
+ |> cast(%{file_size: size, processed: true}, [:file_size, :processed])
+ |> Repo.update()
+ end
+ end
+
+ @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json']
+ def export(%__MODULE__{} = backup) do
+ backup = Repo.preload(backup, :user)
+ name = String.trim_trailing(backup.file_name, ".zip")
+ dir = dir(name)
+
+ with :ok <- File.mkdir(dir),
+ :ok <- actor(dir, backup.user),
+ :ok <- statuses(dir, backup.user),
+ :ok <- likes(dir, backup.user),
+ :ok <- bookmarks(dir, backup.user),
+ {:ok, zip_path} <- :zip.create(String.to_charlist(dir <> ".zip"), @files, cwd: dir),
+ {:ok, _} <- File.rm_rf(dir) do
+ {:ok, to_string(zip_path)}
+ end
+ end
+
+ def dir(name) do
+ dir = Pleroma.Config.get([__MODULE__, :dir]) || System.tmp_dir!()
+ Path.join(dir, name)
+ end
+
+ def upload(%__MODULE__{} = backup, zip_path) do
+ uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
+
+ upload = %Pleroma.Upload{
+ name: backup.file_name,
+ tempfile: zip_path,
+ content_type: backup.content_type,
+ path: Path.join("backups", backup.file_name)
+ }
+
+ with {:ok, _} <- Pleroma.Uploaders.Uploader.put_file(uploader, upload),
+ :ok <- File.rm(zip_path) do
+ {:ok, upload}
+ end
+ end
+
+ defp actor(dir, user) do
+ with {:ok, json} <-
+ UserView.render("user.json", %{user: user})
+ |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"})
+ |> Jason.encode() do
+ File.write(Path.join(dir, "actor.json"), json)
+ end
+ end
+
+ defp write_header(file, name) do
+ IO.write(
+ file,
+ """
+ {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "#{name}.json",
+ "type": "OrderedCollection",
+ "orderedItems": [
+
+ """
+ )
+ end
+
+ defp write(query, dir, name, fun) do
+ path = Path.join(dir, "#{name}.json")
+
+ with {:ok, file} <- File.open(path, [:write, :utf8]),
+ :ok <- write_header(file, name) do
+ total =
+ query
+ |> Pleroma.Repo.chunk_stream(100)
+ |> Enum.reduce(0, fn i, acc ->
+ with {:ok, data} <- fun.(i),
+ {:ok, str} <- Jason.encode(data),
+ :ok <- IO.write(file, str <> ",\n") do
+ acc + 1
+ else
+ _ -> acc
+ end
+ end)
+
+ with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n \"totalItems\": #{total}}") do
+ File.close(file)
+ end
+ end
+ end
+
+ defp bookmarks(dir, %{id: user_id} = _user) do
+ Bookmark
+ |> where(user_id: ^user_id)
+ |> join(:inner, [b], activity in assoc(b, :activity))
+ |> select([b, a], %{id: b.id, object: fragment("(?)->>'object'", a.data)})
+ |> write(dir, "bookmarks", fn a -> {:ok, a.object} end)
+ end
+
+ defp likes(dir, user) do
+ user.ap_id
+ |> Activity.Queries.by_actor()
+ |> Activity.Queries.by_type("Like")
+ |> select([like], %{id: like.id, object: fragment("(?)->>'object'", like.data)})
+ |> write(dir, "likes", fn a -> {:ok, a.object} end)
+ end
+
+ defp statuses(dir, user) do
+ opts =
+ %{}
+ |> Map.put(:type, ["Create", "Announce"])
+ |> Map.put(:actor_id, user.ap_id)
+
+ [
+ [Pleroma.Constants.as_public(), user.ap_id],
+ User.following(user),
+ Pleroma.List.memberships(user)
+ ]
+ |> Enum.concat()
+ |> ActivityPub.fetch_activities_query(opts)
+ |> write(dir, "outbox", fn a ->
+ with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do
+ {:ok, Map.delete(activity, "@context")}
+ end
+ end)
+ end
+end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 2440bf890..7ef2a1455 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -43,6 +43,7 @@ defmodule Pleroma.User.Query do
active: boolean(),
deactivated: boolean(),
need_approval: boolean(),
+ unconfirmed: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
@@ -55,7 +56,8 @@ defmodule Pleroma.User.Query do
ap_id: [String.t()],
order_by: term(),
select: term(),
- limit: pos_integer()
+ limit: pos_integer(),
+ actor_types: [String.t()]
}
| map()
@@ -114,6 +116,10 @@ defmodule Pleroma.User.Query do
where(query, [u], u.is_admin == ^bool)
end
+ defp compose_query({:actor_types, actor_types}, query) when is_list(actor_types) do
+ where(query, [u], u.actor_type in ^actor_types)
+ end
+
defp compose_query({:is_moderator, bool}, query) do
where(query, [u], u.is_moderator == ^bool)
end
@@ -156,6 +162,10 @@ defmodule Pleroma.User.Query do
where(query, [u], u.approval_pending)
end
+ defp compose_query({:unconfirmed, _}, query) do
+ where(query, [u], u.confirmation_pending)
+ end
+
defp compose_query({:followers, %User{id: id}}, query) do
query
|> where([u], u.id != ^id)
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 35a828008..2dab67211 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -164,7 +164,7 @@ defmodule Pleroma.User.Search do
end
defp filter_discoverable_users(query) do
- from(q in query, where: q.discoverable == true)
+ from(q in query, where: q.is_discoverable == true)
end
defp filter_internal_users(query) do
diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index 7779826e3..6ed19d3dd 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -172,7 +172,7 @@ defmodule Pleroma.Web do
def channel do
quote do
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
- use Phoenix.Channel
+ import Phoenix.Channel
import Pleroma.Web.Gettext
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index eb44cffec..d8f685d38 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -827,7 +827,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
query =
from([activity] in query,
where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
- where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
+ where:
+ fragment(
+ "not (?->'to' \\?| ?) or ? = ?",
+ activity.data,
+ ^mutes,
+ activity.actor,
+ ^user.ap_id
+ )
)
unless opts[:skip_preload] do
@@ -930,16 +937,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted_reblogs(query, _), do: query
- defp restrict_instance(query, %{instance: instance}) do
- users =
- from(
- u in User,
- select: u.ap_id,
- where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
- )
- |> Repo.all()
-
- from(activity in query, where: activity.actor in ^users)
+ defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do
+ from(
+ activity in query,
+ where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance)
+ )
end
defp restrict_instance(query, _), do: query
@@ -1228,11 +1230,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{String.trim(name, ":"), url}
end)
- locked = data["manuallyApprovesFollowers"] || false
+ is_locked = data["manuallyApprovesFollowers"] || false
capabilities = data["capabilities"] || %{}
accepts_chat_messages = capabilities["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
- discoverable = data["discoverable"] || false
+ is_discoverable = data["discoverable"] || false
invisible = data["invisible"] || false
actor_type = data["type"] || "Person"
@@ -1257,8 +1259,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
banner: banner,
fields: fields,
emoji: emojis,
- locked: locked,
- discoverable: discoverable,
+ is_locked: is_locked,
+ is_discoverable: is_discoverable,
invisible: invisible,
avatar: avatar,
name: data["name"],
@@ -1371,6 +1373,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, data} <- user_data_from_user_object(data) do
{:ok, maybe_update_follow_information(data)}
else
+ # If this has been deleted, only log a debug and not an error
{:error, "Object has been deleted" = e} ->
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 6bf7421bb..31df80adb 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -45,6 +45,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
)
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
+
plug(
Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
@@ -412,7 +414,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
object =
object
|> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id())
+ |> Map.put("attributedTo", user.ap_id)
|> Transmogrifier.fix_object()
ActivityPub.create(%{
@@ -456,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname} = params
) do
- actor = user.ap_id()
+ actor = user.ap_id
params =
params
@@ -523,19 +525,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{new_user, for_user}
end
- @doc """
- Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
-
- Parameters:
- - (required) `file`: data of the media
- - (optionnal) `description`: description of the media, intended for accessibility
-
- Response:
- - HTTP Code: 201 Created
- - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
-
- Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
- """
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 5e5361082..6e73b2f22 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -3,7 +3,62 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF do
+ require Logger
+
+ @mrf_config_descriptions [
+ %{
+ group: :pleroma,
+ key: :mrf,
+ tab: :mrf,
+ label: "MRF",
+ type: :group,
+ description: "General MRF settings",
+ children: [
+ %{
+ key: :policies,
+ type: [:module, {:list, :module}],
+ description:
+ "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
+ suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
+ },
+ %{
+ key: :transparency,
+ label: "MRF transparency",
+ type: :boolean,
+ description:
+ "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
+ },
+ %{
+ key: :transparency_exclusions,
+ label: "MRF transparency exclusions",
+ type: {:list, :string},
+ description:
+ "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
+ suggestions: [
+ "exclusion.com"
+ ]
+ }
+ ]
+ }
+ ]
+
+ @default_description %{
+ label: "",
+ description: ""
+ }
+
+ @required_description_keys [:key, :related_policy]
+
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
+ @callback describe() :: {:ok | :error, Map.t()}
+ @callback config_description() :: %{
+ optional(:children) => [map()],
+ key: atom(),
+ related_policy: String.t(),
+ label: String.t(),
+ description: String.t()
+ }
+ @optional_callbacks config_description: 0
def filter(policies, %{} = message) do
policies
@@ -51,8 +106,6 @@ defmodule Pleroma.Web.ActivityPub.MRF do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
- @callback describe() :: {:ok | :error, Map.t()}
-
def describe(policies) do
{:ok, policy_configs} =
policies
@@ -82,4 +135,41 @@ defmodule Pleroma.Web.ActivityPub.MRF do
end
def describe, do: get_policies() |> describe()
+
+ def config_descriptions do
+ Pleroma.Web.ActivityPub.MRF
+ |> Pleroma.Docs.Generator.list_behaviour_implementations()
+ |> config_descriptions()
+ end
+
+ def config_descriptions(policies) do
+ Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc ->
+ if function_exported?(policy, :config_description, 0) do
+ description =
+ @default_description
+ |> Map.merge(policy.config_description)
+ |> Map.put(:group, :pleroma)
+ |> Map.put(:tab, :mrf)
+ |> Map.put(:type, :group)
+
+ if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do
+ [description | acc]
+ else
+ Logger.warn(
+ "#{policy} config description doesn't have one or all required keys #{
+ inspect(@required_description_keys)
+ }"
+ )
+
+ acc
+ end
+ else
+ Logger.debug(
+ "#{policy} is excluded from config descriptions, because does not implement `config_description/0` method."
+ )
+
+ acc
+ end
+ end)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index bee47b4ed..655a2ced0 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -40,4 +40,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
_ -> Map.put(activity, "expires_at", expires_at)
end
end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_activity_expiration,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
+ label: "MRF Activity Expiration Policy",
+ description: "Adds automatic expiration to all local activities",
+ children: [
+ %{
+ key: :days,
+ type: :integer,
+ description: "Default global expiration time for all local activities (in days)",
+ suggestions: [90, 365]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 9ba07b4e3..3fd5c1e0a 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -97,4 +97,31 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@impl true
def describe,
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_hellthread,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
+ label: "MRF Hellthread",
+ description: "Block messages with excessive user mentions",
+ children: [
+ %{
+ key: :delist_threshold,
+ type: :integer,
+ description:
+ "Number of mentioned users after which the message gets removed from timelines and" <>
+ "disables notifications. Set to 0 to disable.",
+ suggestions: [10]
+ },
+ %{
+ key: :reject_threshold,
+ type: :integer,
+ description:
+ "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
+ suggestions: [20]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index db66cfa3e..ded0fe7f2 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -126,4 +126,46 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, %{mrf_keyword: mrf_keyword}}
end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_keyword,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
+ label: "MRF Keyword",
+ description:
+ "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
+ children: [
+ %{
+ key: :reject,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in message being rejected.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/iu]
+ },
+ %{
+ key: :federated_timeline_removal,
+ type: {:list, :string},
+ description: """
+ A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/iu]
+ },
+ %{
+ key: :replace,
+ type: {:list, :tuple},
+ description: """
+ **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+
+ **Replacement**: a string. Leaving the field empty is permitted.
+ """
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 7910ca131..9c096712a 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -25,4 +25,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@impl true
def describe, do: {:ok, %{}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_mention,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
+ label: "MRF Mention",
+ description: "Block messages which mention a specific user",
+ children: [
+ %{
+ key: :actors,
+ type: {:list, :string},
+ description: "A list of actors for which any post mentioning them will be dropped",
+ suggestions: ["actor1", "actor2"]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index 7abae37ae..e00575c2a 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@behaviour Pleroma.Web.ActivityPub.MRF
+ @impl true
def filter(%{"type" => "Create", "object" => child_object} = object) do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
@@ -22,5 +23,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
def filter(object), do: {:ok, object}
+ @impl true
def describe, do: {:ok, %{}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_normalize_markup,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
+ label: "MRF Normalize Markup",
+ description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
+ children: [
+ %{
+ key: :scrub_policy,
+ type: :module,
+ suggestions: [Pleroma.HTML.Scrubber.Default]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index d45d2d7e3..eb0481f20 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -106,4 +106,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, %{mrf_object_age: mrf_object_age}}
end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_object_age,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
+ label: "MRF Object Age",
+ description:
+ "Rejects or delists posts based on their timestamp deviance from your server's clock.",
+ children: [
+ %{
+ key: :threshold,
+ type: :integer,
+ description: "Required age (in seconds) of a post before actions are taken.",
+ suggestions: [172_800]
+ },
+ %{
+ key: :actions,
+ type: {:list, :atom},
+ description:
+ "A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
+ "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
+ "`:reject` rejects the message entirely",
+ suggestions: [:delist, :strip_followers, :reject]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index 0b9ed2224..cd7665e31 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -48,4 +48,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def describe,
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_rejectnonpublic,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
+ description: "RejectNonPublic drops posts with non-public visibility settings.",
+ label: "MRF Reject Non Public",
+ children: [
+ %{
+ key: :allow_followersonly,
+ label: "Allow followers-only",
+ type: :boolean,
+ description: "Whether to allow followers-only posts"
+ },
+ %{
+ key: :allow_direct,
+ type: :boolean,
+ description: "Whether to allow direct messages"
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 161177727..6cd91826d 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -244,4 +244,78 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, %{mrf_simple: mrf_simple}}
end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_simple,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
+ label: "MRF Simple",
+ description: "Simple ingress policies",
+ children: [
+ %{
+ key: :media_removal,
+ type: {:list, :string},
+ description: "List of instances to strip media attachments from",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :media_nsfw,
+ label: "Media NSFW",
+ type: {:list, :string},
+ description: "List of instances to tag all media as NSFW (sensitive) from",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :federated_timeline_removal,
+ type: {:list, :string},
+ description:
+ "List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :reject,
+ type: {:list, :string},
+ description: "List of instances to reject activities from (except deletes)",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :accept,
+ type: {:list, :string},
+ description: "List of instances to only accept activities from (except deletes)",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :followers_only,
+ type: {:list, :string},
+ description: "Force posts from the given instances to be visible by followers only",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :report_removal,
+ type: {:list, :string},
+ description: "List of instances to reject reports from",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :avatar_removal,
+ type: {:list, :string},
+ description: "List of instances to strip avatars from",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :banner_removal,
+ type: {:list, :string},
+ description: "List of instances to strip banners from",
+ suggestions: ["example.com", "*.example.com"]
+ },
+ %{
+ key: :reject_deletes,
+ type: {:list, :string},
+ description: "List of instances to reject deletions from",
+ suggestions: ["example.com", "*.example.com"]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
index 048052da6..2ec45260a 100644
--- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -39,4 +39,28 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@impl true
def describe, do: {:ok, %{}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_subchain,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
+ label: "MRF Subchain",
+ description:
+ "This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
+ " All criteria are configured as a map of regular expressions to lists of policy modules.",
+ children: [
+ %{
+ key: :match_actor,
+ type: {:map, {:list, :string}},
+ description: "Matches a series of regular expressions against the actor field",
+ suggestions: [
+ %{
+ ~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
+ }
+ ]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index 1a28f2ba2..e9d0d0503 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -41,4 +41,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end
+
+ # TODO: change way of getting settings on `lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex:18` to use `hosts` subkey
+ # @impl true
+ # def config_description do
+ # %{
+ # key: :mrf_user_allowlist,
+ # related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
+ # description: "Accept-list of users from specified instances",
+ # children: [
+ # %{
+ # key: :hosts,
+ # type: :map,
+ # description:
+ # "The keys in this section are the domain names that the policy should apply to." <>
+ # " Each key should be assigned a list of users that should be allowed " <>
+ # "through by their ActivityPub ID",
+ # suggestions: [%{"example.org" => ["https://example.org/users/admin"]}]
+ # }
+ # ]
+ # }
+ # end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
index a6c545570..f325cb680 100644
--- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF
+ @impl true
def filter(%{"type" => "Undo", "object" => child_message} = message) do
with {:ok, _} <- filter(child_message) do
{:ok, message}
@@ -36,6 +37,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
def filter(message), do: {:ok, message}
+ @impl true
def describe,
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_vocabulary,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
+ label: "MRF Vocabulary",
+ description: "Filter messages which belong to certain activity vocabularies",
+ children: [
+ %{
+ key: :accept,
+ type: {:list, :string},
+ description:
+ "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
+ suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
+ },
+ %{
+ key: :reject,
+ type: {:list, :string},
+ description:
+ "A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
+ suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
+ }
+ ]
+ }
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index df102a134..f96fd54bf 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
field(:type, :string)
field(:mediaType, :string, default: "application/octet-stream")
field(:name, :string)
+ field(:blurhash, :string)
embeds_many :url, UrlObjectValidator, primary_key: false do
field(:type, :string)
@@ -41,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|> fix_url()
struct
- |> cast(data, [:type, :mediaType, :name])
+ |> cast(data, [:type, :mediaType, :name, :blurhash])
|> cast_embed(:url, with: &url_changeset/2)
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
|> validate_required([:type, :mediaType, :url])
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 9c3956683..a2930c1cd 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -242,9 +242,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end)
end
- @doc """
- Publishes an activity to all relevant peers.
- """
+ # Publishes an activity to all relevant peers.
def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity)
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 2eec0ce86..bbff35c36 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
- if followed.local && !followed.locked do
+ if followed.local && !followed.is_locked do
{:ok, accept_data, _} = Builder.accept(followed, object)
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
end
@@ -187,7 +187,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
- if in_reply_to = object.data["inReplyTo"] do
+ if in_reply_to = object.data["inReplyTo"] && object.data["type"] != "Answer" do
Object.increase_replies_count(in_reply_to)
end
@@ -306,11 +306,18 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
streamables =
[[actor, recipient], [recipient, actor]]
+ |> Enum.uniq()
|> Enum.map(fn [user, other_user] ->
if user.local do
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
{:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
+ Cachex.put(
+ :chat_message_id_idempotency_key_cache,
+ cm_ref.id,
+ meta[:idempotency_key]
+ )
+
{
["user", "user:pleroma_chat"],
{user, %{cm_ref | chat: chat, object: object}}
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index d7dd9fe6b..0bcd1db22 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -40,6 +40,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_in_reply_to(options)
|> fix_emoji
|> fix_tag
+ |> set_sensitive
|> fix_content_map
|> fix_addressing
|> fix_summary
@@ -251,6 +252,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
}
|> Maps.put_if_present("mediaType", media_type)
|> Maps.put_if_present("name", data["name"])
+ |> Maps.put_if_present("blurhash", data["blurhash"])
else
nil
end
@@ -313,19 +315,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
tags =
tag
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
+ |> Enum.map(fn %{"name" => name} ->
+ name
+ |> String.slice(1..-1)
+ |> String.downcase()
+ end)
Map.put(object, "tag", tag ++ tags)
end
- def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
- combined = [tag, String.slice(hashtag, 1..-1)]
-
- Map.put(object, "tag", combined)
+ def fix_tag(%{"tag" => %{} = tag} = object) do
+ object
+ |> Map.put("tag", [tag])
+ |> fix_tag
end
- def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
-
def fix_tag(object), do: object
# content map usually only has one language so this will do for now.
@@ -927,7 +931,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "conversation", object["context"])
end
- def set_sensitive(%{"sensitive" => true} = object) do
+ def set_sensitive(%{"sensitive" => _} = object) do
object
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 3a4564912..4dc45cde3 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -101,7 +101,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
- "manuallyApprovesFollowers" => user.locked,
+ "manuallyApprovesFollowers" => user.is_locked,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
@@ -110,7 +110,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
- "discoverable" => user.discoverable,
+ "discoverable" => user.is_discoverable,
"capabilities" => capabilities
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 5c349bb7a..76bd54a42 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -44,29 +44,30 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
def is_list?(%{data: %{"listMessage" => _}}), do: true
def is_list?(_), do: false
- @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
- def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+ @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean()
+ def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true
def visible_for_user?(nil, _), do: false
- def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+ def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false
- def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+ def visible_for_user?(
+ %Activity{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?(%{local: local} = activity, nil) do
- cfg_key = if local, do: :local, else: :remote
-
- if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
+ def visible_for_user?(%Activity{} = activity, nil) do
+ if restrict_unauthenticated_access?(activity),
do: false,
else: is_public?(activity)
end
- def visible_for_user?(activity, user) do
+ def visible_for_user?(%Activity{} = activity, user) do
x = [user.ap_id | User.following(user)]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
is_public?(activity) || Enum.any?(x, &(&1 in y))
@@ -82,6 +83,26 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
result
end
+ def restrict_unauthenticated_access?(%Activity{local: local}) do
+ restrict_unauthenticated_access_to_activity?(local)
+ end
+
+ def restrict_unauthenticated_access?(%Object{} = object) do
+ object
+ |> Object.local?()
+ |> restrict_unauthenticated_access_to_activity?()
+ end
+
+ def restrict_unauthenticated_access?(%User{} = user) do
+ User.visible_for(user, _reading_user = nil)
+ end
+
+ defp restrict_unauthenticated_access_to_activity?(local?) when is_boolean(local?) do
+ cfg_key = if local?, do: :local, else: :remote
+
+ Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key)
+ end
+
def get_visibility(object) do
to = object.data["to"] || []
cc = object.data["cc"] || []
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index c2bd441ee..75525104f 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -5,7 +5,8 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+ import Pleroma.Web.ControllerHelper,
+ only: [json_response: 3, fetch_integer_param: 3]
alias Pleroma.Config
alias Pleroma.MFA
@@ -13,12 +14,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Builder
- alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.ModerationLogView
- alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
@@ -28,7 +26,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"], admin: true}
- when action in [:list_users, :user_show, :right_get, :show_user_credentials]
+ when action in [:right_get, :show_user_credentials, :create_backup]
)
plug(
@@ -37,12 +35,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
when action in [
:get_password_reset,
:force_password_reset,
- :user_delete,
- :users_create,
- :user_toggle_activation,
- :user_activate,
- :user_deactivate,
- :user_approve,
:tag_users,
:untag_users,
:right_add,
@@ -56,12 +48,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
- %{scopes: ["write:follows"], admin: true}
- when action in [:user_follow, :user_unfollow]
- )
-
- plug(
- OAuthScopesPlug,
%{scopes: ["read:statuses"], admin: true}
when action in [:list_user_statuses, :list_instance_statuses]
)
@@ -95,132 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(AdminAPI.FallbackController)
- def user_delete(conn, %{"nickname" => nickname}) do
- user_delete(conn, %{"nicknames" => [nickname]})
- end
-
- def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users =
- nicknames
- |> Enum.map(&User.get_cached_by_nickname/1)
-
- users
- |> Enum.each(fn user ->
- {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
- Pipeline.common_pipeline(delete_data, local: true)
- end)
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "delete"
- })
-
- json(conn, nicknames)
- end
-
- def user_follow(%{assigns: %{user: admin}} = conn, %{
- "follower" => follower_nick,
- "followed" => followed_nick
- }) do
- with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
- %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
- User.follow(follower, followed)
-
- ModerationLog.insert_log(%{
- actor: admin,
- followed: followed,
- follower: follower,
- action: "follow"
- })
- end
-
- json(conn, "ok")
- end
-
- def user_unfollow(%{assigns: %{user: admin}} = conn, %{
- "follower" => follower_nick,
- "followed" => followed_nick
- }) do
- with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
- %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
- User.unfollow(follower, followed)
-
- ModerationLog.insert_log(%{
- actor: admin,
- followed: followed,
- follower: follower,
- action: "unfollow"
- })
- end
-
- json(conn, "ok")
- end
-
- def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
- changesets =
- Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
- user_data = %{
- nickname: nickname,
- name: nickname,
- email: email,
- password: password,
- password_confirmation: password,
- bio: "."
- }
-
- User.register_changeset(%User{}, user_data, need_confirmation: false)
- end)
- |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
- Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
- end)
-
- case Pleroma.Repo.transaction(changesets) do
- {:ok, users} ->
- res =
- users
- |> Map.values()
- |> Enum.map(fn user ->
- {:ok, user} = User.post_register_action(user)
-
- user
- end)
- |> Enum.map(&AccountView.render("created.json", %{user: &1}))
-
- ModerationLog.insert_log(%{
- actor: admin,
- subjects: Map.values(users),
- action: "create"
- })
-
- json(conn, res)
-
- {:error, id, changeset, _} ->
- res =
- Enum.map(changesets.operations, fn
- {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
- AccountView.render("create-error.json", %{changeset: changeset})
-
- {_, {:changeset, current_changeset, _}} ->
- AccountView.render("create-error.json", %{changeset: current_changeset})
- end)
-
- conn
- |> put_status(:conflict)
- |> json(res)
- end
- end
-
- def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
- conn
- |> put_view(AccountView)
- |> render("show.json", %{user: user})
- else
- _ -> {:error, :not_found}
- end
- end
-
def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
@@ -274,69 +134,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
- def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
- user = User.get_cached_by_nickname(nickname)
-
- {:ok, updated_user} = User.deactivate(user, !user.deactivated)
-
- action = if user.deactivated, do: "activate", else: "deactivate"
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: [user],
- action: action
- })
-
- conn
- |> put_view(AccountView)
- |> render("show.json", %{user: updated_user})
- end
-
- def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- {:ok, updated_users} = User.deactivate(users, false)
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "activate"
- })
-
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: Keyword.values(updated_users)})
- end
-
- def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- {:ok, updated_users} = User.deactivate(users, true)
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "deactivate"
- })
-
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: Keyword.values(updated_users)})
- end
-
- def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- {:ok, updated_users} = User.approve(users)
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "approve"
- })
-
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: updated_users})
- end
-
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
@@ -363,43 +160,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
- def list_users(conn, params) do
- {page, page_size} = page_params(params)
- filters = maybe_parse_filters(params["filters"])
-
- search_params = %{
- query: params["query"],
- page: page,
- page_size: page_size,
- tags: params["tags"],
- name: params["name"],
- email: params["email"]
- }
-
- with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
- json(
- conn,
- AccountView.render("index.json",
- users: users,
- count: count,
- page_size: page_size
- )
- )
- end
- end
-
- @filters ~w(local external active deactivated need_approval is_admin is_moderator)
-
- @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
- defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
-
- defp maybe_parse_filters(filters) do
- filters
- |> String.split(",")
- |> Enum.filter(&Enum.member?(@filters, &1))
- |> Map.new(&{String.to_existing_atom(&1), true})
- end
-
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nicknames" => nicknames
@@ -681,25 +441,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
json(conn, %{"status_visibility" => counters})
end
- defp page_params(params) do
- {get_page(params["page"]), get_page_size(params["page_size"])}
- end
+ def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_by_nickname(nickname),
+ {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
+ ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
- defp get_page(page_string) when is_nil(page_string), do: 1
-
- defp get_page(page_string) do
- case Integer.parse(page_string) do
- {page, _} -> page
- :error -> 1
+ json(conn, "")
end
end
- defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
-
- defp get_page_size(page_size_string) do
- case Integer.parse(page_size_string) do
- {page_size, _} -> page_size
- :error -> @users_page_size
- end
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, "page", 1),
+ fetch_integer_param(params, "page_size", @users_page_size)
+ }
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
index 86da93893..6a0e56f5f 100644
--- a/lib/pleroma/web/admin_api/controllers/report_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
end
def show(conn, %{id: id}) do
- with %Activity{} = report <- Activity.get_by_id(id) do
+ with %Activity{} = report <- Activity.get_report(id) do
render(conn, "show.json", Report.extract_report_info(report))
else
_ -> {:error, :not_found}
diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
new file mode 100644
index 000000000..a2a1c875d
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -0,0 +1,281 @@
+# 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.UserController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [fetch_integer_param: 3]
+
+ alias Pleroma.ModerationLog
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.Pipeline
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.AdminAPI.Search
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ @users_page_size 50
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"], admin: true}
+ when action in [:list, :show]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"], admin: true}
+ when action in [
+ :delete,
+ :create,
+ :toggle_activation,
+ :activate,
+ :deactivate,
+ :approve
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:follows"], admin: true}
+ when action in [:follow, :unfollow]
+ )
+
+ action_fallback(AdminAPI.FallbackController)
+
+ def delete(conn, %{"nickname" => nickname}) do
+ delete(conn, %{"nicknames" => [nickname]})
+ end
+
+ def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+
+ Enum.each(users, fn user ->
+ {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
+ Pipeline.common_pipeline(delete_data, local: true)
+ end)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "delete"
+ })
+
+ json(conn, nicknames)
+ end
+
+ def follow(%{assigns: %{user: admin}} = conn, %{
+ "follower" => follower_nick,
+ "followed" => followed_nick
+ }) do
+ with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
+ %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
+ User.follow(follower, followed)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ followed: followed,
+ follower: follower,
+ action: "follow"
+ })
+ end
+
+ json(conn, "ok")
+ end
+
+ def unfollow(%{assigns: %{user: admin}} = conn, %{
+ "follower" => follower_nick,
+ "followed" => followed_nick
+ }) do
+ with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
+ %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
+ User.unfollow(follower, followed)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ followed: followed,
+ follower: follower,
+ action: "unfollow"
+ })
+ end
+
+ json(conn, "ok")
+ end
+
+ def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
+ changesets =
+ Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
+ user_data = %{
+ nickname: nickname,
+ name: nickname,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ bio: "."
+ }
+
+ User.register_changeset(%User{}, user_data, need_confirmation: false)
+ end)
+ |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
+ Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
+ end)
+
+ case Pleroma.Repo.transaction(changesets) do
+ {:ok, users} ->
+ res =
+ users
+ |> Map.values()
+ |> Enum.map(fn user ->
+ {:ok, user} = User.post_register_action(user)
+
+ user
+ end)
+ |> Enum.map(&AccountView.render("created.json", %{user: &1}))
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subjects: Map.values(users),
+ action: "create"
+ })
+
+ json(conn, res)
+
+ {:error, id, changeset, _} ->
+ res =
+ Enum.map(changesets.operations, fn
+ {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
+ AccountView.render("create-error.json", %{changeset: changeset})
+
+ {_, {:changeset, current_changeset, _}} ->
+ AccountView.render("create-error.json", %{changeset: current_changeset})
+ end)
+
+ conn
+ |> put_status(:conflict)
+ |> json(res)
+ end
+ end
+
+ def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ conn
+ |> put_view(AccountView)
+ |> render("show.json", %{user: user})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ user = User.get_cached_by_nickname(nickname)
+
+ {:ok, updated_user} = User.deactivate(user, !user.deactivated)
+
+ action = if user.deactivated, do: "activate", else: "deactivate"
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: [user],
+ action: action
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("show.json", %{user: updated_user})
+ end
+
+ def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "activate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
+ def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "deactivate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
+ def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.approve(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "approve"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: updated_users})
+ end
+
+ def list(conn, params) do
+ {page, page_size} = page_params(params)
+ filters = maybe_parse_filters(params["filters"])
+
+ search_params =
+ %{
+ query: params["query"],
+ page: page,
+ page_size: page_size,
+ tags: params["tags"],
+ name: params["name"],
+ email: params["email"],
+ actor_types: params["actor_types"]
+ }
+ |> Map.merge(filters)
+
+ with {:ok, users, count} <- Search.user(search_params) do
+ json(
+ conn,
+ AccountView.render("index.json",
+ users: users,
+ count: count,
+ page_size: page_size
+ )
+ )
+ end
+ end
+
+ @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
+
+ @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
+ defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
+
+ defp maybe_parse_filters(filters) do
+ filters
+ |> String.split(",")
+ |> Enum.filter(&Enum.member?(@filters, &1))
+ |> Map.new(&{String.to_existing_atom(&1), true})
+ end
+
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, "page", 1),
+ fetch_integer_param(params, "page_size", @users_page_size)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 9c477feab..8bac24d3e 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
:fields,
:name,
:nickname,
- :locked,
+ :is_locked,
:no_rich_text,
:default_scope,
:hide_follows,
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
:skip_thread_containment,
:pleroma_settings_store,
:raw_fields,
- :discoverable,
+ :is_discoverable,
:actor_type
])
|> Map.merge(%{
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index 773f798fe..535556370 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
end
def render("index_notes.json", %{notes: notes}) when is_list(notes) do
- Enum.map(notes, &render(__MODULE__, "show_note.json", &1))
+ Enum.map(notes, &render(__MODULE__, "show_note.json", Map.from_struct(&1)))
end
def render("index_notes.json", _), do: []
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index d90ddb787..451aa2477 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -262,6 +262,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
:query,
%Schema{allOf: [BooleanLike], default: true},
"Mute notifications in addition to statuses? Defaults to `true`."
+ ),
+ Operation.parameter(
+ :expires_in,
+ :query,
+ %Schema{type: :integer, default: 0},
+ "Expire the mute in `expires_in` seconds. Default 0 for infinity"
)
],
responses: %{
@@ -335,6 +341,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
operationId: "AccountController.mutes",
description: "Accounts the user has muted.",
security: [%{"oAuth" => ["follow", "read:mutes"]}],
+ parameters: pagination_params(),
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
@@ -348,6 +355,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
operationId: "AccountController.blocks",
description: "View your blocks. See also accounts/:id/{block,unblock}",
security: [%{"oAuth" => ["read:blocks"]}],
+ parameters: pagination_params(),
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
@@ -721,10 +729,17 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
nullable: true,
description: "Mute notifications in addition to statuses? Defaults to true.",
default: true
+ },
+ expires_in: %Schema{
+ type: :integer,
+ nullable: true,
+ description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
+ default: 0
}
},
example: %{
- "notifications" => true
+ "notifications" => true,
+ "expires_in" => 86_400
}
}
end
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
index 0dcfdb354..560b81f17 100644
--- a/lib/pleroma/web/api_spec/operations/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
+ alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.Chat
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
@@ -132,7 +133,10 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
tags: ["chat"],
summary: "Get a list of chats that you participated in",
operationId: "ChatController.index",
- parameters: pagination_params(),
+ parameters: [
+ Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
+ | pagination_params()
+ ],
responses: %{
200 => Operation.response("The chats of the user", "application/json", chats_response())
},
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
index f09be64cb..264a530d2 100644
--- a/lib/pleroma/web/api_spec/operations/notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -193,6 +193,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"mention",
"pleroma:emoji_reaction",
"pleroma:chat_mention",
+ "pleroma:report",
"move",
"follow_request"
],
@@ -206,6 +207,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
- `poll` - A poll you have voted in or created has ended
- `move` - Someone moved their account
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
+ - `pleroma:chat_mention` - Someone mentioned you in a chat message
+ - `pleroma:report` - Someone was reported
"""
}
end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex
new file mode 100644
index 000000000..6993794db
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex
@@ -0,0 +1,79 @@
+# 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.PleromaBackupOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Backups"],
+ summary: "List backups",
+ security: [%{"oAuth" => ["read:account"]}],
+ operationId: "PleromaAPI.BackupController.index",
+ responses: %{
+ 200 =>
+ Operation.response(
+ "An array of backups",
+ "application/json",
+ %Schema{
+ type: :array,
+ items: backup()
+ }
+ ),
+ 400 => Operation.response("Bad Request", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["Backups"],
+ summary: "Create a backup",
+ security: [%{"oAuth" => ["read:account"]}],
+ operationId: "PleromaAPI.BackupController.create",
+ responses: %{
+ 200 =>
+ Operation.response(
+ "An array of backups",
+ "application/json",
+ %Schema{
+ type: :array,
+ items: backup()
+ }
+ ),
+ 400 => Operation.response("Bad Request", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp backup do
+ %Schema{
+ title: "Backup",
+ description: "Response schema for a backup",
+ type: :object,
+ properties: %{
+ inserted_at: %Schema{type: :string, format: :"date-time"},
+ content_type: %Schema{type: :string},
+ file_name: %Schema{type: :string},
+ file_size: %Schema{type: :integer},
+ processed: %Schema{type: :boolean}
+ },
+ example: %{
+ "content_type" => "application/zip",
+ "file_name" =>
+ "https://cofe.fe:4000/media/backups/archive-foobar-20200908T164207-Yr7vuT5Wycv-sN3kSN2iJ0k-9pMo60j9qmvRCdDqIew.zip",
+ "file_size" => 4105,
+ "inserted_at" => "2020-09-08T16:42:07.000Z",
+ "processed" => true
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
new file mode 100644
index 000000000..2c455b0df
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
@@ -0,0 +1,40 @@
+# 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.PleromaInstancesOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["PleromaInstances"],
+ summary: "Instances federation status",
+ description: "Information about instances deemed unreachable by the server",
+ operationId: "PleromaInstances.show",
+ responses: %{
+ 200 => Operation.response("PleromaInstances", "application/json", pleroma_instances())
+ }
+ }
+ end
+
+ def pleroma_instances do
+ %Schema{
+ type: :object,
+ properties: %{
+ unreachable: %Schema{
+ type: :object,
+ properties: %{hostname: %Schema{type: :string, format: :"date-time"}}
+ }
+ },
+ example: %{
+ "unreachable" => %{"consistently-unreachable.name" => "2020-10-14 22:07:58.216473"}
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index d7ebde6f6..b3b6ceb68 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -223,7 +223,27 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
security: [%{"oAuth" => ["write:mutes"]}],
description: "Do not receive notifications for the thread that this status is part of.",
operationId: "StatusController.mute_conversation",
- parameters: [id_param()],
+ requestBody:
+ request_body("Parameters", %Schema{
+ type: :object,
+ properties: %{
+ expires_in: %Schema{
+ type: :integer,
+ nullable: true,
+ description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
+ default: 0
+ }
+ }
+ }),
+ parameters: [
+ id_param(),
+ Operation.parameter(
+ :expires_in,
+ :query,
+ %Schema{type: :integer, default: 0},
+ "Expire the mute in `expires_in` seconds. Default 0 for infinity"
+ )
+ ],
responses: %{
200 => status_response(),
400 => Operation.response("Error", "application/json", ApiError)
diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
index 8e19bace7..95720df9f 100644
--- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
@@ -59,6 +59,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
local_param(),
+ instance_param(),
only_media_param(),
with_muted_param(),
exclude_visibilities_param(),
@@ -158,8 +159,17 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
)
end
+ defp instance_param do
+ Operation.parameter(
+ :instance,
+ :query,
+ %Schema{type: :string},
+ "Show only statuses from the given domain"
+ )
+ end
+
defp with_muted_param do
- Operation.parameter(:with_muted, :query, BooleanLike, "Includeactivities by muted users")
+ Operation.parameter(:with_muted, :query, BooleanLike, "Include activities by muted users")
end
defp exclude_visibilities_param do
diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex
index b4986b734..65f908e33 100644
--- a/lib/pleroma/web/api_spec/schemas/chat.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat.ex
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
"fields" => []
},
"statuses_count" => 1,
- "locked" => false,
+ "is_locked" => false,
"created_at" => "2020-04-16T13:40:15.000Z",
"display_name" => "lain",
"fields" => [],
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
index c62096db0..0dfa60b97 100644
--- a/lib/pleroma/web/api_spec/schemas/poll.ex
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -28,8 +28,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
},
votes_count: %Schema{
type: :integer,
- nullable: true,
- description: "How many votes have been received. Number, or null if `multiple` is false."
+ description: "How many votes have been received. Number."
+ },
+ voters_count: %Schema{
+ type: :integer,
+ description: "How many unique accounts have voted. Number."
},
voted: %Schema{
type: :boolean,
@@ -61,7 +64,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
expired: true,
multiple: false,
votes_count: 10,
- voters_count: nil,
+ voters_count: 10,
voted: true,
own_votes: [
1
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 947e42890..e6890df2d 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -252,7 +252,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"header" => "http://localhost:4001/images/banner.png",
"header_static" => "http://localhost:4001/images/banner.png",
"id" => "9toJCsKN7SmSf3aj5c",
- "locked" => false,
+ "is_locked" => false,
"note" => "Tester Number 6",
"pleroma" => %{
"background_image" => nil,
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 60a50b027..0ab1b115d 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -45,7 +45,8 @@ defmodule Pleroma.Web.CommonAPI do
{_, {:ok, %Activity{} = activity, _meta}} <-
{:common_pipeline,
Pipeline.common_pipeline(create_activity_data,
- local: true
+ local: true,
+ idempotency_key: opts[:idempotency_key]
)} do
{:ok, activity}
else
@@ -453,20 +454,46 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def add_mute(user, activity) do
+ def add_mute(user, activity, params \\ %{}) do
+ expires_in = Map.get(params, :expires_in, 0)
+
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
+ if expires_in > 0 do
+ Pleroma.Workers.MuteExpireWorker.enqueue(
+ "unmute_conversation",
+ %{"user_id" => user.id, "activity_id" => activity.id},
+ schedule_in: expires_in
+ )
+ end
+
{:ok, activity}
else
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
end
end
- def remove_mute(user, activity) do
+ def remove_mute(%User{} = user, %Activity{} = activity) do
ThreadMute.remove_mute(user.id, activity.data["context"])
{:ok, activity}
end
+ def remove_mute(user_id, activity_id) do
+ with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
+ {:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
+ remove_mute(user, activity)
+ else
+ {what, result} = error ->
+ Logger.warn(
+ "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
+ activity_id
+ }"
+ )
+
+ {:error, error}
+ end
+ end
+
def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
when is_binary(context) do
ThreadMute.exists?(user_id, context)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 21f4d43e9..3b71adf0e 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -274,7 +274,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def format_input(text, format, options \\ [])
@doc """
- Formatting text to plain text.
+ Formatting text to plain text, BBCode, HTML, or Markdown
"""
def format_input(text, "text/plain", options) do
text
@@ -285,9 +285,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end).()
end
- @doc """
- Formatting text as BBCode.
- """
def format_input(text, "text/bbcode", options) do
text
|> String.replace(~r/\r/, "")
@@ -297,18 +294,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.linkify(options)
end
- @doc """
- Formatting text to html.
- """
def format_input(text, "text/html", options) do
text
|> Formatter.html_escape("text/html")
|> Formatter.linkify(options)
end
- @doc """
- Formatting text to markdown.
- """
def format_input(text, "text/markdown", options) do
text
|> Formatter.mentions_escape(options)
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 56562c12f..f26542e88 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -7,8 +7,12 @@ defmodule Pleroma.Web.Endpoint do
require Pleroma.Constants
+ alias Pleroma.Config
+
socket("/socket", Pleroma.Web.UserSocket)
+ plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
+
plug(Pleroma.Web.Plugs.SetLocalePlug)
plug(CORSPlug)
plug(Pleroma.Web.Plugs.HTTPSecurityPlug)
@@ -86,19 +90,19 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.Parsers,
parsers: [
:urlencoded,
- {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
+ {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},
:json
],
pass: ["*/*"],
json_decoder: Jason,
- length: Pleroma.Config.get([:instance, :upload_limit]),
+ length: Config.get([:instance, :upload_limit]),
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
plug(Plug.MethodOverride)
plug(Plug.Head)
- secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
+ secure_cookies = Config.get([__MODULE__, :secure_cookie_flag])
cookie_name =
if secure_cookies,
@@ -106,7 +110,7 @@ defmodule Pleroma.Web.Endpoint do
else: "pleroma_key"
extra =
- Pleroma.Config.get([__MODULE__, :extra_cookie_attrs])
+ Config.get([__MODULE__, :extra_cookie_attrs])
|> Enum.join(";")
# The session will be stored in the cookie and signed,
@@ -116,7 +120,7 @@ defmodule Pleroma.Web.Endpoint do
Plug.Session,
store: :cookie,
key: cookie_name,
- signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
+ signing_salt: Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
http_only: true,
secure: secure_cookies,
extra: extra
@@ -136,8 +140,34 @@ defmodule Pleroma.Web.Endpoint do
use Prometheus.PlugExporter
end
+ defmodule MetricsExporterCaller do
+ @behaviour Plug
+
+ def init(opts), do: opts
+
+ def call(conn, opts) do
+ prometheus_config = Application.get_env(:prometheus, MetricsExporter, [])
+ ip_whitelist = List.wrap(prometheus_config[:ip_whitelist])
+
+ cond do
+ !prometheus_config[:enabled] ->
+ conn
+
+ ip_whitelist != [] and
+ !Enum.find(ip_whitelist, fn ip ->
+ Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip}
+ end) ->
+ conn
+
+ true ->
+ MetricsExporter.call(conn, opts)
+ end
+ end
+ end
+
plug(PipelineInstrumenter)
- plug(MetricsExporter)
+
+ plug(MetricsExporterCaller)
plug(Pleroma.Web.Router)
diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
index 6f759d559..1ac1319f8 100644
--- a/lib/pleroma/web/fallback/redirect_controller.ex
+++ b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -37,10 +37,11 @@ defmodule Pleroma.Web.Fallback.RedirectController do
tags = build_tags(conn, params)
preloads = preload_data(conn, params)
+ title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
response =
index_content
- |> String.replace("<!--server-generated-meta-->", tags <> preloads)
+ |> String.replace("<!--server-generated-meta-->", tags <> preloads <> title)
conn
|> put_resp_content_type("text/html")
@@ -54,10 +55,11 @@ defmodule Pleroma.Web.Fallback.RedirectController do
def redirector_with_preload(conn, params) do
{:ok, index_content} = File.read(index_file_path())
preloads = preload_data(conn, params)
+ title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
response =
index_content
- |> String.replace("<!--server-generated-meta-->", preloads)
+ |> String.replace("<!--server-generated-meta-->", preloads <> title)
conn
|> put_resp_content_type("text/html")
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index 1ae03e7e2..56c024617 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.Feed.FeedView do
def activity_content(_), do: ""
- def activity_context(activity), do: activity.data["context"]
+ def activity_context(activity), do: escape(activity.data["context"])
def attachment_href(attachment) do
attachment["url"]
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
index 93a8294b7..218cdbdf3 100644
--- a/lib/pleroma/web/feed/tag_controller.ex
+++ b/lib/pleroma/web/feed/tag_controller.ex
@@ -10,14 +10,14 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.Feed.FeedView
def feed(conn, params) do
- unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
+ if Config.get!([:instance, :public]) do
render_feed(conn, params)
else
render_error(conn, :not_found, "Not found")
end
end
- def render_feed(conn, %{"tag" => raw_tag} = params) do
+ defp render_feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
@@ -36,12 +36,13 @@ defmodule Pleroma.Web.Feed.TagController do
end
@spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
- defp parse_tag(raw_tag) when is_binary(raw_tag) do
- case Enum.reverse(String.split(raw_tag, ".")) do
- [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
- _ -> {"rss", raw_tag}
+ defp parse_tag(raw_tag) do
+ case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do
+ [format | tag] when format in ["rss", "atom"] ->
+ {format, Enum.join(tag, ".")}
+
+ _ ->
+ {"atom", raw_tag}
end
end
-
- defp parse_tag(raw_tag), do: {"rss", raw_tag}
end
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index 752983c3b..a5013d2c0 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
@@ -22,12 +23,7 @@ defmodule Pleroma.Web.Feed.UserController do
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
- with %{halted: false} = conn <-
- Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- ) do
- ActivityPubController.call(conn, :user)
- end
+ ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
@@ -36,25 +32,18 @@ defmodule Pleroma.Web.Feed.UserController do
end
end
- def feed(conn, params) do
- unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
- render_feed(conn, params)
- else
- errors(conn, {:error, :not_found})
- end
- end
-
- def render_feed(conn, %{"nickname" => nickname} = params) do
+ def feed(conn, %{"nickname" => nickname} = params) do
format = get_format(conn)
format =
- if format in ["rss", "atom"] do
+ if format in ["atom", "rss"] do
format
else
"atom"
end
- with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
activities =
%{
type: ["Create"],
@@ -69,7 +58,7 @@ defmodule Pleroma.Web.Feed.UserController do
|> render("user.#{format}",
user: user,
activities: activities,
- feed_config: Pleroma.Config.get([:feed])
+ feed_config: Config.get([:feed])
)
end
end
@@ -81,6 +70,8 @@ defmodule Pleroma.Web.Feed.UserController do
def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+ def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found})
+
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 4f9696d52..784fdc975 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -177,7 +177,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
user_params =
[
:no_rich_text,
- :locked,
:hide_followers_count,
:hide_follows_count,
:hide_followers,
@@ -186,7 +185,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:show_role,
:skip_thread_containment,
:allow_following_move,
- :discoverable,
:accepts_chat_messages
]
|> Enum.reduce(%{}, fn key, acc ->
@@ -210,6 +208,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
+ |> Maps.put_if_present(:is_locked, params[:locked])
+ |> Maps.put_if_present(:is_discoverable, params[:discoverable])
# What happens here:
#
@@ -394,7 +394,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "POST /api/v1/accounts/:id/mute"
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
- with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
+ with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
render(conn, "relationship.json", user: muter, target: muted)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -442,15 +442,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "GET /api/v1/mutes"
- def mutes(%{assigns: %{user: user}} = conn, _) do
- users = User.muted_users(user, _restrict_deactivated = true)
- render(conn, "index.json", users: users, for: user, as: :user)
+ def mutes(%{assigns: %{user: user}} = conn, params) do
+ users =
+ user
+ |> User.muted_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
+
+ conn
+ |> add_link_headers(users)
+ |> render("index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/blocks"
- def blocks(%{assigns: %{user: user}} = conn, _) do
- users = User.blocked_users(user, _restrict_deactivated = true)
- render(conn, "index.json", users: users, for: user, as: :user)
+ def blocks(%{assigns: %{user: user}} = conn, params) do
+ users =
+ user
+ |> User.blocked_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
+
+ conn
+ |> add_link_headers(users)
+ |> render("index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/endorsements"
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index 75b809aab..9cc3984d0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
redirect(conn, to: local_mastodon_root_path(conn))
end
- @doc "Local Mastodon FE login init action"
+ # Local Mastodon FE login init action
def login(conn, %{"code" => auth_token}) do
with {:ok, app} <- get_or_make_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
end
end
- @doc "Local Mastodon FE callback action"
+ # Local Mastodon FE callback action
def login(conn, _) do
with {:ok, app} <- get_or_make_app() do
path =
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
index 9586b14bc..161193134 100644
--- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 08d6c1c22..4d9be5240 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -127,9 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
@doc """
POST /api/v1/statuses
-
- Creates a scheduled status when `scheduled_at` param is present and it's far enough
"""
+ # Creates a scheduled status when `scheduled_at` param is present and it's far enough
def create(
%{
assigns: %{user: user},
@@ -160,11 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
end
- @doc """
- POST /api/v1/statuses
-
- Creates a regular status
- """
+ # Creates a regular status
def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
@@ -289,9 +284,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
@doc "POST /api/v1/statuses/:id/mute"
- def mute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
+ def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id(id),
- {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+ {:ok, activity} <- CommonAPI.add_mute(user, activity, params) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 7a5c80e01..ac96520a3 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -111,6 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:blocking_user, user)
|> Map.put(:muting_user, user)
|> Map.put(:reply_filtering_user, user)
+ |> Map.put(:instance, params[:instance])
|> ActivityPub.fetch_public_activities()
conn
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 121ba1693..3158d09ed 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -242,7 +242,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
username: username_from_nickname(user.nickname),
acct: user.nickname,
display_name: display_name,
- locked: user.locked,
+ locked: user.is_locked,
created_at: Utils.to_masto_date(user.inserted_at),
followers_count: followers_count,
following_count: following_count,
@@ -261,7 +261,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
sensitive: false,
fields: user.raw_fields,
pleroma: %{
- discoverable: user.discoverable,
+ discoverable: user.is_discoverable,
actor_type: user.actor_type
}
},
@@ -388,7 +388,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
data
|> Kernel.put_in(
[:pleroma, :unread_conversation_count],
- user.unread_conversation_count
+ Pleroma.Conversation.Participation.unread_count(user)
)
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index a91994915..82fcff062 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -33,8 +33,15 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
end
activity = Activity.get_by_id_with_object(last_activity_id)
- # Conversations return all users except the current user.
- users = Enum.reject(participation.recipients, &(&1.id == user.id))
+
+ # Conversations return all users except the current user,
+ # except when the current user is the only participant
+ users =
+ if length(participation.recipients) > 1 do
+ Enum.reject(participation.recipients, &(&1.id == user.id))
+ else
+ participation.recipients
+ end
%{
id: participation.id |> to_string(),
@@ -43,7 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
last_status:
render(StatusView, "show.json",
activity: activity,
- direct_conversation_id: participation.id
+ direct_conversation_id: participation.id,
+ for: user
)
}
end
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index c97e6d32f..5b06a6b51 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.UserRelationship
+ alias Pleroma.Web.AdminAPI.Report
+ alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView
@@ -118,11 +120,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
"pleroma:chat_mention" ->
put_chat_message(response, activity, reading_user, status_render_opts)
+ "pleroma:report" ->
+ put_report(response, activity)
+
type when type in ["follow", "follow_request"] ->
response
end
end
+ defp put_report(response, activity) do
+ report_render = ReportView.render("show.json", Report.extract_report_info(activity))
+
+ Map.put(response, :report, report_render)
+ end
+
defp put_emoji(response, activity) do
Map.put(response, :emoji, activity.data["content"])
end
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 1208dc9a0..4101f21d0 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
expired: expired,
multiple: multiple,
votes_count: votes_count,
- voters_count: (multiple || nil) && voters_count(object),
+ voters_count: voters_count(object),
options: options,
voted: voted?(params),
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 435bcde15..7cbbd3750 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -435,7 +435,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
text_url: href,
type: type,
description: attachment["name"],
- pleroma: %{mime_type: media_type}
+ pleroma: %{mime_type: media_type},
+ blurhash: attachment["blurhash"]
}
end
diff --git a/lib/pleroma/web/media_proxy/invalidation/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex
index bb81d8888..0b0cde68c 100644
--- a/lib/pleroma/web/media_proxy/invalidation/http.ex
+++ b/lib/pleroma/web/media_proxy/invalidation/http.ex
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
{:ok, %{status: status} = env} when 400 <= status and status < 500 ->
{:error, env}
- {:error, error} = error ->
+ {:error, _} = error ->
error
_ ->
diff --git a/lib/pleroma/web/metadata/providers/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
index a1dcb6e15..900c2434d 100644
--- a/lib/pleroma/web/metadata/providers/restrict_indexing.ex
+++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
"""
@impl true
- def build_tags(%{user: %{local: true, discoverable: true}}), do: []
+ def build_tags(%{user: %{local: true, is_discoverable: true}}), do: []
def build_tags(_) do
[
diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
index b044260b3..668ae0ea4 100644
--- a/lib/pleroma/web/o_status/o_status_controller.ex
+++ b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -16,10 +16,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.Router
- plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- )
-
plug(
RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@@ -37,14 +33,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :object)
end
- def object(%{assigns: %{format: format}} = conn, _params) do
+ def object(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -59,13 +53,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :activity)
end
- def activity(%{assigns: %{format: format}} = conn, _params) do
+ def activity(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -119,6 +111,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%Object{} = object <- Object.normalize(activity),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 61f4a9bd9..30cf83567 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -18,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants
plug(
+ Majic.Plug,
+ [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner]
+ )
+
+ plug(
OpenApiSpex.Plug.PutApiSpec,
[module: Pleroma.Web.ApiSpec] when action == :confirmation_resend
)
diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
new file mode 100644
index 000000000..dd0a2e22f
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.BackupController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.User.Backup
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
+ plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation
+
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ backups = Backup.list(user)
+ render(conn, "index.json", backups: backups)
+ end
+
+ def create(%{assigns: %{user: user}} = conn, _params) do
+ with {:ok, _} <- Backup.create(user) do
+ backups = Backup.list(user)
+ render(conn, "index.json", backups: backups)
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index 6357148d0..77564b342 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -15,7 +15,6 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
- alias Pleroma.Web.PleromaAPI.ChatView
alias Pleroma.Web.Plugs.OAuthScopesPlug
import Ecto.Query
@@ -80,7 +79,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
{:ok, activity} <-
CommonAPI.post_chat_message(user, recipient, params[:content],
- media_id: params[:media_id]
+ media_id: params[:media_id],
+ idempotency_key: idempotency_key(conn)
),
message <- Object.normalize(activity, false),
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
@@ -120,9 +120,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
) do
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
- conn
- |> put_view(ChatView)
- |> render("show.json", chat: chat)
+ render(conn, "show.json", chat: chat)
end
end
@@ -140,33 +138,37 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do
- blocked_ap_ids = User.blocked_users_ap_ids(user)
+ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
+ exclude_users =
+ User.blocked_users_ap_ids(user) ++
+ if params[:with_muted], do: [], else: User.muted_users_ap_ids(user)
chats =
- Chat.for_user_query(user_id)
- |> where([c], c.recipient not in ^blocked_ap_ids)
+ user_id
+ |> Chat.for_user_query()
+ |> where([c], c.recipient not in ^exclude_users)
|> Repo.all()
- conn
- |> put_view(ChatView)
- |> render("index.json", chats: chats)
+ render(conn, "index.json", chats: chats)
end
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
- conn
- |> put_view(ChatView)
- |> render("show.json", chat: chat)
+ render(conn, "show.json", chat: chat)
end
end
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
- conn
- |> put_view(ChatView)
- |> render("show.json", chat: chat)
+ render(conn, "show.json", chat: chat)
+ end
+ end
+
+ defp idempotency_key(conn) do
+ case get_req_header(conn, "idempotency-key") do
+ [key] -> key
+ _ -> nil
end
end
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
new file mode 100644
index 000000000..9e97480df
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.InstancesController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Instances
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaInstancesOperation
+
+ def show(conn, _params) do
+ unreachable =
+ Instances.get_consistently_unreachable()
+ |> Map.new(fn {host, date} -> {host, to_string(date)} end)
+
+ json(conn, %{"unreachable" => unreachable})
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
index 0f6f0b9db..15210f1e6 100644
--- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Plugs.OAuthScopesPlug
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show)
@@ -22,14 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
@doc "PUT /api/v1/pleroma/mascot"
def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do
- with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
- # Reject if not an image
- %{type: "image"} = attachment <- render_attachment(object) do
+ with {:content_type, "image" <> _} <- {:content_type, file.content_type},
+ {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do
+ attachment = render_attachment(object)
{:ok, _user} = User.mascot_update(user, attachment)
json(conn, attachment)
else
- %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
+ {:content_type, _} ->
+ render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end
diff --git a/lib/pleroma/web/pleroma_api/views/backup_view.ex b/lib/pleroma/web/pleroma_api/views/backup_view.ex
new file mode 100644
index 000000000..af75876aa
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/backup_view.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.BackupView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.User.Backup
+ alias Pleroma.Web.CommonAPI.Utils
+
+ def render("show.json", %{backup: %Backup{} = backup}) do
+ %{
+ content_type: backup.content_type,
+ url: download_url(backup),
+ file_size: backup.file_size,
+ processed: backup.processed,
+ inserted_at: Utils.to_masto_date(backup.inserted_at)
+ }
+ end
+
+ def render("index.json", %{backups: backups}) do
+ render_many(backups, __MODULE__, "show.json")
+ end
+
+ def download_url(%Backup{file_name: file_name}) do
+ Pleroma.Web.Endpoint.url() <> "/media/backups/" <> file_name
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
index d4e08b50d..c058fb340 100644
--- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
use Pleroma.Web, :view
+ alias Pleroma.Maps
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
@@ -37,6 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
)
}
+ |> put_idempotency_key()
end
def render("index.json", opts) do
@@ -47,4 +49,13 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
Map.put(opts, :as, :chat_message_reference)
)
end
+
+ defp put_idempotency_key(data) do
+ with {:ok, idempotency_key} <- Cachex.get(:chat_message_id_idempotency_key_cache, data.id) do
+ data
+ |> Maps.put_if_present(:idempotency_key, idempotency_key)
+ else
+ _ -> data
+ end
+ end
end
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
index ceb10dcf8..1b0b36813 100644
--- a/lib/pleroma/web/plugs/frontend_static.ex
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -34,22 +34,26 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
end
def call(conn, opts) do
- frontend_type = Map.get(opts, :frontend_type, :primary)
- path = file_path("", frontend_type)
-
- if path do
- conn
- |> call_static(opts, path)
+ with false <- invalid_path?(conn.path_info),
+ frontend_type <- Map.get(opts, :frontend_type, :primary),
+ path when not is_nil(path) <- file_path("", frontend_type) do
+ call_static(conn, opts, path)
else
- conn
+ _ ->
+ conn
end
end
- defp call_static(conn, opts, from) do
- opts =
- opts
- |> Map.put(:from, from)
+ defp invalid_path?(list) do
+ invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
+ end
+ defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
+ defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
+ defp invalid_path?([], _match), do: false
+
+ defp call_static(conn, opts, from) do
+ opts = Map.put(opts, :from, from)
Plug.Static.call(conn, opts)
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index d2d939989..0f0538182 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -5,6 +5,26 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
+ pipeline :accepts_html do
+ plug(:accepts, ["html"])
+ end
+
+ pipeline :accepts_html_xml do
+ plug(:accepts, ["html", "xml", "rss", "atom"])
+ end
+
+ pipeline :accepts_html_json do
+ plug(:accepts, ["html", "activity+json", "json"])
+ end
+
+ pipeline :accepts_html_xml_json do
+ plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
+ end
+
+ pipeline :accepts_xml_rss_atom do
+ plug(:accepts, ["xml", "rss", "atom"])
+ end
+
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
@@ -129,16 +149,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through(:admin_api)
- post("/users/follow", AdminAPIController, :user_follow)
- post("/users/unfollow", AdminAPIController, :user_unfollow)
-
put("/users/disable_mfa", AdminAPIController, :disable_mfa)
- delete("/users", AdminAPIController, :user_delete)
- post("/users", AdminAPIController, :users_create)
- patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
- patch("/users/activate", AdminAPIController, :user_activate)
- patch("/users/deactivate", AdminAPIController, :user_deactivate)
- patch("/users/approve", AdminAPIController, :user_approve)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
@@ -161,6 +172,15 @@ defmodule Pleroma.Web.Router do
:right_delete_multiple
)
+ post("/users/follow", UserController, :follow)
+ post("/users/unfollow", UserController, :unfollow)
+ delete("/users", UserController, :delete)
+ post("/users", UserController, :create)
+ patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
+ patch("/users/activate", UserController, :activate)
+ patch("/users/deactivate", UserController, :deactivate)
+ patch("/users/approve", UserController, :approve)
+
get("/relay", RelayController, :index)
post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow)
@@ -175,8 +195,8 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
- get("/users", AdminAPIController, :list_users)
- get("/users/:nickname", AdminAPIController, :user_show)
+ get("/users", UserController, :list)
+ get("/users/:nickname", UserController, :show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
@@ -223,6 +243,8 @@ defmodule Pleroma.Web.Router do
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
+
+ post("/backups", AdminAPIController, :create_backup)
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
@@ -353,6 +375,9 @@ defmodule Pleroma.Web.Router do
put("/mascot", MascotController, :update)
post("/scrobble", ScrobbleController, :create)
+
+ get("/backups", BackupController, :index)
+ post("/backups", BackupController, :create)
end
scope [] do
@@ -373,6 +398,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:api)
get("/accounts/:id/scrobbles", ScrobbleController, :index)
+ get("/federation_status", InstancesController, :show)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -566,30 +592,43 @@ defmodule Pleroma.Web.Router do
)
end
- pipeline :ostatus do
- plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
- plug(Pleroma.Web.Plugs.StaticFEPlug)
- end
-
- pipeline :oembed do
- plug(:accepts, ["json", "xml"])
- end
-
scope "/", Pleroma.Web do
- pipe_through([:ostatus, :http_signature])
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
- get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
# Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
+ end
- get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+
+ # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+ end
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+
+ get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ end
+
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_html)
+ get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
+ end
+
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_xml_rss_atom)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index 687b17df6..bdec0897a 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -17,74 +17,14 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id)
- plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- )
-
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
- defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
- do: name
-
- defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
- do: summary
-
- defp get_title(_), do: nil
-
- defp not_found(conn, message) do
- conn
- |> put_status(404)
- |> render("error.html", %{message: message, meta: ""})
- end
-
- defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
-
- %{
- likes: data["like_count"] || 0,
- replies: data["repliesCount"] || 0,
- announces: data["announcement_count"] || 0
- }
- end
-
- defp represent(%Activity{} = activity), do: represent(activity, false)
-
- defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
- {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
-
- link =
- case user.local do
- true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
- _ -> data["url"] || data["external_url"] || data["id"]
- end
-
- content =
- if data["content"] do
- data["content"]
- |> Pleroma.HTML.filter_tags()
- |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
- else
- nil
- end
-
- %{
- user: User.sanitize_html(user),
- title: get_title(activity.object),
- content: content,
- attachment: data["attachment"],
- link: link,
- published: data["published"],
- sensitive: data["sensitive"],
- selected: selected,
- counts: get_counts(activity),
- id: activity.id
- }
- end
-
+ @doc "Renders requested local public activity or public activities of requested user"
def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
@@ -107,34 +47,35 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
- case User.get_cached_by_nickname_or_id(username_or_id) do
- %User{} = user ->
- meta = Metadata.build_tags(%{user: user})
-
- params =
- params
- |> Map.take(@page_keys)
- |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
-
- timeline =
- user
- |> ActivityPub.fetch_user_activities(nil, params)
- |> Enum.map(&represent/1)
-
- prev_page_id =
- (params["min_id"] || params["max_id"]) &&
- List.first(timeline) && List.first(timeline).id
-
- next_page_id = List.last(timeline) && List.last(timeline).id
-
- render(conn, "profile.html", %{
- user: User.sanitize_html(user),
- timeline: timeline,
- prev_page_id: prev_page_id,
- next_page_id: next_page_id,
- meta: meta
- })
+ with {_, %User{local: true} = user} <-
+ {:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
+ meta = Metadata.build_tags(%{user: user})
+
+ params =
+ params
+ |> Map.take(@page_keys)
+ |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+ timeline =
+ user
+ |> ActivityPub.fetch_user_activities(_reading_user = nil, params)
+ |> Enum.map(&represent/1)
+
+ prev_page_id =
+ (params["min_id"] || params["max_id"]) &&
+ List.first(timeline) && List.first(timeline).id
+
+ next_page_id = List.last(timeline) && List.last(timeline).id
+
+ render(conn, "profile.html", %{
+ user: User.sanitize_html(user),
+ timeline: timeline,
+ prev_page_id: prev_page_id,
+ next_page_id: next_page_id,
+ meta: meta
+ })
+ else
_ ->
not_found(conn, "User not found.")
end
@@ -166,6 +107,64 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
+ defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
+ do: name
+
+ defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
+ do: summary
+
+ defp get_title(_), do: nil
+
+ defp not_found(conn, message) do
+ conn
+ |> put_status(404)
+ |> render("error.html", %{message: message, meta: ""})
+ end
+
+ defp get_counts(%Activity{} = activity) do
+ %Object{data: data} = Object.normalize(activity)
+
+ %{
+ likes: data["like_count"] || 0,
+ replies: data["repliesCount"] || 0,
+ announces: data["announcement_count"] || 0
+ }
+ end
+
+ defp represent(%Activity{} = activity), do: represent(activity, false)
+
+ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
+
+ link =
+ case user.local do
+ true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+ _ -> data["url"] || data["external_url"] || data["id"]
+ end
+
+ content =
+ if data["content"] do
+ data["content"]
+ |> Pleroma.HTML.filter_tags()
+ |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
+ else
+ nil
+ end
+
+ %{
+ user: User.sanitize_html(user),
+ title: get_title(activity.object),
+ content: content,
+ attachment: data["attachment"],
+ link: link,
+ published: data["published"],
+ sensitive: data["sensitive"],
+ selected: selected,
+ counts: get_counts(activity),
+ id: activity.id
+ }
+ end
+
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index d618dfe54..71fe27c89 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -57,6 +57,15 @@ defmodule Pleroma.Web.Streamer do
{:ok, "hashtag:" <> tag}
end
+ # Allow remote instance streams.
+ def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
+ {:ok, "public:remote:" <> instance}
+ end
+
+ def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
+ {:ok, "public:remote:media:" <> instance}
+ end
+
# Expand user streams.
def get_topic(
stream,
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
index 78350f2aa..3fd150c4e 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
@@ -12,7 +12,7 @@
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
- <summary><%= @data["summary"] %></summary>
+ <summary><%= escape(@data["summary"]) %></summary>
<% end %>
<%= if @activity.local do %>
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
index a304a16af..42960de7d 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
@@ -12,7 +12,7 @@
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
<%= if @data["summary"] do %>
- <description><%= @data["summary"] %></description>
+ <description><%= escape(@data["summary"]) %></description>
<% end %>
<%= if @activity.local do %>
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 51603fe0c..3f28f1920 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -228,7 +228,7 @@
<body>
<div class="container">
<h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</div>
</body>
</html>
diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex
index ca2caaf4d..82cabd889 100644
--- a/lib/pleroma/web/templates/layout/email_styled.html.eex
+++ b/lib/pleroma/web/templates/layout/email_styled.html.eex
@@ -181,7 +181,7 @@
</div>
</div>
<% end %>
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</td>
</tr>
diff --git a/lib/pleroma/web/templates/layout/metadata_player.html.eex b/lib/pleroma/web/templates/layout/metadata_player.html.eex
index 460f28094..c00f6fa21 100644
--- a/lib/pleroma/web/templates/layout/metadata_player.html.eex
+++ b/lib/pleroma/web/templates/layout/metadata_player.html.eex
@@ -10,7 +10,7 @@ video, audio {
}
</style>
-<%= render @view_module, @view_template, assigns %>
+<%= @inner_content %>
</body>
</html>
diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex
index dc0ee2a5c..e6adb526b 100644
--- a/lib/pleroma/web/templates/layout/static_fe.html.eex
+++ b/lib/pleroma/web/templates/layout/static_fe.html.eex
@@ -9,7 +9,7 @@
</head>
<body>
<div class="container">
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</div>
</body>
</html>
diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex
new file mode 100644
index 000000000..5b4985983
--- /dev/null
+++ b/lib/pleroma/workers/backup_worker.ex
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.BackupWorker do
+ use Oban.Worker, queue: :backup, max_attempts: 1
+
+ alias Oban.Job
+ alias Pleroma.User.Backup
+
+ def process(backup, admin_user_id \\ nil) do
+ %{"op" => "process", "backup_id" => backup.id, "admin_user_id" => admin_user_id}
+ |> new()
+ |> Oban.insert()
+ end
+
+ def schedule_deletion(backup) do
+ days = Pleroma.Config.get([Backup, :purge_after_days])
+ time = 60 * 60 * 24 * days
+ scheduled_at = Calendar.NaiveDateTime.add!(backup.inserted_at, time)
+
+ %{"op" => "delete", "backup_id" => backup.id}
+ |> new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+
+ def delete(backup) do
+ %{"op" => "delete", "backup_id" => backup.id}
+ |> new()
+ |> Oban.insert()
+ end
+
+ def perform(%Job{
+ args: %{"op" => "process", "backup_id" => backup_id, "admin_user_id" => admin_user_id}
+ }) do
+ with {:ok, %Backup{} = backup} <-
+ backup_id |> Backup.get() |> Backup.process(),
+ {:ok, _job} <- schedule_deletion(backup),
+ :ok <- Backup.remove_outdated(backup),
+ {:ok, _} <-
+ backup
+ |> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
+ |> Pleroma.Emails.Mailer.deliver() do
+ {:ok, backup}
+ end
+ end
+
+ def perform(%Job{args: %{"op" => "delete", "backup_id" => backup_id}}) do
+ case Backup.get(backup_id) do
+ %Backup{} = backup -> Backup.delete(backup)
+ nil -> :ok
+ end
+ end
+end
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
new file mode 100644
index 000000000..32a12ba85
--- /dev/null
+++ b/lib/pleroma/workers/mute_expire_worker.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.MuteExpireWorker do
+ use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
+
+ @impl Oban.Worker
+ def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
+ Pleroma.User.unmute(muter_id, mutee_id)
+ :ok
+ end
+
+ def perform(%Job{
+ args: %{"op" => "unmute_conversation", "user_id" => user_id, "activity_id" => activity_id}
+ }) do
+ Pleroma.Web.CommonAPI.remove_mute(user_id, activity_id)
+ :ok
+ end
+end