yeet AP C2S support
literally nothing uses C2S AP, and it's another route into core systems which requires analysis and maintenance. A second API is just extra surface for potentially bad things so let's take it out back and obliterate it
This commit is contained in:
parent
c1f0b6b875
commit
ddb8a5ef73
3 changed files with 8 additions and 744 deletions
|
@ -9,16 +9,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
alias Pleroma.Delivery
|
alias Pleroma.Delivery
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.ControllerHelper
|
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
|
alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
|
||||||
|
@ -37,14 +33,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
[unless_func: &FederatingPlug.federating?/1] 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]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
Pleroma.Web.Plugs.Cache,
|
Pleroma.Web.Plugs.Cache,
|
||||||
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
|
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
|
||||||
|
@ -234,43 +222,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def outbox(
|
@doc """
|
||||||
%{assigns: %{user: for_user}} = conn,
|
POST /users/:nickname/inbox
|
||||||
%{"nickname" => nickname, "page" => page?} = params
|
"""
|
||||||
)
|
|
||||||
when page? in [true, "true"] do
|
|
||||||
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) do
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/activity+json")
|
|
||||||
|> put_view(UserView)
|
|
||||||
|> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||||
with %User{} = recipient <- User.get_cached_by_nickname(nickname),
|
with %User{} = recipient <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
|
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
|
||||||
|
@ -317,163 +271,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> represent_service_actor(conn)
|
|> represent_service_actor(conn)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
|
|
||||||
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/activity+json")
|
|
||||||
|> put_view(UserView)
|
|
||||||
|> render("user.json", %{user: user})
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_inbox(
|
|
||||||
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
|
|
||||||
%{"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 =
|
|
||||||
[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
|
|
||||||
|
|
||||||
def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
|
|
||||||
"nickname" => nickname
|
|
||||||
}) do
|
|
||||||
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, %{
|
|
||||||
"nickname" => nickname
|
|
||||||
}) do
|
|
||||||
err =
|
|
||||||
dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
|
|
||||||
nickname: nickname,
|
|
||||||
as_nickname: as_nickname
|
|
||||||
)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
{:ok, Map.put(activity, "object", object)}
|
|
||||||
else
|
|
||||||
{:error,
|
|
||||||
dgettext(
|
|
||||||
"errors",
|
|
||||||
"Character limit (%{limit} characters) exceeded, contains %{length} characters",
|
|
||||||
limit: limit,
|
|
||||||
length: length
|
|
||||||
)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
{:normalize, _} ->
|
|
||||||
{:error, "No such object found"}
|
|
||||||
|
|
||||||
{:permission, _} ->
|
|
||||||
{:forbidden, "You can't delete this object"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fix_user_message(%User{}, activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_outbox(
|
|
||||||
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
|
|
||||||
%{"nickname" => nickname} = params
|
|
||||||
) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.drop(["nickname"])
|
|
||||||
|> Map.put("id", Utils.generate_activity_id())
|
|
||||||
|> Map.put("actor", actor)
|
|
||||||
|
|
||||||
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)
|
|
||||||
else
|
|
||||||
{:forbidden, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(message)
|
|
||||||
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(message)
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json("Bad Request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
|
|
||||||
err =
|
|
||||||
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
|
|
||||||
nickname: nickname,
|
|
||||||
as_nickname: user.nickname
|
|
||||||
)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp errors(conn, {:error, :not_found}) do
|
defp errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
@ -495,21 +292,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
|
@doc """
|
||||||
with {:ok, object} <-
|
GET /users/:nickname/collections/featured
|
||||||
ActivityPub.upload(
|
"""
|
||||||
file,
|
|
||||||
actor: User.ap_id(user),
|
|
||||||
description: Map.get(data, "description")
|
|
||||||
) do
|
|
||||||
Logger.debug(inspect(object))
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> json(object.data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pinned(conn, %{"nickname" => nickname}) do
|
def pinned(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -797,26 +797,15 @@ defmodule Pleroma.Web.Router do
|
||||||
plug(:after_auth)
|
plug(:after_auth)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
|
||||||
pipe_through([:activitypub_client])
|
|
||||||
|
|
||||||
get("/api/ap/whoami", ActivityPubController, :whoami)
|
|
||||||
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
|
||||||
|
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
|
||||||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
|
||||||
post("/api/ap/upload_media", ActivityPubController, :upload_media)
|
|
||||||
|
|
||||||
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
|
||||||
end
|
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
# Note: html format is supported only if static FE is enabled
|
# Note: html format is supported only if static FE is enabled
|
||||||
pipe_through([:accepts_html_json, :static_fe, :activitypub_client])
|
pipe_through([:accepts_html_json, :static_fe, :activitypub_client])
|
||||||
|
|
||||||
# The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
|
# The following two are used in both staticFE and AP S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
|
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
|
|
|
@ -1028,33 +1028,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert Activity.get_by_ap_id(data["id"])
|
assert Activity.get_by_ap_id(data["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects reads from other users", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/inbox")
|
|
||||||
|
|
||||||
assert json_response(conn, 403)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns a note activity in a collection", %{conn: conn} do
|
|
||||||
note_activity = insert(:direct_note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/inbox?page=true")
|
|
||||||
|
|
||||||
assert response(conn, 200) =~ note_object.data["content"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
data = Map.put(data, "bcc", [user.ap_id])
|
data = Map.put(data, "bcc", [user.ap_id])
|
||||||
|
@ -1123,20 +1096,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
refute recipient.follower_address in activity.data["to"]
|
refute recipient.follower_address in activity.data["to"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it requires authentication", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
conn = put_req_header(conn, "accept", "application/activity+json")
|
|
||||||
|
|
||||||
ret_conn = get(conn, "/users/#{user.nickname}/inbox")
|
|
||||||
assert json_response(ret_conn, 403)
|
|
||||||
|
|
||||||
ret_conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> get("/users/#{user.nickname}/inbox")
|
|
||||||
|
|
||||||
assert json_response(ret_conn, 200)
|
|
||||||
end
|
|
||||||
|
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
test "forwarded report", %{conn: conn} do
|
test "forwarded report", %{conn: conn} do
|
||||||
|
@ -1276,386 +1235,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /users/:nickname/outbox" do
|
|
||||||
test "it paginates correctly", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
conn = assign(conn, :user, user)
|
|
||||||
outbox_endpoint = user.ap_id <> "/outbox"
|
|
||||||
|
|
||||||
_posts =
|
|
||||||
for i <- 0..25 do
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
|
|
||||||
activity
|
|
||||||
end
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(outbox_endpoint <> "?page=true")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
|
|
||||||
assert length(result["orderedItems"]) == 20
|
|
||||||
assert length(result_ids) == 20
|
|
||||||
assert result["next"]
|
|
||||||
assert String.starts_with?(result["next"], outbox_endpoint)
|
|
||||||
|
|
||||||
result_next =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(result["next"])
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
|
|
||||||
assert length(result_next["orderedItems"]) == 6
|
|
||||||
assert length(result_next_ids) == 6
|
|
||||||
refute Enum.find(result_next_ids, fn x -> x in result_ids end)
|
|
||||||
refute Enum.find(result_ids, fn x -> x in result_next_ids end)
|
|
||||||
assert String.starts_with?(result["id"], outbox_endpoint)
|
|
||||||
|
|
||||||
result_next_again =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(result_next["id"])
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert result_next == result_next_again
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns 200 even if there're no activities", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
outbox_endpoint = user.ap_id <> "/outbox"
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(outbox_endpoint)
|
|
||||||
|
|
||||||
result = json_response(conn, 200)
|
|
||||||
assert outbox_endpoint == result["id"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns a local note activity when authenticated as local user", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
reader = insert(:user)
|
|
||||||
{:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
|
|
||||||
ap_id = note_activity.data["id"]
|
|
||||||
|
|
||||||
resp =
|
|
||||||
conn
|
|
||||||
|> assign(:user, reader)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not return a local note activity when unauthenticated", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
|
|
||||||
|
|
||||||
resp =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert %{"orderedItems" => []} = resp
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns a note activity in a collection", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
|
||||||
|
|
||||||
assert response(conn, 200) =~ note_object.data["content"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns an announce activity in a collection", %{conn: conn} do
|
|
||||||
announce_activity = insert(:announce_activity)
|
|
||||||
user = User.get_cached_by_ap_id(announce_activity.data["actor"])
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
|
||||||
|
|
||||||
assert response(conn, 200) =~ announce_activity.data["object"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "It returns poll Answers when authenticated", %{conn: conn} do
|
|
||||||
poller = insert(:user)
|
|
||||||
voter = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(poller, %{
|
|
||||||
status: "suya...",
|
|
||||||
poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
|
|
||||||
})
|
|
||||||
|
|
||||||
assert question = Object.normalize(activity, fetch: false)
|
|
||||||
|
|
||||||
{:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
|
|
||||||
|
|
||||||
assert outbox_get =
|
|
||||||
conn
|
|
||||||
|> assign(:user, voter)
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(voter.ap_id <> "/outbox?page=true")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert [answer_outbox] = outbox_get["orderedItems"]
|
|
||||||
assert answer_outbox["id"] == activity.data["id"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /users/:nickname/outbox (C2S)" do
|
|
||||||
setup do: clear_config([:instance, :limit])
|
|
||||||
|
|
||||||
setup do
|
|
||||||
[
|
|
||||||
activity: %{
|
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
||||||
"type" => "Create",
|
|
||||||
"object" => %{
|
|
||||||
"type" => "Note",
|
|
||||||
"content" => "AP C2S test",
|
|
||||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"cc" => []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects posts from other users / unauthenticated users", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
conn = put_req_header(conn, "content-type", "application/activity+json")
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it inserts an incoming create activity into the database", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
assert result["object"]
|
|
||||||
assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
|
|
||||||
assert object["content"] == activity["object"]["content"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
activity =
|
|
||||||
activity
|
|
||||||
|> put_in(["object", "type"], "Benis")
|
|
||||||
|
|
||||||
_result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(400)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it inserts an incoming sensitive activity into the database", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
conn = assign(conn, :user, user)
|
|
||||||
object = Map.put(activity["object"], "sensitive", true)
|
|
||||||
activity = Map.put(activity, "object", object)
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(response["id"])
|
|
||||||
assert response["object"]
|
|
||||||
assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
|
|
||||||
assert response_object["sensitive"] == true
|
|
||||||
assert response_object["content"] == activity["object"]["content"]
|
|
||||||
|
|
||||||
representation =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(response["id"])
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert representation["object"]["sensitive"] == true
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
|
|
||||||
user = insert(:user)
|
|
||||||
activity = Map.put(activity, "type", "BadType")
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|
|
||||||
assert json_response(conn, 400)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
"type" => "Delete",
|
|
||||||
"object" => %{
|
|
||||||
"id" => note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
|
|
||||||
assert object = Object.get_by_ap_id(note_object.data["id"])
|
|
||||||
assert object.data["type"] == "Tombstone"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects delete activity of object from other actor", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
type: "Delete",
|
|
||||||
object: %{
|
|
||||||
id: note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|
|
||||||
assert json_response(conn, 403)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it increases like count when receiving a like action", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
type: "Like",
|
|
||||||
object: %{
|
|
||||||
id: note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|
|
||||||
result = json_response(conn, 201)
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
|
|
||||||
assert object = Object.get_by_ap_id(note_object.data["id"])
|
|
||||||
assert object.data["like_count"] == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it doesn't spreads faulty attributedTo or actor fields", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
reimu = insert(:user, nickname: "reimu")
|
|
||||||
cirno = insert(:user, nickname: "cirno")
|
|
||||||
|
|
||||||
assert reimu.ap_id
|
|
||||||
assert cirno.ap_id
|
|
||||||
|
|
||||||
activity =
|
|
||||||
activity
|
|
||||||
|> put_in(["object", "actor"], reimu.ap_id)
|
|
||||||
|> put_in(["object", "attributedTo"], reimu.ap_id)
|
|
||||||
|> put_in(["actor"], reimu.ap_id)
|
|
||||||
|> put_in(["attributedTo"], reimu.ap_id)
|
|
||||||
|
|
||||||
_reimu_outbox =
|
|
||||||
conn
|
|
||||||
|> assign(:user, cirno)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{reimu.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
|
|
||||||
cirno_outbox =
|
|
||||||
conn
|
|
||||||
|> assign(:user, cirno)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{cirno.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert cirno_outbox["attributedTo"] == nil
|
|
||||||
assert cirno_outbox["actor"] == cirno.ap_id
|
|
||||||
|
|
||||||
assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
|
|
||||||
assert cirno_object.data["actor"] == cirno.ap_id
|
|
||||||
assert cirno_object.data["attributedTo"] == cirno.ap_id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "Character limitation", %{conn: conn, activity: activity} do
|
|
||||||
clear_config([:instance, :limit], 5)
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(400)
|
|
||||||
|
|
||||||
assert result == "Character limit (5 characters) exceeded, contains 11 characters"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "/relay/followers" do
|
describe "/relay/followers" do
|
||||||
test "it returns relay followers", %{conn: conn} do
|
test "it returns relay followers", %{conn: conn} do
|
||||||
relay_actor = Relay.get_actor()
|
relay_actor = Relay.get_actor()
|
||||||
|
@ -1977,95 +1556,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Additional ActivityPub C2S endpoints" do
|
|
||||||
test "GET /api/ap/whoami", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> get("/api/ap/whoami")
|
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
|
|
||||||
assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> get("/api/ap/whoami")
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do: clear_config([:media_proxy])
|
|
||||||
setup do: clear_config([Pleroma.Upload])
|
|
||||||
|
|
||||||
test "POST /api/ap/upload_media", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
desc = "Description of the image"
|
|
||||||
|
|
||||||
image = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
object =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
|
||||||
|> json_response(:created)
|
|
||||||
|
|
||||||
assert object["name"] == desc
|
|
||||||
assert object["type"] == "Document"
|
|
||||||
assert object["actor"] == user.ap_id
|
|
||||||
assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
|
|
||||||
assert is_binary(object_href)
|
|
||||||
assert object_mediatype == "image/jpeg"
|
|
||||||
assert String.ends_with?(object_href, ".jpg")
|
|
||||||
|
|
||||||
activity_request = %{
|
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
||||||
"type" => "Create",
|
|
||||||
"object" => %{
|
|
||||||
"type" => "Note",
|
|
||||||
"content" => "AP C2S test, attachment",
|
|
||||||
"attachment" => [object],
|
|
||||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"cc" => []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activity_response =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity_request)
|
|
||||||
|> json_response(:created)
|
|
||||||
|
|
||||||
assert activity_response["id"]
|
|
||||||
assert activity_response["object"]
|
|
||||||
assert activity_response["actor"] == user.ap_id
|
|
||||||
|
|
||||||
assert %Object{data: %{"attachment" => [attachment]}} =
|
|
||||||
Object.normalize(activity_response["object"], fetch: false)
|
|
||||||
|
|
||||||
assert attachment["type"] == "Document"
|
|
||||||
assert attachment["name"] == desc
|
|
||||||
|
|
||||||
assert [
|
|
||||||
%{
|
|
||||||
"href" => ^object_href,
|
|
||||||
"type" => "Link",
|
|
||||||
"mediaType" => ^object_mediatype
|
|
||||||
}
|
|
||||||
] = attachment["url"]
|
|
||||||
|
|
||||||
# Fails if unauthenticated
|
|
||||||
conn
|
|
||||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "pinned collection", %{conn: conn} do
|
test "pinned collection", %{conn: conn} do
|
||||||
clear_config([:instance, :max_pinned_statuses], 2)
|
clear_config([:instance, :max_pinned_statuses], 2)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
Loading…
Reference in a new issue