Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into alexgleason/pleroma-block-behavior

This commit is contained in:
lain 2020-11-04 16:18:11 +01:00
commit bae48c98e3
104 changed files with 3547 additions and 1805 deletions

View file

@ -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])

View file

@ -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

View file

@ -168,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
@ -178,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]))

View file

@ -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}

View file

@ -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} =

View file

@ -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,

View file

@ -189,4 +189,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

View file

@ -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()

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -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}")

View file

@ -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)
@ -426,7 +425,6 @@ defmodule Pleroma.User do
params,
[
:bio,
:name,
:emoji,
:ap_id,
:inbox,
@ -455,7 +453,9 @@ defmodule Pleroma.User do
: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)
@ -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
@ -774,6 +784,7 @@ defmodule Pleroma.User do
def post_register_action(%User{} = 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),
@ -1293,47 +1304,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)

258
lib/pleroma/user/backup.ex Normal file
View file

@ -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

View file

@ -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)

View file

@ -976,16 +976,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
@ -1418,6 +1413,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}

View file

@ -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
@ -312,6 +312,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects 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}}

View file

@ -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"] || []

View file

@ -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,
@ -54,12 +46,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}
@ -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
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"})
json(conn, "")
end
end
defp page_params(params) do
{get_page(params["page"]), get_page_size(params["page_size"])}
end
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
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
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @users_page_size)
}
end
end

View file

@ -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}

View file

@ -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

View file

@ -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(%{

View file

@ -335,6 +335,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 +349,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())
}

View file

@ -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())
},

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -7,6 +7,8 @@ defmodule Pleroma.Web.Endpoint do
require Pleroma.Constants
alias Pleroma.Config
socket("/socket", Pleroma.Web.UserSocket)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
@ -88,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,
@ -108,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,
@ -118,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
@ -138,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)

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"])

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
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)
get("/users/:nickname", Feed.UserController, :feed_redirect, 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

View file

@ -17,12 +17,96 @@ 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"]
@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})
timeline =
activity.object.data["context"]
|> ActivityPub.fetch_activities_for_context(%{})
|> Enum.reverse()
|> Enum.map(&represent(&1, &1.object.id == activity.object.id))
render(conn, "conversation.html", %{activities: timeline, meta: meta})
else
%Activity{object: %Object{data: data}} ->
conn
|> put_status(:found)
|> redirect(external: data["url"] || data["external_url"] || data["id"])
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
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
end
def show(%{assigns: %{object_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_create_by_object_ap_id_with_object(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{activity_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
do: name
@ -81,91 +165,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
}
end
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),
%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})
timeline =
activity.object.data["context"]
|> ActivityPub.fetch_activities_for_context(%{})
|> Enum.reverse()
|> Enum.map(&represent(&1, &1.object.id == activity.object.id))
render(conn, "conversation.html", %{activities: timeline, meta: meta})
else
%Activity{object: %Object{data: data}} ->
conn
|> put_status(:found)
|> redirect(external: data["url"] || data["external_url"] || data["id"])
_ ->
not_found(conn, "Post not found.")
end
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
})
_ ->
not_found(conn, "User not found.")
end
end
def show(%{assigns: %{object_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_create_by_object_ap_id_with_object(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{activity_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)

View file

@ -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,

View file

@ -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