X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Factivity_pub%2Factivity_pub_controller.ex;h=c07f91b2e9cb0e31dd85071d0608f1869a20683c;hb=3e0a5851e528135a04ca8f9ab59607ecc3ba42d9;hp=8b9eb4a2c718598d372c5ca6c288f75ac50f808f;hpb=dfd2c741849e9afaf35e3ddbecbb50feb47f5d22;p=akkoma
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 8b9eb4a2c..c07f91b2e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
+# Copyright © 2017-2021 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
@@ -9,18 +9,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery
alias Pleroma.Object
alias Pleroma.Object.Fetcher
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.FederatingPlug
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator
+ alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
+ alias Pleroma.Web.Plugs.FederatingPlug
require Logger
@@ -32,16 +35,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
plug(
EnsureAuthenticatedPlug,
- [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions
+ [unless_func: &FederatingPlug.federating?/1] when action not in @federating_only_actions
)
+ # Note: :following and :followers must be served even without authentication (as via :api)
plug(
EnsureAuthenticatedPlug
- when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers]
+ when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
)
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
+
plug(
- Pleroma.Plugs.Cache,
+ Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object]
)
@@ -60,8 +66,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def user(conn, %{"nickname" => nickname}) do
- with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -72,19 +77,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
- def object(conn, %{"uuid" => uuid}) do
- with ap_id <- o_status_url(conn, :object, uuid),
+ def object(%{assigns: assigns} = conn, _) do
+ with ap_id <- Endpoint.url() <> conn.request_path,
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(object)} do
+ user <- Map.get(assigns, :user, nil),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
conn
+ |> maybe_skip_cache(user)
|> assign(:tracking_fun_data, object.id)
|> set_cache_ttl_for(object)
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("object.json", object: object)
else
- {:public?, false} ->
- {:error, :not_found}
+ {:visible?, false} -> {:error, :not_found}
+ nil -> {:error, :not_found}
end
end
@@ -98,24 +105,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
- def activity(conn, %{"uuid" => uuid}) do
- with ap_id <- o_status_url(conn, :activity, uuid),
+ def activity(%{assigns: assigns} = conn, _) do
+ with ap_id <- Endpoint.url() <> conn.request_path,
%Activity{} = activity <- Activity.normalize(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(activity)} do
+ {_, true} <- {:local?, activity.local},
+ user <- Map.get(assigns, :user, nil),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do
conn
+ |> maybe_skip_cache(user)
|> maybe_set_tracking_data(activity)
|> set_cache_ttl_for(activity)
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("object.json", object: activity)
else
- {:public?, false} -> {:error, :not_found}
+ {:visible?, false} -> {:error, :not_found}
+ {:local?, false} -> {:error, :not_found}
nil -> {:error, :not_found}
end
end
defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
- object_id = Object.normalize(activity).id
+ object_id = Object.normalize(activity, fetch: false).id
assign(conn, :tracking_fun_data, object_id)
end
@@ -141,6 +152,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
assign(conn, :cache_ttl, ttl)
end
+ def maybe_skip_cache(conn, user) do
+ if user do
+ conn
+ |> assign(:skip_cache, true)
+ else
+ conn
+ end
+ end
+
# GET /relay/following
def relay_following(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
@@ -153,7 +173,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_follows, true} <-
{:show_follows, (for_user && for_user == user) || !user.hide_follows} do
{page, _} = Integer.parse(page)
@@ -171,8 +190,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -192,7 +210,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_followers, true} <-
{:show_followers, (for_user && for_user == user) || !user.hide_followers} do
{page, _} = Integer.parse(page)
@@ -210,8 +227,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -224,37 +240,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
- activities =
- if params["max_id"] do
- ActivityPub.fetch_user_activities(user, for_user, %{
- "max_id" => params["max_id"],
- # This is a hack because postgres generates inefficient queries when filtering by
- # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
- "include_poll_votes" => true,
- "limit" => 10
- })
- else
- ActivityPub.fetch_user_activities(user, for_user, %{
- "limit" => 10,
- "include_poll_votes" => true
- })
- end
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
+ # "include_poll_votes" is a hack because postgres generates inefficient
+ # queries when filtering by 'Answer', poll votes will be hidden by the
+ # visibility filter in this case anyway
+ params =
+ params
+ |> Map.drop(["nickname", "page"])
+ |> Map.put("include_poll_votes", true)
+ |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+
+ activities = ActivityPub.fetch_user_activities(user, for_user, params)
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
|> render("activity_collection_page.json", %{
activities: activities,
+ pagination: ControllerHelper.get_pagination_fields(conn, activities),
iri: "#{user.ap_id}/outbox"
})
end
end
def outbox(conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -277,15 +287,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
+ def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
+ conn
+ |> put_status(:bad_request)
+ |> json("Invalid HTTP Signature")
+ end
+
# POST /relay/inbox -or- POST /internal/fetch/inbox
- def inbox(conn, params) do
- if params["type"] == "Create" && FederatingPlug.federating?() do
+ def inbox(conn, %{"type" => "Create"} = params) do
+ if FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params)
else
- post_inbox_fallback(conn, params)
+ conn
+ |> put_status(:bad_request)
+ |> json("Not federating")
end
end
+ def inbox(conn, _params) do
+ conn
+ |> put_status(:bad_request)
+ |> json("error, missing HTTP Signature")
+ end
+
defp post_inbox_relayed_create(conn, params) do
Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source"
@@ -296,32 +320,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
- defp post_inbox_fallback(conn, params) do
- headers = Enum.into(conn.req_headers, %{})
-
- if headers["signature"] && params["actor"] &&
- String.contains?(headers["signature"], params["actor"]) do
- Logger.debug(
- "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
- )
-
- Logger.debug(inspect(conn.req_headers))
- end
-
- conn
- |> put_status(:bad_request)
- |> json(dgettext("errors", "error"))
- end
-
defp represent_service_actor(%User{} = user, conn) do
- with {:ok, user} <- User.ensure_keys_present(user) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("user.json", %{user: user})
- else
- nil -> {:error, :not_found}
- end
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("user.json", %{user: user})
end
defp represent_service_actor(nil, _), do: {:error, :not_found}
@@ -349,21 +352,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do
+ params =
+ params
+ |> Map.drop(["nickname", "page"])
+ |> Map.put("blocking_user", user)
+ |> Map.put("user", user)
+ |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+
activities =
- if params["max_id"] do
- ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
- "max_id" => params["max_id"],
- "limit" => 10
- })
- else
- ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
- end
+ [user.ap_id | User.following(user)]
+ |> ActivityPub.fetch_activities(params)
+ |> Enum.reverse()
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
|> render("activity_collection_page.json", %{
activities: activities,
+ pagination: ControllerHelper.get_pagination_fields(conn, activities),
iri: "#{user.ap_id}/inbox"
})
end
@@ -371,12 +377,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
"nickname" => nickname
}) do
- with {:ok, user} <- User.ensure_keys_present(user) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
- end
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
end
def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
@@ -393,67 +397,90 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(err)
end
- defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do
- object =
- params["object"]
- |> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id())
- |> Transmogrifier.fix_object()
+ defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
+ when is_map(object) do
+ length =
+ [object["content"], object["summary"], object["name"]]
+ |> Enum.filter(&is_binary(&1))
+ |> Enum.join("")
+ |> String.length()
- ActivityPub.create(%{
- to: params["to"],
- actor: user,
- context: object["context"],
- object: object,
- additional: Map.take(params, ["cc"])
- })
- end
+ limit = Pleroma.Config.get([:instance, :limit])
+
+ if length < limit do
+ object =
+ object
+ |> Transmogrifier.strip_internal_fields()
+ |> Map.put("attributedTo", actor)
+ |> Map.put("actor", actor)
+ |> Map.put("id", Utils.generate_object_id())
- defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
- with %Object{} = object <- Object.normalize(params["object"]),
- true <- user.is_moderator || user.ap_id == object.data["actor"],
- {:ok, delete} <- ActivityPub.delete(object) do
- {:ok, delete}
+ {:ok, Map.put(activity, "object", object)}
else
- _ -> {:error, dgettext("errors", "Can't delete object")}
+ {:error,
+ dgettext(
+ "errors",
+ "Character limit (%{limit} characters) exceeded, contains %{length} characters",
+ limit: limit,
+ length: length
+ )}
end
end
- defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
- with %Object{} = object <- Object.normalize(params["object"]),
- {:ok, activity, _object} <- ActivityPub.like(user, object) do
+ defp fix_user_message(
+ %User{ap_id: actor} = user,
+ %{"type" => "Delete", "object" => object} = activity
+ ) do
+ with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
+ {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
{:ok, activity}
else
- _ -> {:error, dgettext("errors", "Can't like object")}
+ {:normalize, _} ->
+ {:error, "No such object found"}
+
+ {:permission, _} ->
+ {:forbidden, "You can't delete this object"}
end
end
- defp handle_user_activity(_, _) do
- {:error, dgettext("errors", "Unhandled activity type")}
+ defp fix_user_message(%User{}, activity) do
+ {:ok, activity}
end
def update_outbox(
- %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
+ %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
%{"nickname" => nickname} = params
) do
- actor = user.ap_id()
-
params =
params
- |> Map.drop(["id"])
+ |> Map.drop(["nickname"])
+ |> Map.put("id", Utils.generate_activity_id())
|> Map.put("actor", actor)
- |> Transmogrifier.fix_addressing()
- with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
+ with {:ok, params} <- fix_user_message(user, params),
+ {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
+ %Activity{data: activity_data} <- Activity.normalize(activity) do
conn
|> put_status(:created)
- |> put_resp_header("location", activity.data["id"])
- |> json(activity.data)
+ |> put_resp_header("location", activity_data["id"])
+ |> json(activity_data)
else
+ {:forbidden, message} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(message)
+
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(message)
+
+ e ->
+ Logger.warn(fn -> "AP C2S: #{inspect(e)}" end)
+
+ conn
+ |> put_status(:bad_request)
+ |> json("Bad Request")
end
end
@@ -490,31 +517,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
- defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
- {:ok, new_user} = User.ensure_keys_present(user)
-
- for_user =
- if new_user != user and match?(%User{}, for_user) do
- User.get_cached_by_nickname(for_user.nickname)
- else
- for_user
- end
-
- {new_user, for_user}
- end
-
- # TODO: Add support for "object" field
- @doc """
- Endpoint based on
-
- 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
- """
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
@@ -529,4 +531,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(object.data)
end
end
+
+ def pinned(conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> json(UserView.render("featured.json", %{user: user}))
+ end
+ end
end