-
- 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/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 @@ def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
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 @@ def handle(
%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 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) 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 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) 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..39c8f7e39 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -40,6 +40,7 @@ def fix_object(object, options \\ []) do
|> fix_in_reply_to(options)
|> fix_emoji
|> fix_tag
+ |> set_sensitive
|> fix_content_map
|> fix_addressing
|> fix_summary
@@ -313,19 +314,21 @@ def fix_tag(%{"tag" => tag} = object) when is_list(tag) 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 +930,7 @@ def set_conversation(object) 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 @@ def render("user.json", %{user: user}) 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 @@ def render("user.json", %{user: user}) 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 @@ def is_direct?(activity) 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 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) 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 bdd3e195d..5c2c282b3 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,
@@ -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 @@ def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}
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 @@ def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "
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 @@ def stats(conn, params) 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
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 @@ def index(conn, params) 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
+# 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 @@ def render("credentials.json", %{user: user, for: for_user}) do
:fields,
:name,
:nickname,
- :locked,
+ :is_locked,
:no_rich_text,
:default_scope,
:hide_follows,
@@ -52,7 +52,7 @@ def render("credentials.json", %{user: user, for: for_user}) 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 @@ def render("show.json", %{report: report, user: user, account: account, statuses
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..4934b7788 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -335,6 +335,7 @@ def mutes_operation 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 @@ def blocks_operation 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())
}
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 @@ def index_operation 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/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
+# 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/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/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..318ffc5d0 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -45,7 +45,8 @@ def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ [])
{_, {: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
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 @@ defp build_attachment_link(_), 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 @@ def format_input(text, "text/plain", options) 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 @@ def format_input(text, "text/bbcode", options) 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 MetricsExporter 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/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 @@ def render_feed(conn, %{"tag" => raw_tag} = params) 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 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname
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 @@ def feed_redirect(conn, %{"nickname" => nickname}) 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 @@ def render_feed(conn, %{"nickname" => nickname} = params) 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 @@ def errors(conn, {:error, :not_found}) 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..a2715cf28 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 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
user_params =
[
:no_rich_text,
- :locked,
:hide_followers_count,
:hide_follows_count,
:hide_followers,
@@ -186,7 +185,6 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
:show_role,
:skip_thread_containment,
:allow_following_move,
- :discoverable,
:accepts_chat_messages
]
|> Enum.reduce(%{}, fn key, acc ->
@@ -210,6 +208,8 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
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:
#
@@ -442,15 +442,27 @@ def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) 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 @@ def login(%{assigns: %{user: %User{}}} = conn, _params) 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 @@ def login(conn, %{"code" => auth_token}) 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..6848adace 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 @@ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) 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 @@ def create(
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])
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 @@ defp do_render("show.json", %{user: user} = opts) 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 @@ defp do_render("show.json", %{user: user} = opts) do
sensitive: false,
fields: user.raw_fields,
pleroma: %{
- discoverable: user.discoverable,
+ discoverable: user.is_discoverable,
actor_type: user.actor_type
}
},
@@ -388,7 +388,7 @@ defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{
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 @@ def render("participation.json", %{participation: participation, for: user}) 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 @@ def render("participation.json", %{participation: participation, for: user}) 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/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 @@ defp do_purge(method, url, headers, options) 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 @@ def object(%{assigns: %{format: format}} = conn, _params)
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 @@ def activity(%{assigns: %{format: format}} = conn, _params)
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 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) 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
@@ -17,6 +17,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
+# 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 @@ def post_chat_message(
%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 @@ def mark_as_read(
) 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 @@ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) 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/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 @@ def show(%{assigns: %{user: user}} = conn, _params) 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
+# 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 @@ def render(
Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
)
}
+ |> put_idempotency_key()
end
def render("index.json", opts) do
@@ -47,4 +49,13 @@ def render("index.json", opts) 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 @@ def init(opts) 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..efe67ad7a 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
@@ -566,30 +591,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
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,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 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) 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)
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 @@
<%= Pleroma.Config.get([:instance, :name]) %>
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>